diff --git a/KCC-Linux.ui b/KCC-Linux.ui index 6d8eaba..03bba46 100644 --- a/KCC-Linux.ui +++ b/KCC-Linux.ui @@ -388,7 +388,7 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Use it when Panel View support is not needed.</span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></span><span style=" font-family:'MS Shell Dlg 2';">- Maximum quality when zoom is not enabled.<br />- Poor quality when zoom is enabled.<br />- Lowest file size.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Not zoomed image </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; font-style:italic;">might </span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">be a little blurry.</span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></span><span style=" font-family:'MS Shell Dlg 2';">- Medium/High quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Not zoomed image </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; font-style:italic;">might </span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">be a little blurry.<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Smaller images might be forcefully upscaled in this mode.</span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></span><span style=" font-family:'MS Shell Dlg 2';">- Medium/High quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Maximum possible quality.</span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></span><span style=" font-family:'MS Shell Dlg 2';">- Maximum quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.<br />- Very high file size.</span></p></body></html> diff --git a/KCC-OSX.ui b/KCC-OSX.ui index 4896197..465af4b 100644 --- a/KCC-OSX.ui +++ b/KCC-OSX.ui @@ -388,7 +388,7 @@ Qt::NoFocus - <html><head/><body><p><span style="font-size:12pt; font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span><span style="font-size:12pt; font-style:italic;">Use it when Panel View support is not needed.</span><span style="font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style="font-size:12pt;">- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</span></p><p><span style="font-size:12pt; font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span><span style="font-size:12pt; font-style:italic;">Not zoomed image </span><span style="font-size:12pt; font-weight:600; font-style:italic;">might </span><span style="font-size:12pt; font-style:italic;">be a little blurry.</span><span style="font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style="font-size:12pt;">- Medium/High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</span></p><p><span style="font-size:12pt; font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span><span style="font-size:12pt; font-style:italic;">Maximum possible quality.</span><span style="font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style="font-size:12pt;">- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</span></p></body></html> + <html><head/><body><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span><span style=" font-size:12pt; font-style:italic;">Use it when Panel View support is not needed.</span><span style=" font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style=" font-size:12pt;">- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</span></p><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span><span style=" font-size:12pt; font-style:italic;">Not zoomed image </span><span style=" font-size:12pt; font-weight:600; font-style:italic;">might </span><span style=" font-size:12pt; font-style:italic;">be a little blurry.<br/>Smaller images might be forcefully upscaled in this mode.</span><span style=" font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style=" font-size:12pt;">- Medium/High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</span></p><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span><span style=" font-size:12pt; font-style:italic;">Maximum possible quality.</span><span style=" font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style=" font-size:12pt;">- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</span></p></body></html> High/Ultra quality diff --git a/KCC.qrc b/KCC.qrc index fd00909..6795573 100644 --- a/KCC.qrc +++ b/KCC.qrc @@ -3,6 +3,7 @@ icons/comic2ebook.png + icons/Kobo.png icons/Other.png icons/Kindle.png diff --git a/KCC.ui b/KCC.ui index 6725915..3d2e7b0 100644 --- a/KCC.ui +++ b/KCC.ui @@ -340,7 +340,7 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br /></span><span style=" font-style:italic;">Use it when Panel View support is not needed.</span><span style=" font-weight:600; text-decoration: underline;"><br /></span>- Maximum quality when zoom is not enabled.<br />- Poor quality when zoom is enabled.<br />- Lowest file size.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br /></span><span style=" font-style:italic;">Not zoomed image </span><span style=" font-weight:600; font-style:italic;">might </span><span style=" font-style:italic;">be </span><span style=" font-style:italic;">a little blurry.</span><span style=" font-weight:600; text-decoration: underline;"><br /></span>- Medium/High quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br /></span><span style=" font-style:italic;">Not zoomed image </span><span style=" font-weight:600; font-style:italic;">might </span><span style=" font-style:italic;">be a little blurry.<br />Smaller images might be forcefully upscaled in this mode.</span><span style=" font-weight:600; text-decoration: underline;"><br /></span>- Medium/High quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.</p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br /></span><span style=" font-style:italic;">Maximum possible quality.</span><span style=" font-weight:600; text-decoration: underline;"><br /></span>- Maximum quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.<br />- Very high file size.</p></body></html> diff --git a/README.md b/README.md index fcf64e5..5935b47 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # KCC -**Kindle Comic Converter** is a Python app to convert comic files or folders to ePub or Panel View MOBI. +**Kindle Comic Converter** is a Python app to convert comic files or folders to ePub, Panel View MOBI or E-Ink optimized CBZ. It was initally developed for Kindle but since v2.2 it outputs valid ePub 2.0 so _**despite its name, KCC is actually a comic to EPUB converter that every e-reader owner can happily use**_. It can also optionally optimize images by applying a number of transformations. @@ -11,7 +11,8 @@ Amazon's tool is for comic publishers and involves a lot of manual effort, while _KC2_ in no way is a replacement for **KCC** so you can be quite confident we'll going to carry on developing our little monster ;-) ### Issues / new features / donations -If you have some problems using KCC please [file an issue here](https://github.com/ciromattia/kcc/issues/new). +If you have general questions about usage, feedback etc. please [post it here](http://www.mobileread.com/forums/showthread.php?t=207461). +If you have some **technical** problems using KCC please [file an issue here](https://github.com/ciromattia/kcc/issues/new). If you can fix an open issue, fork & make a pull request. If you want more chances an issue is fixes or your wanted feature added, consider [placing a bounty](https://www.bountysource.com/trackers/65571-ciromattia-kcc)! @@ -23,54 +24,35 @@ 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.vulturis.eu/Windows/](http://kcc.vulturis.eu/Windows/) - **Linux:** [http://kcc.vulturis.eu/Linux/](http://kcc.vulturis.eu/Linux/) -- **OS X (10.8 or later):** [http://kcc.vulturis.eu/OSX/](http://kcc.vulturis.eu/OSX/) -- **OS X (10.7 or earlier):** Soon™ +- **OS X 10.8+:** [http://kcc.vulturis.eu/OSX/](http://kcc.vulturis.eu/OSX/) ## INPUT FORMATS -**KCC** can understand and convert, at the moment, the following file types: -- PNG, JPG, GIF, TIFF, BMP -- Folders +**KCC** can understand and convert, at the moment, the following input types: +- Folders containing: PNG, JPG, GIF, TIFF or BMP files - CBZ, ZIP - CBR, RAR *(With `unrar` executable)* - CB7, 7Z *(With `7za` executable)* - PDF *(Extracting only contained JPG images)* ## OPTIONAL REQUIREMENTS -- [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)* +- [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)* ### For compiling/running from source: - Python 2.7 - Included in MacOS and Linux, follow the [official documentation](http://www.python.org/getit/windows/) to install on Windows. - [PyQt4](http://www.riverbankcomputing.co.uk/software/pyqt/download) - Please refer to official documentation for installing into your system. -- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.2.1+ - For comic optimizations. Please refer to official documentation for installing into your system. +- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.3.0+ - For comic optimizations. Please refer to official documentation for installing into your system. - [Psutil](https://code.google.com/p/psutil/) - Please refer to official documentation for installing into your system. - **To build OS X release you need a modified QT:** [patch](https://github.com/ciromattia/kcc/blob/master/other/QT-4.8.5-QListWidget.patch) ## USAGE -### Important tips: -* Use high quality source files. **This little detail have a major impact on the final result.** -* Read tooltip of _High/Ultra quality_ option. There are many important informations there. -* When converting images smaller than device resolution remember to enable upscaling. -* Panel View (auto zooming every part of page) can be disabled directly on Kindle. There is no KCC option to do that. -* Check our [wiki](https://github.com/ciromattia/kcc/wiki/Other-devices) for a list of tested Non-Kindle E-Readers. -* The first image found will be set as the comic's cover. -* All files/directories will be added to EPUB in alphabetical order. -* Using high/ultra quality output option with Kindle Fire HD/HDX in most cases is just waste of space. -* ComicRack metadata will be parsed only if they are saved in *ComicInfo.xml* file. - -### Calibre: -* Calibre can be used to upload files created by KCC. -* Uploading KCC output with Calibre will remove *Personal* tag from cover. -* **Don't convert files created by KCC with Calibre!** Any conversion process will corrupt the file! -* Don't use Calibre reader to preview files created by KCC. It can't parse them correctly. - -### GUI - Should be pretty self-explanatory. All options have detailed informations in tooltips. After completed conversion you should find ready file alongside the original input file (same directory). +Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details. + ### Standalone `comic2ebook.py` usage: ``` @@ -79,7 +61,7 @@ Usage: comic2ebook.py [options] comic_file|comic_folder Options: MAIN: -p PROFILE, --profile=PROFILE - Device profile (Choose one among K1, K2, K345, KDX, KHD, KF, KFHD, KFHD8, KFHDX, KFHDX8, KFA) [Default=KHD] + Device profile (Choose one among K1, K2, K345, KDX, KHD, KF, KFHD, KFHD8, KFHDX, KFHDX8, KFA, KoMT, KoG, KoA, KoAHD) [Default=KHD] -q QUALITY, --quality=QUALITY Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0] -m, --manga-style Manga style (Right-to-left reading and splitting) @@ -127,6 +109,7 @@ Options: -y HEIGHT, --height=HEIGHT Height of the target device screen -i, --in-place Overwrite source directory + -m, --merge Combine every directory into a single image before splitting OTHER: -d, --debug Create debug file for every splitted image @@ -149,11 +132,14 @@ The app relies and includes the following scripts/binaries: * [Kindle Paperwhite](http://kcc.vulturis.eu/Samples/Ubunchu!-KPW.mobi) * [Kindle](http://kcc.vulturis.eu/Samples/Ubunchu!-K345.mobi) * [Kindle DX/DXG](http://kcc.vulturis.eu/Samples/Ubunchu!-KDX.mobi) -* [Kindle Fire](http://kcc.vulturis.eu/Samples/Ubunchu!-KF.mobi) * [Kindle Fire HD](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHD.mobi) * [Kindle Fire HD 8.9"](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHD8.mobi) * [Kindle Fire HDX](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHDX.mobi) * [Kindle Fire HDX 8.9"](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHDX8.mobi) +* [Kobo Mini/Touch](http://kcc.vulturis.eu/Samples/Ubunchu!-KoMT.cbz) +* [Kobo Glow](http://kcc.vulturis.eu/Samples/Ubunchu!-KoG.cbz) +* [Kobo Aura](http://kcc.vulturis.eu/Samples/Ubunchu!-KoA.cbz) +* [Kobo Aura HD](http://kcc.vulturis.eu/Samples/Ubunchu!-KoAHD.cbz) ## CHANGELOG ####1.0 @@ -311,6 +297,21 @@ The app relies and includes the following scripts/binaries: * Fixed previous PNG output fix * Fixed Panel View anomalies +####3.7: +* Added profiles for KOBO devices +* Improved Panel View support +* Improved WebToon splitter +* Improved margin color autodetection +* Tweaked EPUB output +* Fixed stretching option +* GUI tweaks and minor bugfixes + +####3.7.1: +* Hotfixed Kobo profiles + +####3.7.2: +* Fixed problems with HQ mode + ## COPYRIGHT Copyright (c) 2012-2013 Ciro Mattia Gonano and Paweł Jastrzębski. diff --git a/icons/Kobo.png b/icons/Kobo.png new file mode 100644 index 0000000..c563dfe Binary files /dev/null and b/icons/Kobo.png differ diff --git a/kcc.iss b/kcc.iss index 7d5bffd..6a8df5c 100644 --- a/kcc.iss +++ b/kcc.iss @@ -66,6 +66,7 @@ Source: "build\exe.win-amd64-2.7\sip.pyd"; DestDir: "{app}"; Flags: ignoreversio Source: "build\exe.win-amd64-2.7\SSLEAY32.dll"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode Source: "build\exe.win-amd64-2.7\unicodedata.pyd"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode Source: "build\exe.win-amd64-2.7\_psutil_mswindows.pyd"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode +Source: "other\vcredist_x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall; Check: Is64BitInstallMode ; x86 files Source: "build\exe.win32-2.7\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion solidbreak; Check: not Is64BitInstallMode Source: "build\exe.win32-2.7\_ctypes.pyd"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode @@ -91,6 +92,7 @@ Source: "build\exe.win32-2.7\sip.pyd"; DestDir: "{app}"; Flags: ignoreversion; C Source: "build\exe.win32-2.7\SSLEAY32.dll"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode Source: "build\exe.win32-2.7\unicodedata.pyd"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode Source: "build\exe.win32-2.7\_psutil_mswindows.pyd"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode +Source: "other\vcredist_x86.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall; Check: not Is64BitInstallMode ; Common files Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak Source: "other\Additional-LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion @@ -104,6 +106,8 @@ Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent +Filename: "{tmp}\vcredist_x64.exe"; Parameters: "/passive /Q:a /c:""msiexec /qb /i vcredist.msi"" "; StatusMsg: "Installing Microsoft Visual C++ 2008 Redistributable Package..."; Check: Is64BitInstallMode +Filename: "{tmp}\vcredist_x86.exe"; Parameters: "/passive /Q:a /c:""msiexec /qb /i vcredist.msi"" "; StatusMsg: "Installing Microsoft Visual C++ 2008 Redistributable Package..."; Check: not Is64BitInstallMode [Messages] WelcomeLabel1=Welcome to the KCC Setup Wizard diff --git a/kcc.py b/kcc.py index a6f9c23..f7bd864 100644 --- a/kcc.py +++ b/kcc.py @@ -26,7 +26,7 @@ __docformat__ = 'restructuredtext en' import sys import os try: - #noinspection PyUnresolvedReferences + # noinspection PyUnresolvedReferences from PyQt4 import QtCore, QtGui, QtNetwork except ImportError: print("ERROR: PyQT4 is not installed!") diff --git a/kcc/KCC_gui.py b/kcc/KCC_gui.py index 5d0dda4..7c4b303 100644 --- a/kcc/KCC_gui.py +++ b/kcc/KCC_gui.py @@ -33,14 +33,13 @@ from time import sleep from shutil import move from http.server import BaseHTTPRequestHandler, HTTPServer from socketserver import ThreadingMixIn -from .image import ProfileData from subprocess import STDOUT, PIPE from PyQt4 import QtGui, QtCore from xml.dom.minidom import parse from html.parser import HTMLParser from .KCC_rc_web import WebContent try: - #noinspection PyUnresolvedReferences + # noinspection PyUnresolvedReferences from psutil import TOTAL_PHYMEM, Popen except ImportError: print("ERROR: Psutil is not installed!") @@ -63,6 +62,8 @@ class Icons: def __init__(self): self.deviceKindle = QtGui.QIcon() self.deviceKindle.addPixmap(QtGui.QPixmap(":/Devices/icons/Kindle.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.deviceKobo = QtGui.QIcon() + self.deviceKobo.addPixmap(QtGui.QPixmap(":/Devices/icons/Kobo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.deviceOther = QtGui.QIcon() self.deviceOther.addPixmap(QtGui.QPixmap(":/Devices/icons/Other.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) @@ -345,7 +346,7 @@ class WorkerThread(QtCore.QThread): def run(self): self.emit(QtCore.SIGNAL("modeConvert"), False) - profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())] + profile = GUI.profiles[str(GUI.DeviceBox.currentText())]['Label'] argv = ["--profile=" + profile] currentJobs = [] if GUI.MangaBox.isChecked(): @@ -356,6 +357,8 @@ class WorkerThread(QtCore.QThread): argv.append("--quality=1") elif GUI.QualityBox.checkState() == 2: argv.append("--quality=2") + if str(GUI.FormatBox.currentText()) == 'CBZ': + argv.append("--cbz-output") if GUI.currentMode == 1: if profile in ['KFHD', 'KFHD8', 'KFHDX', 'KFHDX8']: argv.append("--upscale") @@ -379,8 +382,6 @@ class WorkerThread(QtCore.QThread): if float(GUI.GammaValue) > 0.09: # noinspection PyTypeChecker argv.append("--gamma=" + GUI.GammaValue) - if str(GUI.FormatBox.currentText()) == 'CBZ': - argv.append("--cbz-output") if str(GUI.FormatBox.currentText()) == 'MOBI': argv.append("--batchsplit") if GUI.currentMode > 2: @@ -493,10 +494,8 @@ class WorkerThread(QtCore.QThread): # break #if not self.errors: # for item in outputPath: - # GUI.progress.content = '' - # mobiPath = item.replace('.epub', '.mobi') - # os.remove(mobiPath + '_toclean') - # GUI.completedWork[os.path.basename(mobiPath).encode('utf-8')] = \ + # GUI.completedWork[os.path.basename(mobiPath).encode('utf-8')] = mobiPath.encode('utf-8') + # self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI files... Done!', 'info', True) # mobiPath.encode('utf-8') # self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI files... Done!', 'info', # True) @@ -523,7 +522,7 @@ class WorkerThread(QtCore.QThread): self.emit(QtCore.SIGNAL("addTrayMessage"), 'KindleGen failed to create MOBI!', 'Critical') if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '': self.emit(QtCore.SIGNAL("showDialog"), "KindleGen error:\n\n" + - self.self.kindlegenErrorCode[1]) + self.kindlegenErrorCode[1]) if self.kindlegenErrorCode[0] == 23026: self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file was too big.', 'error') @@ -581,7 +580,7 @@ class KCCGUI(KCC_ui.Ui_KCC): dnames = "" for dname in dnames: if str(dname) != "": - if sys.platform == 'win32': + if sys.platform.startswith('win'): dname = dname.replace('/', '\\') self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir)) GUI.JobList.addItem(dname) @@ -623,67 +622,103 @@ class KCCGUI(KCC_ui.Ui_KCC): MW.setMinimumSize(QtCore.QSize(420, 287)) MW.setMaximumSize(QtCore.QSize(420, 287)) MW.resize(420, 287) + GUI.BasicModeButton.setEnabled(True) + GUI.AdvModeButton.setEnabled(True) GUI.BasicModeButton.setStyleSheet('font-weight:Bold;') GUI.AdvModeButton.setStyleSheet('font-weight:Normal;') - GUI.FormatBox.setCurrentIndex(0) GUI.FormatBox.setEnabled(False) - GUI.NoRotateBox.setChecked(False) - GUI.WebtoonBox.setChecked(False) - GUI.ProcessingBox.setChecked(False) + GUI.OptionsBasic.setEnabled(True) + GUI.OptionsBasic.setVisible(True) + GUI.MangaBox.setChecked(False) + GUI.RotateBox.setChecked(False) + GUI.QualityBox.setChecked(False) GUI.OptionsAdvanced.setEnabled(False) + GUI.OptionsAdvanced.setVisible(False) + GUI.ProcessingBox.setChecked(False) + GUI.UpscaleBox.setChecked(False) + GUI.NoRotateBox.setChecked(False) + GUI.BorderBox.setChecked(False) + GUI.WebtoonBox.setChecked(False) + GUI.NoDitheringBox.setChecked(False) GUI.OptionsAdvancedGamma.setEnabled(False) + GUI.OptionsAdvancedGamma.setVisible(False) GUI.OptionsExpert.setEnabled(False) - GUI.ProcessingBox.hide() - GUI.UpscaleBox.hide() - GUI.NoRotateBox.hide() - GUI.MangaBox.setEnabled(True) - self.changeFormat() + GUI.OptionsExpert.setVisible(False) + GUI.ColorBox.setChecked(False) def modeAdvanced(self): self.currentMode = 2 MW.setMinimumSize(QtCore.QSize(420, 365)) MW.setMaximumSize(QtCore.QSize(420, 365)) MW.resize(420, 365) + GUI.BasicModeButton.setEnabled(True) + GUI.AdvModeButton.setEnabled(True) GUI.BasicModeButton.setStyleSheet('font-weight:Normal;') GUI.AdvModeButton.setStyleSheet('font-weight:Bold;') GUI.FormatBox.setEnabled(True) - GUI.ProcessingBox.show() - GUI.UpscaleBox.show() - GUI.NoRotateBox.show() - GUI.OptionsAdvancedGamma.setEnabled(True) + GUI.OptionsBasic.setEnabled(True) + GUI.OptionsBasic.setVisible(True) + GUI.MangaBox.setChecked(False) + GUI.RotateBox.setChecked(False) + GUI.QualityBox.setChecked(False) GUI.OptionsAdvanced.setEnabled(True) - GUI.OptionsExpert.setEnabled(False) - GUI.MangaBox.setEnabled(True) + GUI.OptionsAdvanced.setVisible(True) + GUI.ProcessingBox.setChecked(False) + GUI.UpscaleBox.setChecked(False) + GUI.NoRotateBox.setChecked(False) + GUI.BorderBox.setChecked(False) + GUI.WebtoonBox.setChecked(False) + GUI.NoDitheringBox.setChecked(False) + GUI.OptionsAdvancedGamma.setEnabled(True) + GUI.OptionsAdvancedGamma.setVisible(True) + GUI.OptionsExpert.setEnabled(True) + GUI.OptionsExpert.setVisible(True) + GUI.ColorBox.setChecked(False) - def modeExpert(self, KFA=False): - self.modeAdvanced() + def modeExpert(self): self.currentMode = 3 MW.setMinimumSize(QtCore.QSize(420, 397)) MW.setMaximumSize(QtCore.QSize(420, 397)) MW.resize(420, 397) + GUI.BasicModeButton.setEnabled(False) + GUI.AdvModeButton.setEnabled(False) + GUI.BasicModeButton.setStyleSheet('font-weight:Normal;') + GUI.AdvModeButton.setStyleSheet('font-weight:Normal;') + GUI.FormatBox.setEnabled(True) + GUI.OptionsBasic.setEnabled(True) + GUI.OptionsBasic.setVisible(True) + GUI.MangaBox.setChecked(False) + GUI.RotateBox.setChecked(False) + GUI.QualityBox.setChecked(False) + GUI.OptionsAdvanced.setEnabled(True) + GUI.OptionsAdvanced.setVisible(True) + GUI.ProcessingBox.setChecked(False) + GUI.UpscaleBox.setChecked(False) + GUI.NoRotateBox.setChecked(False) + GUI.BorderBox.setChecked(False) + GUI.WebtoonBox.setChecked(False) + GUI.NoDitheringBox.setChecked(False) + GUI.OptionsAdvancedGamma.setEnabled(True) + GUI.OptionsAdvancedGamma.setVisible(True) GUI.OptionsExpert.setEnabled(True) - if KFA: - GUI.ColorBox.setChecked(True) - GUI.FormatBox.setCurrentIndex(0) - GUI.FormatBox.setEnabled(False) - else: - GUI.FormatBox.setEnabled(True) - GUI.MangaBox.setChecked(False) - GUI.MangaBox.setEnabled(False) + GUI.OptionsExpert.setVisible(True) + GUI.ColorBox.setChecked(False) def modeConvert(self, enable): if self.currentMode != 3: GUI.BasicModeButton.setEnabled(enable) GUI.AdvModeButton.setEnabled(enable) + if self.currentMode != 1: + GUI.FormatBox.setEnabled(enable) GUI.DirectoryButton.setEnabled(enable) GUI.ClearButton.setEnabled(enable) GUI.FileButton.setEnabled(enable) GUI.DeviceBox.setEnabled(enable) - GUI.FormatBox.setEnabled(enable) GUI.OptionsBasic.setEnabled(enable) GUI.OptionsAdvanced.setEnabled(enable) GUI.OptionsAdvancedGamma.setEnabled(enable) GUI.OptionsExpert.setEnabled(enable) + GUI.ConvertButton.setEnabled(True) if enable: self.conversionAlive = False self.worker.sync() @@ -691,13 +726,6 @@ class KCCGUI(KCC_ui.Ui_KCC): icon.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) GUI.ConvertButton.setIcon(icon) GUI.ConvertButton.setText('Convert') - GUI.ConvertButton.setEnabled(True) - if self.currentMode == 1: - self.modeBasic() - elif self.currentMode == 2: - self.modeAdvanced() - elif self.currentMode == 3: - self.modeExpert() else: self.conversionAlive = True self.worker.sync() @@ -705,16 +733,6 @@ class KCCGUI(KCC_ui.Ui_KCC): icon.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) GUI.ConvertButton.setIcon(icon) GUI.ConvertButton.setText('Abort') - GUI.ConvertButton.setEnabled(True) - - def changeGamma(self, value): - value = float(value) - value = '%.2f' % (value/100) - if float(value) <= 0.09: - GUI.GammaLabel.setText('Gamma: Auto') - else: - GUI.GammaLabel.setText('Gamma: ' + str(value)) - self.GammaValue = value def toggleWebtoonBox(self, value): if value: @@ -729,9 +747,8 @@ class KCCGUI(KCC_ui.Ui_KCC): if not GUI.ProcessingBox.isChecked(): GUI.NoRotateBox.setEnabled(True) GUI.QualityBox.setEnabled(True) - GUI.MangaBox.setEnabled(True) - self.changeDevice(GUI.DeviceBox.currentIndex(), False) - self.changeFormat() + if GUI.profiles[str(GUI.DeviceBox.currentText())]['MangaMode']: + GUI.MangaBox.setEnabled(True) def toggleNoSplitRotate(self, value): if value: @@ -740,8 +757,6 @@ class KCCGUI(KCC_ui.Ui_KCC): else: if not GUI.ProcessingBox.isChecked(): GUI.RotateBox.setEnabled(True) - self.changeDevice(GUI.DeviceBox.currentIndex(), False) - self.changeFormat() def toggleProcessingBox(self, value): if value: @@ -765,7 +780,6 @@ class KCCGUI(KCC_ui.Ui_KCC): GUI.GammaLabel.setEnabled(False) else: GUI.RotateBox.setEnabled(True) - GUI.QualityBox.setEnabled(True) GUI.UpscaleBox.setEnabled(True) GUI.NoRotateBox.setEnabled(True) GUI.BorderBox.setEnabled(True) @@ -774,45 +788,73 @@ class KCCGUI(KCC_ui.Ui_KCC): GUI.ColorBox.setEnabled(True) GUI.GammaSlider.setEnabled(True) GUI.GammaLabel.setEnabled(True) - self.changeDevice(GUI.DeviceBox.currentIndex(), False) - self.changeFormat() + if GUI.profiles[str(GUI.DeviceBox.currentText())]['Quality']: + GUI.QualityBox.setEnabled(True) - def changeDevice(self, value, showInfo=True): - if value == 9: - GUI.BasicModeButton.setEnabled(False) - GUI.AdvModeButton.setEnabled(False) - if showInfo: - self.addMessage('' - 'List of supported Non-Kindle devices', 'info') - self.modeExpert() - elif value == 8: - GUI.BasicModeButton.setEnabled(False) - GUI.AdvModeButton.setEnabled(False) - self.modeExpert(True) + def changeGamma(self, value): + value = float(value) + value = '%.2f' % (value/100) + if float(value) <= 0.09: + GUI.GammaLabel.setText('Gamma: Auto') + else: + GUI.GammaLabel.setText('Gamma: ' + str(value)) + self.GammaValue = value + + def changeDevice(self): + if self.currentMode == 1: + self.modeBasic() + elif self.currentMode == 2: + self.modeAdvanced() elif self.currentMode == 3: + self.modeExpert() + profile = GUI.profiles[str(GUI.DeviceBox.currentText())] + if profile['ForceExpert']: + self.modeExpert() + GUI.BasicModeButton.setEnabled(False) + GUI.AdvModeButton.setEnabled(False) + else: GUI.BasicModeButton.setEnabled(True) GUI.AdvModeButton.setEnabled(True) - self.modeBasic() - if value in [9, 11, 12, 13]: - GUI.QualityBox.setChecked(False) - GUI.QualityBox.setEnabled(False) - self.QualityBoxDisabled = True - if value in [4, 5, 6, 7]: - if GUI.UpscaleBox.isEnabled(): - GUI.UpscaleBox.setChecked(True) - else: - if not GUI.WebtoonBox.isChecked() and not GUI.ProcessingBox.isChecked() \ - and str(GUI.FormatBox.currentText()) != 'CBZ' and value not in [9, 11, 12, 13]: - GUI.QualityBox.setEnabled(True) - self.QualityBoxDisabled = False + if self.currentMode == 3: + self.modeBasic() + self.changeFormat() + GUI.GammaSlider.setValue(0) + self.changeGamma(0) + if profile['DefaultUpscale']: + GUI.UpscaleBox.setChecked(True) + if str(GUI.DeviceBox.currentText()) == 'Other': + self.addMessage('' + 'List of supported Non-Kindle devices.', 'info') - def changeFormat(self): - if str(GUI.FormatBox.currentText()) == 'CBZ': - GUI.QualityBox.setChecked(False) - GUI.QualityBox.setEnabled(False) + def changeFormat(self, outputFormat=None): + profile = GUI.profiles[str(GUI.DeviceBox.currentText())] + if outputFormat is not None: + GUI.FormatBox.setCurrentIndex(outputFormat) else: - if not GUI.WebtoonBox.isChecked() and not GUI.ProcessingBox.isChecked() and not self.QualityBoxDisabled: - GUI.QualityBox.setEnabled(True) + if GUI.FormatBox.count() == 3: + GUI.FormatBox.setCurrentIndex(profile['DefaultFormat']) + else: + if profile['DefaultFormat'] != 0: + tmpFormat = profile['DefaultFormat'] - 1 + else: + tmpFormat = 0 + GUI.FormatBox.setCurrentIndex(tmpFormat) + if (str(GUI.FormatBox.currentText()) == 'CBZ' and not 'Kobo' in str(GUI.DeviceBox.currentText())) or \ + GUI.WebtoonBox.isChecked(): + GUI.MangaBox.setEnabled(False) + GUI.QualityBox.setEnabled(False) + GUI.MangaBox.setChecked(False) + GUI.QualityBox.setChecked(False) + else: + GUI.MangaBox.setEnabled(profile['MangaMode']) + if not profile['MangaMode']: + GUI.MangaBox.setChecked(False) + GUI.QualityBox.setEnabled(profile['Quality']) + if not profile['Quality']: + GUI.QualityBox.setChecked(False) + if GUI.ProcessingBox.isChecked(): + GUI.QualityBox.setEnabled(False) + GUI.QualityBox.setChecked(False) def stripTags(self, html): s = HTMLStripper() @@ -873,6 +915,11 @@ class KCCGUI(KCC_ui.Ui_KCC): self.addMessage('Target resolution is not set!', 'error') self.needClean = True return + if 'Kobo' in str(GUI.DeviceBox.currentText()) and GUI.QualityBox.checkState() == 2: + GUI.JobList.clear() + self.addMessage('Kobo devices can\'t use ultra quality mode!', 'error') + self.needClean = True + return self.worker.start() def hideProgressBar(self): @@ -964,7 +1011,6 @@ class KCCGUI(KCC_ui.Ui_KCC): self.progress = ProgressThread() self.conversionAlive = False self.needClean = True - self.QualityBoxDisabled = False self.GammaValue = 1.0 self.completedWork = {} if sys.platform.startswith('darwin'): @@ -982,10 +1028,68 @@ class KCCGUI(KCC_ui.Ui_KCC): self.statusBarStyle = 'QLabel{padding-top:3px;padding-bottom:3px;border-top:2px solid #C2C7CB}' self.tray.show() + self.profiles = { + "Kindle Paperwhite": {'MangaMode': True, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': False, 'Label': 'KHD'}, + "Kindle": {'MangaMode': True, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': False, 'Label': 'K345'}, + "Kindle DX/DXG": {'MangaMode': False, 'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': False, 'Label': 'KDX'}, + "Kindle Fire": {'MangaMode': True, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': False, 'Label': 'KF'}, + "K. Fire HD 7\"": {'MangaMode': True, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': True, 'Label': 'KFHD'}, + "K. Fire HD 8.9\"": {'MangaMode': True, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': True, 'Label': 'KFHD8'}, + "K. Fire HDX 7\"": {'MangaMode': True, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': True, 'Label': 'KFHDX'}, + "K. Fire HDX 8.9\"": {'MangaMode': True, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': True, 'Label': 'KFHDX8'}, + "Kobo Mini/Touch": {'MangaMode': False, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2, + 'DefaultUpscale': False, 'Label': 'KoMT'}, + "Kobo Glow": {'MangaMode': False, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2, + 'DefaultUpscale': False, 'Label': 'KoG'}, + "Kobo Aura": {'MangaMode': False, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2, + 'DefaultUpscale': False, 'Label': 'KoA'}, + "Kobo Aura HD": {'MangaMode': False, 'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2, + 'DefaultUpscale': False, 'Label': 'KoAHD'}, + "Other": {'MangaMode': False, 'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1, + 'DefaultUpscale': False, 'Label': 'OTHER'}, + "Kindle for Android": {'MangaMode': True, 'Quality': False, 'ForceExpert': True, 'DefaultFormat': 0, + 'DefaultUpscale': False, 'Label': 'KFA'}, + "Kindle 1": {'MangaMode': False, 'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': False, 'Label': 'K1'}, + "Kindle 2": {'MangaMode': False, 'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': False, 'Label': 'K2'} + } + profilesGUI = [ + "Kindle Paperwhite", + "Kindle", + "Kindle DX/DXG", + "Separator", + "Kindle Fire", + "K. Fire HD 7\"", + "K. Fire HD 8.9\"", + "K. Fire HDX 7\"", + "K. Fire HDX 8.9\"", + "Separator", + "Kobo Mini/Touch", + "Kobo Glow", + "Kobo Aura", + "Kobo Aura HD", + "Separator", + "Other", + "Separator", + "Kindle for Android", + "Kindle 1", + "Kindle 2", + ] + statusBarLabel = QtGui.QLabel('HOMEPAGE - DONATE' - ' - README<' - '/a> - WIKI') + ' - WIKI' + ' - FORUM' + '') statusBarLabel.setAlignment(QtCore.Qt.AlignCenter) statusBarLabel.setStyleSheet(self.statusBarStyle) statusBarLabel.setOpenExternalLinks(True) @@ -998,7 +1102,8 @@ class KCCGUI(KCC_ui.Ui_KCC): self.addMessage('Remember: All options have additional informations in tooltips.', 'info') if self.firstStart: self.addMessage('Since you are using KCC for first time please see few ' - 'important tips.', 'info') + 'important tips.', + 'info') kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) if kindleGenExitCode.wait() == 0: self.KindleGen = True @@ -1017,8 +1122,12 @@ class KCCGUI(KCC_ui.Ui_KCC): else: self.KindleGen = False formats = ['EPUB', 'CBZ'] - self.addMessage('Cannot find kindlegen in PATH! MOBI creation will be disabled.', 'warning') + if sys.platform.startswith('win'): + self.addMessage('Cannot find ' + 'kindlegen in KCC directory! MOBI creation will be disabled.', 'warning') + else: + self.addMessage('Cannot find ' + 'kindlegen in PATH! MOBI creation will be disabled.', 'warning') rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, shell=True) rarExitCode = rarExitCode.wait() if rarExitCode == 0 or rarExitCode == 7: @@ -1060,44 +1169,40 @@ class KCCGUI(KCC_ui.Ui_KCC): MW.connect(self.progress, QtCore.SIGNAL("addMessage"), self.addMessage) MW.closeEvent = self.saveSettings + for profile in profilesGUI: + if profile == "Other": + GUI.DeviceBox.addItem(self.icons.deviceOther, profile) + elif profile == "Separator": + GUI.DeviceBox.insertSeparator(GUI.DeviceBox.count()+1) + elif 'Ko' in profile: + GUI.DeviceBox.addItem(self.icons.deviceKobo, profile) + else: + GUI.DeviceBox.addItem(self.icons.deviceKindle, profile) for f in formats: GUI.FormatBox.addItem(eval('self.icons.' + f + 'Format'), f) + if self.lastDevice > GUI.DeviceBox.count(): + self.lastDevice = 0 + if profilesGUI[self.lastDevice] == "Separator": + self.lastDevice = 0 if self.currentFormat > GUI.FormatBox.count(): - GUI.FormatBox.setCurrentIndex(0) self.currentFormat = 0 - else: - GUI.FormatBox.setCurrentIndex(self.currentFormat) + GUI.DeviceBox.setCurrentIndex(self.lastDevice) + self.changeDevice() + if self.currentFormat != self.profiles[str(GUI.DeviceBox.currentText())]['DefaultFormat']: + self.changeFormat(self.currentFormat) for option in self.options: if str(option) == "customWidth": GUI.customWidth.setText(str(self.options[option])) elif str(option) == "customHeight": GUI.customHeight.setText(str(self.options[option])) elif str(option) == "GammaSlider": - GUI.GammaSlider.setValue(int(self.options[option])) - self.changeGamma(int(self.options[option])) + if GUI.GammaSlider.isEnabled(): + GUI.GammaSlider.setValue(int(self.options[option])) + self.changeGamma(int(self.options[option])) else: - eval('GUI.' + str(option)).setCheckState(self.options[option]) - for profile in ProfileData.ProfileLabelsGUI: - if profile == "Other": - GUI.DeviceBox.addItem(self.icons.deviceOther, profile) - elif profile == "Separator": - GUI.DeviceBox.insertSeparator(GUI.DeviceBox.count()+1) - else: - GUI.DeviceBox.addItem(self.icons.deviceKindle, profile) - if self.lastDevice > GUI.DeviceBox.count(): - GUI.DeviceBox.setCurrentIndex(0) - self.lastDevice = 0 - else: - GUI.DeviceBox.setCurrentIndex(self.lastDevice) + if eval('GUI.' + str(option)).isEnabled(): + eval('GUI.' + str(option)).setCheckState(self.options[option]) - if self.currentMode == 1: - self.modeBasic() - elif self.currentMode == 2: - self.modeAdvanced() - elif self.currentMode == 3: - self.modeExpert() - self.changeDevice(self.lastDevice) - self.changeFormat() self.versionCheck.start() self.contentServer.start() self.hideProgressBar() diff --git a/kcc/KCC_rc.py b/kcc/KCC_rc.py index f7307d2..084d287 100644 --- a/kcc/KCC_rc.py +++ b/kcc/KCC_rc.py @@ -8358,6 +8358,158 @@ qt_resource_data = b"\ \x5f\x00\xdc\x06\xf0\x91\x73\xee\x3b\x8e\x03\x82\x20\x08\x82\x20\ \x08\x82\x20\x08\x82\x20\x08\x62\x23\xf8\x1f\x8e\x96\x1e\x7d\x49\ \xfc\xce\x76\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x09\x54\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3\x3e\x61\xcb\ +\x00\x00\x09\x1b\x49\x44\x41\x54\x78\xda\xed\x9d\x7f\x8c\x14\x67\ +\x19\xc7\x3f\xef\xec\xec\x71\xcb\x1d\x72\x77\xd8\xbb\x63\x0f\x7a\ +\xb6\x0d\x28\x98\x6a\xea\x4a\x8c\xa5\x35\xa9\x56\x62\xa2\x89\x11\ +\x88\xd4\xc6\x98\x02\x96\x06\x42\x4d\xda\x44\x8b\xda\x54\x8d\x36\ +\xd5\xc4\x68\x62\xa9\x50\x51\x92\xca\x1f\x4d\x2d\xa8\x69\x54\x1a\ +\xff\xb0\x92\xa0\xa9\xe0\x24\x10\x8b\x28\x1c\xe5\xc7\x71\x2c\x5c\ +\xe1\xe0\x7e\x71\x77\xbb\x33\xf3\xfa\x47\xef\x70\x6f\xd9\x99\xdb\ +\xe3\x6e\x77\xe7\xc7\xf3\x4d\x36\x7b\xb7\xb7\xb7\x33\xfb\x3c\x9f\ +\xf7\x79\x9e\xf7\xc7\xcc\x0b\x22\x91\x48\x24\x12\x89\x44\x22\x91\ +\x48\x24\x12\x89\x44\x22\x91\x28\xfa\x52\x41\x38\x89\x4c\x26\xd3\ +\x01\xac\x02\x3a\x81\xf9\x31\xb2\xff\x75\xe0\x3c\xf0\x57\xcb\xb2\ +\x8e\xc7\x0e\x80\x4c\x26\xb3\x0c\xf8\x21\xf0\x39\xc0\x88\x79\x63\ +\x3c\x04\x7c\xc3\xb2\xac\x03\xd5\x3c\x68\xa2\x86\xce\x5f\x03\xfc\ +\x09\xb8\x3b\x28\x91\xa8\xc6\xea\x00\xbe\x92\x4e\xa7\x73\xd9\x6c\ +\xf6\x60\xa4\x01\xc8\x64\x32\x0f\x00\xbf\x07\xea\xc5\xef\x37\x45\ +\xe4\x07\xd3\xe9\xf4\xe5\x6c\x36\x7b\x38\x92\x29\x20\x93\xc9\xa4\ +\x80\x13\xc0\x22\xf1\xb7\xa7\xc6\x80\xe5\x96\x65\xbd\x5d\xe9\x03\ +\xd5\x22\xef\x3e\x2a\xce\x9f\x52\x73\x80\x6f\x56\xe3\x40\xb5\x00\ +\x60\xad\xf8\xb7\x2c\x7d\x21\x93\xc9\x24\xa2\x08\xc0\x3d\xe2\xdb\ +\xb2\xb4\x60\xbc\x5b\x1c\x1d\x00\x32\x99\x4c\x03\xd0\x28\xbe\x2d\ +\x5b\x6d\x51\x8b\x00\x49\xf1\x69\xb0\xec\x65\x88\x8d\xe3\x2d\x33\ +\x48\x27\x93\x4a\xa5\xe8\xec\x2c\x2f\xed\x29\xf5\x6e\x0f\xd6\x30\ +\x0c\x94\x52\x68\xad\x27\x3d\x82\xae\x91\x91\x11\xce\x9e\x3d\x2b\ +\x00\x14\x6a\xc9\x92\x25\xec\xdc\xb9\x33\x16\x2d\xef\xe8\xd1\xa3\ +\x6c\xde\xbc\x59\x00\x98\x89\x26\xa2\x40\xb1\xc2\x10\x01\x24\x05\ +\xcc\x82\xe3\x0b\x01\x98\x48\x03\x85\x3f\x0b\x08\x11\x04\x40\x29\ +\x35\x09\x80\x62\x08\x0a\x23\x80\x80\x10\x31\x00\x26\x1c\x5e\x0c\ +\x81\xd7\x7b\x0b\x1d\x2f\x10\x44\x2c\x02\x78\x39\xbe\x54\x6a\x70\ +\x5d\x57\x20\x08\x3b\x00\xa5\x5a\xbf\x57\x31\x58\x98\x02\x26\xba\ +\x8a\x5a\x6b\x5c\xd7\x15\x08\x8a\x64\x44\xcd\xf9\xa5\x5e\x9b\xf8\ +\x3f\xc3\x30\x6e\x8c\x1b\x88\x42\x12\x01\xa6\xeb\x7c\xbf\x82\x50\ +\xba\x8b\x21\x03\xa0\xd8\xf9\xb7\xd2\x72\x6f\x14\x83\xae\x83\x32\ +\x12\x32\x6e\x10\x16\x00\xa6\x72\xfe\x74\x60\x50\x4a\xa1\xc7\x9d\ +\x3f\xf1\x59\xa5\x86\x8d\xe3\x08\x82\x19\x34\xa7\xfb\xf5\xfb\x8b\ +\x5b\xed\xb4\x21\x28\x70\xf0\x74\x41\x88\xea\xa8\x63\xa0\x00\x30\ +\x0c\xa3\xac\xae\xde\xad\x16\x71\xc5\x10\x14\x7e\x56\x21\x0c\x85\ +\xf5\xc3\x74\x8e\x15\x46\x18\x02\x1d\x01\xbc\x46\xfa\x66\x7a\x0c\ +\x2f\x08\x8a\x8b\x47\xaf\x88\x54\x1c\x49\xc2\x9c\x46\x02\x19\x01\ +\x26\x0c\xfb\xef\x1e\x87\xbd\x87\xf2\x8c\xd9\x35\x34\xaa\xc6\x7b\ +\xed\xb4\x9e\x78\xd2\x93\x5e\x9b\xf4\x3b\x30\xc7\x54\xac\x59\x61\ +\xb2\x2c\x6d\x08\x00\x7e\x2d\xb3\x30\x05\x1c\xef\x71\xf8\xea\xaf\ +\x46\x22\x53\x6c\xfd\xf9\x2d\x87\x17\xd7\xcf\x09\x1c\x04\x81\x43\ +\x72\x22\xec\xbe\x7a\x28\x1f\xb9\x8a\x7b\xdf\x61\x3b\x70\xe7\x14\ +\x18\x00\x8a\x73\x67\x4d\xc3\x7e\x85\x14\xc4\xef\x14\xa8\x08\x20\ +\x63\xf5\x31\x2f\x02\x67\x6b\xd6\xce\x9d\xe2\xdf\x0d\x99\x0a\x08\ +\x36\x00\x33\x91\xe3\xc2\x86\x95\x39\x96\xb6\xe9\x92\x8e\x3f\x91\ +\x75\xd8\x75\x30\x89\x69\xca\x0a\xf5\xc0\x01\x30\x1b\xe1\xdf\xd5\ +\x70\x7b\xb3\xcb\xf2\x76\xb7\x24\x00\xa3\xa3\x36\xb9\xbe\x33\x98\ +\xb7\x2d\x05\x65\xc6\x1e\x00\x23\x88\x00\x54\xb6\x0e\x70\x41\xbb\ +\xe4\xfa\xba\xfe\xdf\x91\x17\x00\x02\x28\x5d\x31\xca\xc6\x9f\x1d\ +\x72\x57\xfe\x0b\x6e\x5e\x00\x88\x95\x0a\xeb\x0c\xed\x90\xbb\xda\ +\x05\xda\x11\x00\x82\x17\x00\x2a\x14\x02\x8a\x7b\x00\xda\x25\xd7\ +\x77\x32\xb6\xe9\x40\xae\x0d\xbc\x91\x0e\xfe\x53\xf1\x74\xa0\x02\ +\x78\x2b\x24\x01\xa0\x30\x12\xc4\x30\x1d\x08\x00\x31\x4f\x07\x02\ +\x40\x8d\xd2\x81\x00\x10\x8a\x74\xe0\xa2\xc7\xe3\x81\xd7\x23\xec\ +\x92\xa1\x30\x0f\xe5\xf3\x0e\x1f\x6d\xee\xe6\xae\xce\x85\xe0\xb3\ +\x34\xed\xd2\x35\x9b\x83\x6f\xd7\x13\xd6\x4b\x0d\x04\x80\x52\x01\ +\x40\xc3\xa7\x3e\xd4\xc8\xf7\x1f\x6e\x63\x2c\xef\x3d\x87\xdf\xd3\ +\x67\xb3\x71\x7b\x37\x24\xe7\x61\xbe\xe7\x76\x01\x20\x2a\xce\x5f\ +\xfc\xde\x3a\xbe\xb5\xb6\x95\xa1\x51\xef\xc9\x29\x17\xd8\xfa\x8b\ +\x9e\x77\x67\x1e\x73\x83\xe4\xfb\xcf\x92\x9c\xdf\x19\xba\xef\x2b\ +\x35\x40\x91\x16\x36\x27\x79\x61\x53\x1a\xc7\x67\x62\xd2\x76\x34\ +\x1b\x7e\xd6\x3d\x09\x10\x9d\x1f\x24\x7f\xcd\xff\xc6\x9e\x1a\x59\ +\x10\x12\x68\x35\x37\x9a\x6c\xdf\x94\xc6\x4c\x78\x27\x74\x57\xc3\ +\xb6\x3d\x59\x7a\xfb\xed\xa2\xbc\xaf\xd0\xf6\x08\xf6\xc0\x39\x89\ +\x00\x61\x94\x52\xf0\x93\xf5\x0b\x49\xd5\x79\x9b\xc4\x50\xf0\xec\ +\xde\x5e\x8e\x9f\x1f\xf3\x2c\xfa\xdc\xdc\x20\xf9\xfe\x33\x02\x40\ +\x98\x94\x30\x14\x2f\x6e\x5e\x44\x7b\x93\xe9\xeb\xfc\xe7\xf6\xf5\ +\x72\xf0\xf8\xd0\xd4\x75\x44\x7e\x88\xfc\xb5\xd3\x02\x40\x18\xe4\ +\xb8\xf0\xd4\xea\xdb\xe8\x68\x49\x7a\x2e\x25\xab\x33\x15\xfb\xde\ +\xec\xe7\xc0\xb1\x61\xcc\xb2\xd6\x93\x29\xb4\x7d\x1d\x7b\xa0\x5b\ +\x00\x08\xba\x9e\x5e\xdb\xca\x7d\xcb\x1a\x7c\xdf\xf3\xbb\x7f\x0c\ +\xb0\xf3\xf5\x2b\xd3\xee\xeb\xbb\xb9\x81\xc0\xa7\x83\xd8\x02\x90\ +\xb3\x35\x1b\x1f\x6c\xe1\x13\x1f\x6c\xc0\x6b\x01\x52\xc2\x50\x1c\ +\x3a\x79\x9d\x9f\xef\xbf\xec\x5b\x18\x86\x39\x1d\xc4\x12\x80\xbc\ +\xa3\xf9\x6c\x66\x1e\x5f\xbc\xb7\xc9\x33\xec\x1b\x0a\xba\xb2\x63\ +\x3c\xf3\xf2\xc5\x99\x96\x97\x81\x4e\x07\xf1\x03\x40\xc3\xfd\xcb\ +\x1b\x79\x6a\x75\x2b\xb6\xcf\xfa\xf1\x2b\x83\x0e\x8f\xed\x38\x4f\ +\x62\x96\xd6\x90\xbb\xb9\x01\xec\xc1\xf3\x02\x40\x8d\x7d\x4f\x6b\ +\x93\xc9\xb7\xd7\xb6\x32\x96\xf7\x76\xbe\xd6\xb0\x75\x57\x0f\x49\ +\x33\xfa\x17\x10\xc4\x6a\x28\x58\x6b\xe8\x58\x90\xc4\x6f\xd1\x71\ +\xde\xd6\xac\xdf\x7e\x9e\xab\x43\x0e\x71\xb8\x97\x54\xec\x52\x80\ +\x9f\xf3\xeb\xeb\x14\x4f\xbf\x7c\x89\xab\x43\x36\x71\xb9\x91\x58\ +\xec\x00\xf0\x73\xec\x58\x5e\xb3\xe5\x33\x2d\xbe\xb5\x81\x00\x10\ +\x72\xe7\x5f\xee\xb7\x71\x3c\x1c\xac\x35\xdc\xd1\x5a\xc7\x8e\xc7\ +\x16\x49\x04\x88\x24\x00\x40\xf7\xe5\x3c\x3f\x7d\xed\x1d\xea\x3d\ +\xc6\xfc\x5d\x0d\x77\xb6\xd5\xf1\xdd\x75\xed\x8c\xe4\x5c\x01\xa0\ +\x76\xce\x52\x15\xa3\xe0\x0f\xff\x1c\xe4\xf9\x3f\xbe\xe3\x3b\xb8\ +\x73\xef\x07\xe6\xf2\xbd\x87\xda\x7d\xa7\x85\x05\x80\x90\x6a\x4e\ +\x52\xf1\x9b\xbf\xf5\xf3\xda\xa1\x01\xcf\x4b\xc5\x73\xb6\xe6\x81\ +\xbb\x1b\x79\xe4\x93\xcd\x44\xb9\x24\x88\xed\x50\x70\x9d\xa9\x78\ +\x61\xff\x65\xde\xf8\xd7\x90\xe7\x60\x8f\xed\x68\xbe\x74\x7f\x13\ +\x0f\xdd\xd7\xe4\x59\x37\x08\x00\x95\x4c\xd8\x55\xd0\x0f\xf6\xf6\ +\xf2\xc6\x5b\x43\x9e\x45\x9f\xd6\xb0\xe9\xd3\x2d\xac\xfd\x78\x13\ +\x51\xbc\x79\x49\xa0\x00\x98\x6a\x13\x88\x4a\x45\x82\x1f\xfd\xb6\ +\x97\x33\xbd\x39\xcf\x74\x30\x66\x6b\x1e\x5d\xd5\xc2\x47\xee\x4a\ +\xe1\xba\x02\x40\xc5\x54\xcb\xfb\x03\x3d\xbe\xeb\x02\xa7\x7b\x73\ +\x3e\xe7\x06\xcf\x3e\xdc\xce\xc7\x96\xce\x15\x00\x2a\x1d\x01\x6a\ +\x21\xc7\xd5\x3c\xb9\x3b\xcb\x85\x3e\xef\x2b\x82\x34\xf0\xcc\xba\ +\x56\x96\x2f\xae\x8f\x4c\x3a\x90\x08\x50\xa0\xd1\xbc\xcb\x13\xbb\ +\x2f\x70\x7d\xcc\xf5\x2c\x41\x0c\xa5\x78\xee\xcb\xed\xb4\x35\x99\ +\x91\x80\x20\x90\x45\x60\x2d\x41\x18\x1c\x71\x59\xff\x7c\x37\xc3\ +\x63\xde\xc9\xde\x4c\x28\x76\x6d\x59\x44\x5b\x93\x29\x00\x54\x8e\ +\x82\x5a\xa5\x21\x18\x1a\x75\xd9\xb6\x27\xeb\xdb\xc2\xcd\x84\xe2\ +\xc7\x8f\xa4\x69\x98\x63\x84\xfa\x1a\x41\x59\x15\xec\xa1\xae\x8b\ +\x39\xb6\xee\xea\xf1\xed\xff\x2f\x98\x97\x60\xf7\xe3\x8b\x99\x5b\ +\x67\x08\x00\xb3\x1f\x00\x6e\xb5\x5d\x29\x52\x49\x97\xc6\x7a\xe3\ +\xe6\x47\x2a\xe1\xbb\xee\xbf\x78\x18\xe2\xf4\xa5\x1c\xdb\xf6\x5c\ +\x64\x5e\xca\x28\xfd\x79\xf5\x06\x0b\x9b\x4d\x5e\xfa\xda\x62\x92\ +\x65\xac\x19\x0c\xe2\xfc\x52\xe4\x16\x84\x24\x13\x9a\x27\x5e\x49\ +\x32\xd2\x77\x0a\xed\xe4\x6e\x32\xbb\x99\xa0\x7c\x08\x14\x1c\x3b\ +\x37\xca\x3d\x4f\x9e\x9c\xf2\x7d\x8d\xf5\xe1\x8c\x02\x91\x5c\x11\ +\x94\xaa\x4f\x92\x4a\xbf\x9f\x7c\xdf\x09\xb4\x3b\xb3\x3b\x74\x2b\ +\x05\xf3\x52\xd1\xcd\x94\x11\xae\x01\x14\xc9\xe6\x25\xa8\x84\xdc\ +\x12\x36\xbe\x45\xa0\x32\x48\xce\xbf\x13\x65\xc8\x55\xf0\xa1\x00\ +\xa0\x70\x24\xb0\x3e\x39\x4b\x25\x93\x61\x92\x6c\x59\x1a\x08\x08\ +\x82\xb8\xca\x38\xb0\x00\xac\x59\x31\x9b\x0e\x1b\x4f\x07\x46\x6d\ +\xd3\xc1\xea\x95\x6d\x52\x04\x96\x03\x80\x52\x8a\xe5\x1d\x26\xbf\ +\xdc\x68\xb0\xf7\x70\x9e\xb1\xd9\xda\x69\xc5\x5d\x8a\x3d\x7c\xb1\ +\x3a\x5f\xa6\x60\x14\x29\x69\x2a\x56\xaf\x6c\xe3\xc3\x77\x34\x04\ +\x6e\x43\x8c\x40\x6f\x1b\xb7\xac\x23\xc1\x77\x16\x27\x3d\xff\x7e\ +\x6b\x9a\x3f\x73\xc7\x4e\x75\x1e\x5a\xa3\xb5\x03\x2a\x31\x69\x63\ +\x4a\x37\x80\x73\xc9\x32\x12\x78\x2b\xfd\x42\x5f\xdf\x6b\xb4\x76\ +\x27\x45\x80\xe2\x9d\x49\x05\x00\x1f\xe3\xf9\xbd\x16\xf4\xfd\x84\ +\x26\x1c\x9d\xb7\x1d\x30\xcc\x40\x3b\x3e\xd0\x00\xf8\x19\xae\x5a\ +\x06\x2d\xde\xbc\xc2\xef\xb9\xf0\x7c\x95\x52\x98\xa6\xe9\xf9\x77\ +\x29\x02\xa7\x11\x01\x8a\xb7\x68\xf5\x7a\xbd\x78\x9b\xd7\x89\xdf\ +\x67\x6a\xf4\x72\x21\x98\x4e\x44\x13\x00\xca\x34\x58\xb9\x3b\x78\ +\x7b\x39\xa3\x56\xc6\x2f\x6c\xed\x41\x6f\xfd\x81\x03\x60\x3a\x2d\ +\x48\x05\xe8\xda\x2d\x5d\xa2\xe0\xab\xce\xfe\x47\x11\x07\xa0\x94\ +\x71\x55\xc0\x2e\xda\x2b\x15\xb5\xc2\xe2\xfc\x50\x00\xe0\x05\x41\ +\xd0\x8c\x1b\xd6\x2d\xe4\x43\x33\x4b\xe2\x65\xe0\xa0\x44\x84\xb0\ +\x6e\x79\x1b\xfa\x69\x32\xd9\x6b\x38\x42\x00\x0c\x0f\x0f\x73\xe4\ +\xc8\x91\x58\x18\xbe\xab\xab\x4b\x00\x28\xd6\xa9\x53\xa7\xd8\xb2\ +\x65\x8b\x34\xcb\x2a\x4a\xe6\x02\x04\x00\x91\x00\x20\x12\x00\x44\ +\x02\x80\x48\x00\x10\x09\x00\x22\x01\xa0\x82\x92\x61\xbb\x80\xd9\ +\xab\xda\x00\x0c\x02\xb6\xf8\xb5\x6c\x5d\x8b\x14\x00\x96\x65\xb9\ +\xc0\x39\xf1\x6b\x59\x72\xaa\x61\xab\x5a\xd4\x00\xfb\xc5\xb7\x65\ +\xe9\xef\x96\x65\xf5\x47\x11\x80\x1d\xe3\x74\x8b\xfc\xb5\x3d\x92\ +\xbd\x00\xcb\xb2\x8e\x55\xeb\xcb\x85\x58\x7f\x01\x5e\x8d\x72\x37\ +\xf0\xeb\xc0\xeb\xe2\xe7\x92\x3a\x0e\xac\xb3\x2c\xab\x2a\x3d\xa6\ +\x44\x2d\xbe\x61\x36\x9b\x75\xd3\xe9\xf4\x2b\x40\x03\xb0\xa2\x56\ +\xe7\x11\xc0\x2e\xdf\x5e\xe0\xf3\x96\x65\x5d\xad\xd6\x41\x6b\xbe\ +\x9e\x2a\x93\xc9\x2c\x01\x36\x00\xab\x80\xf7\x01\x4d\x31\x72\xfa\ +\x30\xd0\x0d\x1c\x00\x7e\x6d\x59\xd6\x9b\xd2\x0e\x44\x22\x91\x48\ +\x24\x12\x89\x44\x22\x91\x48\x24\x12\x89\x44\x15\xd1\xff\x00\x56\ +\x1c\x01\xcd\xc9\x01\xf3\xd5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ " qt_resource_name = b"\ @@ -8442,33 +8594,38 @@ qt_resource_name = b"\ \x0e\xc5\xfa\x07\ \x00\x4f\ \x00\x74\x00\x68\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x08\ +\x05\x92\x5d\x07\ +\x00\x4b\ +\x00\x6f\x00\x62\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\ -\x00\x00\x00\x28\x00\x02\x00\x00\x00\x01\x00\x00\x00\x17\ -\x00\x00\x00\x36\x00\x02\x00\x00\x00\x01\x00\x00\x00\x11\ -\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0d\ +\x00\x00\x00\x28\x00\x02\x00\x00\x00\x01\x00\x00\x00\x18\ +\x00\x00\x00\x36\x00\x02\x00\x00\x00\x01\x00\x00\x00\x12\ +\x00\x00\x00\x46\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0e\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0a\ \x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x06\ \x00\x00\x00\x58\x00\x02\x00\x00\x00\x03\x00\x00\x00\x07\ \x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x01\xad\xa7\ \x00\x00\x01\x7e\x00\x00\x00\x00\x00\x01\x00\x01\x91\x58\ \x00\x00\x01\xaa\x00\x00\x00\x00\x00\x01\x00\x01\xcc\xe3\ -\x00\x00\x00\x58\x00\x02\x00\x00\x00\x02\x00\x00\x00\x0b\ +\x00\x00\x00\x58\x00\x02\x00\x00\x00\x03\x00\x00\x00\x0b\ +\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x01\x00\x02\x07\xc9\ \x00\x00\x01\xbe\x00\x00\x00\x00\x00\x01\x00\x01\xf6\xde\ \x00\x00\x01\xd8\x00\x00\x00\x00\x00\x01\x00\x02\x01\xe5\ -\x00\x00\x00\x58\x00\x02\x00\x00\x00\x03\x00\x00\x00\x0e\ +\x00\x00\x00\x58\x00\x02\x00\x00\x00\x03\x00\x00\x00\x0f\ \x00\x00\x00\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x09\x5d\ \x00\x00\x00\x68\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x9a\ -\x00\x00\x00\x58\x00\x02\x00\x00\x00\x05\x00\x00\x00\x12\ +\x00\x00\x00\x58\x00\x02\x00\x00\x00\x05\x00\x00\x00\x13\ \x00\x00\x01\x38\x00\x00\x00\x00\x00\x01\x00\x00\x6a\x71\ \x00\x00\x01\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x46\x38\ \x00\x00\x00\xce\x00\x00\x00\x00\x00\x01\x00\x00\x37\xa3\ \x00\x00\x00\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x3a\ \x00\x00\x00\xe6\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x02\ -\x00\x00\x00\x58\x00\x02\x00\x00\x00\x01\x00\x00\x00\x18\ +\x00\x00\x00\x58\x00\x02\x00\x00\x00\x01\x00\x00\x00\x19\ \x00\x00\x01\x5a\x00\x00\x00\x00\x00\x01\x00\x00\x73\xc8\ " diff --git a/kcc/KCC_ui.py b/kcc/KCC_ui.py index b530932..f3d2e1b 100644 --- a/kcc/KCC_ui.py +++ b/kcc/KCC_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'KCC.ui' # -# Created: Fri Dec 13 19:22:05 2013 +# Created: Tue Jan 14 15:50:02 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -298,7 +298,7 @@ class Ui_KCC(object): "p, li { white-space: pre-wrap; }\n" "\n" "

Unchecked - Normal quality mode
Use it when Panel View support is not needed.
- Maximum quality when zoom is not enabled.
- Poor quality when zoom is enabled.
- Lowest file size.

\n" -"

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

\n" +"

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
Smaller images might be forcefully upscaled in this mode.

- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

\n" "

Checked - Ultra quality mode
Maximum possible quality.
- Maximum quality when zoom is not enabled.
- Maximum quality when zoom is enabled.
- Very high file size.

", None)) self.QualityBox.setText(_translate("KCC", "High/Ultra quality", None)) self.RotateBox.setToolTip(_translate("KCC", "

Disable splitting of two-page spreads.
They will be rotated instead.

", None)) diff --git a/kcc/KCC_ui_linux.py b/kcc/KCC_ui_linux.py index 711556b..a45005d 100644 --- a/kcc/KCC_ui_linux.py +++ b/kcc/KCC_ui_linux.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'KCC-Linux.ui' # -# Created: Fri Dec 13 19:22:17 2013 +# Created: Tue Jan 14 15:50:14 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -367,7 +367,7 @@ class Ui_KCC(object): "p, li { white-space: pre-wrap; }\n" "\n" "

Unchecked - Normal quality mode
Use it when Panel View support is not needed.
- Maximum quality when zoom is not enabled.
- Poor quality when zoom is enabled.
- Lowest file size.

\n" -"

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

\n" +"

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
Smaller images might be forcefully upscaled in this mode.
- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

\n" "

Checked - Ultra quality mode
Maximum possible quality.
- Maximum quality when zoom is not enabled.
- Maximum quality when zoom is enabled.
- Very high file size.

", None)) self.QualityBox.setText(_translate("KCC", "High/Ultra quality", None)) self.RotateBox.setToolTip(_translate("KCC", "

Disable splitting of two-page spreads.
They will be rotated instead.

", None)) diff --git a/kcc/KCC_ui_osx.py b/kcc/KCC_ui_osx.py index 23b3321..e993b68 100644 --- a/kcc/KCC_ui_osx.py +++ b/kcc/KCC_ui_osx.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'KCC-OSX.ui' # -# Created: Fri Dec 13 19:22:27 2013 +# Created: Tue Jan 14 15:50:25 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -386,7 +386,7 @@ class Ui_KCC(object): self.ClearButton.setText(_translate("KCC", "Clear list", None)) self.MangaBox.setToolTip(_translate("KCC", "

Enable right-to-left reading.

", None)) self.MangaBox.setText(_translate("KCC", "Manga mode", None)) - self.QualityBox.setToolTip(_translate("KCC", "

Unchecked - Normal quality mode
Use it when Panel View support is not needed.
- Maximum quality when zoom is not enabled.
- Poor quality when zoom is enabled.
- Lowest file size.

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

Checked - Ultra quality mode
Maximum possible quality.
- Maximum quality when zoom is not enabled.
- Maximum quality when zoom is enabled.
- Very high file size.

", None)) + self.QualityBox.setToolTip(_translate("KCC", "

Unchecked - Normal quality mode
Use it when Panel View support is not needed.
- Maximum quality when zoom is not enabled.
- Poor quality when zoom is enabled.
- Lowest file size.

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
Smaller images might be forcefully upscaled in this mode.

- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

Checked - Ultra quality mode
Maximum possible quality.
- Maximum quality when zoom is not enabled.
- Maximum quality when zoom is enabled.
- Very high file size.

", None)) self.QualityBox.setText(_translate("KCC", "High/Ultra quality", None)) self.RotateBox.setToolTip(_translate("KCC", "

Disable splitting of two-page spreads.
They will be rotated instead.

", None)) self.RotateBox.setText(_translate("KCC", "Horizontal mode", None)) diff --git a/kcc/cbxarchive.py b/kcc/cbxarchive.py index 17a612c..459a4c1 100644 --- a/kcc/cbxarchive.py +++ b/kcc/cbxarchive.py @@ -26,7 +26,7 @@ import locale from sys import platform from subprocess import STDOUT, PIPE try: - #noinspection PyUnresolvedReferences + # noinspection PyUnresolvedReferences from psutil import Popen except ImportError: print("ERROR: Psutil is not installed!") diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py index 364e1f9..38bc07c 100755 --- a/kcc/comic2ebook.py +++ b/kcc/comic2ebook.py @@ -29,13 +29,35 @@ import re import stat import string import unicodedata +import zipfile from tempfile import mkdtemp -from shutil import move, copyfile, copytree, rmtree, make_archive +from shutil import move, copyfile, copytree, rmtree from optparse import OptionParser, OptionGroup from multiprocessing import Pool, freeze_support from xml.dom.minidom import parse from uuid import uuid4 from slugify import slugify +try: + # noinspection PyUnresolvedReferences + from PIL import Image + if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))): + print "ERROR: Pillow 2.3.0 or newer is required!" + if sys.platform.startswith('linux'): + import Tkinter + import tkMessageBox + importRoot = Tkinter.Tk() + importRoot.withdraw() + tkMessageBox.showerror("KCC - Error", "Pillow 2.3.0 or newer is required!") + exit(1) +except ImportError: + print "ERROR: Pillow is not installed!" + if sys.platform.startswith('linux'): + import Tkinter + import tkMessageBox + importRoot = Tkinter.Tk() + importRoot.withdraw() + tkMessageBox.showerror("KCC - Error", "Pillow 2.3.0 or newer is required!") + exit(1) try: from PyQt4 import QtCore except ImportError: @@ -53,6 +75,10 @@ def buildHTML(path, imgfile): rotatedPage = True else: rotatedPage = False + if "_kccnpv" in str(filename): + noPV = True + else: + noPV = False if "_kccnh" in str(filename): noHorizontalPV = True else: @@ -90,7 +116,7 @@ def buildHTML(path, imgfile): "
\"",
\n" ]) - if options.panelview: + if options.panelview and not noPV: if not noHorizontalPV and not noVerticalPV: if rotatedPage: if options.righttoleft: @@ -234,7 +260,7 @@ def buildOPF(dstdir, title, filelist, cover=None): "en-US\n", "", options.uuid, "\n"]) for author in options.authors: - f.writelines(["", author, "\n"]) + f.writelines(["", author.encode('utf-8'), "\n"]) f.writelines(["\n", "\n", "\n", @@ -281,9 +307,6 @@ def buildOPF(dstdir, title, filelist, cover=None): f.write("\n\n\n\n") f.close() os.mkdir(os.path.join(dstdir, 'META-INF')) - f = open(os.path.join(dstdir, 'mimetype'), 'w') - f.write('application/epub+zip') - f.close() f = open(os.path.join(dstdir, 'META-INF', 'container.xml'), 'w') f.writelines(["\n", "\n", @@ -686,9 +709,6 @@ def sanitizeTreeBeforeConversion(filetree): for root, dirs, files in os.walk(filetree, False): for name in files: os.chmod(os.path.join(root, name), stat.S_IWRITE | stat.S_IREAD) - # Detect corrupted files - Phase 1 - if os.path.getsize(os.path.join(root, name)) == 0: - os.remove(os.path.join(root, name)) for name in dirs: os.chmod(os.path.join(root, name), stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC) @@ -844,6 +864,40 @@ def preSplitDirectory(path): return [path] +def detectCorruption(tmpPath, orgPath): + for root, dirs, files in os.walk(tmpPath, False): + for name in files: + if getImageFileName(name) is not None: + path = os.path.join(root, name) + pathOrg = os.path.join(orgPath, name) + if os.path.getsize(path) == 0: + rmtree(os.path.join(tmpPath, '..', '..'), True) + raise RuntimeError('Image file %s is corrupted.' % pathOrg) + try: + img = Image.open(path) + img.verify() + img = Image.open(path) + img.load() + except: + rmtree(os.path.join(tmpPath, '..', '..'), True) + raise RuntimeError('Image file %s is corrupted.' % pathOrg) + + +def makeZIP(zipFilename, baseDir, isEPUB=False): + zipFilename = os.path.abspath(zipFilename) + '.zip' + zipOutput = zipfile.ZipFile(zipFilename, 'w', zipfile.ZIP_DEFLATED) + if isEPUB: + zipOutput.writestr('mimetype', 'application/epub+zip', zipfile.ZIP_STORED) + for dirpath, dirnames, filenames in os.walk(baseDir): + for name in filenames: + path = os.path.normpath(os.path.join(dirpath, name)) + aPath = os.path.normpath(os.path.join(dirpath.replace(baseDir, ''), name)) + if os.path.isfile(path): + zipOutput.write(path, aPath) + zipOutput.close() + return zipFilename + + def Copyright(): print(('comic2ebook v%(__version__)s. ' 'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) @@ -864,7 +918,7 @@ def main(argv=None, qtGUI=None): otherOptions = OptionGroup(parser, "OTHER") mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KHD", help="Device profile (Choose one among K1, K2, K345, KDX, KHD, KF, KFHD, KFHD8, KFHDX," - " KFHDX8, KFA) [Default=KHD]") + " KFHDX8, KFA, KoMT, KoG, KoA, KoAHD) [Default=KHD]") mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0", help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]") mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, @@ -925,14 +979,13 @@ def main(argv=None, qtGUI=None): parser.print_help() return path = getWorkFolder(args[0]) + detectCorruption(path + "/OEBPS/Images/", args[0]) checkComicInfo(path + "/OEBPS/Images/", args[0]) if options.webtoon: - if GUI: - GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Splitting images') if options.customheight > 0: - comic2panel.main(['-y ' + str(options.customheight), '-i', path], qtGUI) + comic2panel.main(['-y ' + str(options.customheight), '-i', '-m', path], qtGUI) else: - comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', path], qtGUI) + comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', '-m', path], qtGUI) if options.imgproc: print("\nProcessing images...") if GUI: @@ -966,7 +1019,7 @@ def main(argv=None, qtGUI=None): filepath.append(getOutputFilename(args[0], options.output, '.cbz', ' ' + str(tomeNumber))) else: filepath.append(getOutputFilename(args[0], options.output, '.cbz', '')) - make_archive(tome + '_comic', 'zip', tome + '/OEBPS/Images') + makeZIP(tome + '_comic', tome + '/OEBPS/Images') else: print("\nCreating EPUB structure...") genEpubStruct(tome) @@ -975,7 +1028,7 @@ def main(argv=None, qtGUI=None): filepath.append(getOutputFilename(args[0], options.output, '.epub', ' ' + str(tomeNumber))) else: filepath.append(getOutputFilename(args[0], options.output, '.epub', '')) - make_archive(tome + '_comic', 'zip', tome) + makeZIP(tome + '_comic', tome, True) move(tome + '_comic.zip', filepath[-1]) rmtree(tome, True) if GUI: @@ -1033,6 +1086,11 @@ def checkOptions(): if options.profile == 'OTHER': options.panelview = False options.quality = 0 + if 'Ko' in options.profile: + options.panelview = False + # Kobo models can't use ultra quality mode + if options.quality == 2: + options.quality = 1 # Kindle for Android profile require target resolution. if options.profile == 'KFA' and (options.customwidth == 0 or options.customheight == 0): print("ERROR: Kindle for Android profile require --customwidth and --customheight options!") diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py index 787eec8..592db77 100644 --- a/kcc/comic2panel.py +++ b/kcc/comic2panel.py @@ -29,29 +29,25 @@ from shutil import rmtree, copytree, move from optparse import OptionParser, OptionGroup from multiprocessing import Pool, freeze_support try: - #noinspection PyUnresolvedReferences + # noinspection PyUnresolvedReferences from PIL import Image, ImageStat - if tuple(map(int, ('2.2.1'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))): - print("ERROR: Pillow 2.2.1 or newer is required!") + if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))): + print("ERROR: Pillow 2.3.0 or newer is required!") if sys.platform.startswith('linux'): - #noinspection PyUnresolvedReferences import tkinter - #noinspection PyUnresolvedReferences import tkinter.messagebox importRoot = tkinter.Tk() importRoot.withdraw() - tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!") + tkinter.messagebox.showerror("KCC - Error", "Pillow 2.3.0 or newer is required!") exit(1) except ImportError: print("ERROR: Pillow is not installed!") if sys.platform.startswith('linux'): - #noinspection PyUnresolvedReferences import tkinter - #noinspection PyUnresolvedReferences import tkinter.messagebox importRoot = tkinter.Tk() importRoot.withdraw() - tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!") + tkinter.messagebox.showerror("KCC - Error", "Pillow 2.3.0 or newer is required!") exit(1) try: from PyQt4 import QtCore @@ -73,6 +69,59 @@ def getImageFileName(imgfile): return filename +def walkLevel(some_dir, level=1): + some_dir = some_dir.rstrip(os.path.sep) + assert os.path.isdir(some_dir) + num_sep = some_dir.count(os.path.sep) + for root, dirs, files in os.walk(some_dir): + yield root, dirs, files + num_sep_this = root.count(os.path.sep) + if num_sep + level <= num_sep_this: + del dirs[:] + + +def mergeDirectory_tick(output): + if output: + mergeWorkerOutput.append(output) + mergeWorkerPool.terminate() + if GUI: + GUI.emit(QtCore.SIGNAL("progressBarTick")) + if not GUI.conversionAlive: + mergeWorkerPool.terminate() + + +def mergeDirectory(work): + try: + directory = work[0] + images = [] + imagesClear = [] + sizes = [] + for root, dirs, files in walkLevel(directory, 0): + for name in files: + if getImageFileName(name) is not None: + images.append([Image.open(os.path.join(root, name)), os.path.join(root, name)]) + if len(images) > 0: + for i in images: + sizes.append(i[0].size[0]) + mw = max(set(sizes), key=sizes.count) + for i in images: + if i[0].size[0] == mw: + i[0] = i[0].convert('RGB') + imagesClear.append(i) + h = sum(i[0].size[1] for i in imagesClear) + result = Image.new('RGB', (mw, h)) + y = 0 + for i in imagesClear: + result.paste(i[0], (0, y)) + y += i[0].size[1] + for i in imagesClear: + os.remove(i[1]) + savePath = os.path.split(imagesClear[0][1]) + result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG') + except StandardError: + return str(sys.exc_info()[1]) + + def sanitizePanelSize(panel, opt): newPanels = [] if panel[2] > 8 * opt.height: @@ -121,21 +170,6 @@ def splitImage(work): print(".", end=' ') fileExpanded = os.path.splitext(name) filePath = os.path.join(path, name) - # Detect corrupted files - try: - Image.open(filePath) - except IOError: - raise RuntimeError('Cannot read image file %s' % filePath) - try: - image = Image.open(filePath) - image.verify() - except: - raise RuntimeError('Image file %s is corrupted' % filePath) - try: - image = Image.open(filePath) - image.load() - except: - raise RuntimeError('Image file %s is corrupted' % filePath) image = Image.open(filePath) image = image.convert('RGB') widthImg, heightImg = image.size @@ -220,7 +254,7 @@ def Copyright(): def main(argv=None, qtGUI=None): - global options, GUI, splitWorkerPool, splitWorkerOutput + global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput parser = OptionParser(usage="Usage: %prog [options] comic_folder", add_help_option=False) mainOptions = OptionGroup(parser, "MANDATORY") otherOptions = OptionGroup(parser, "OTHER") @@ -228,6 +262,8 @@ def main(argv=None, qtGUI=None): help="Height of the target device screen") mainOptions.add_option("-i", "--in-place", action="store_true", dest="inPlace", default=False, help="Overwrite source directory") + mainOptions.add_option("-m", "--merge", action="store_true", dest="merge", default=False, + help="Combine every directory into a single image before splitting") otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False, help="Create debug file for every splitted image") otherOptions.add_option("-h", "--help", action="help", @@ -253,6 +289,29 @@ def main(argv=None, qtGUI=None): pagenumber = 0 splitWorkerOutput = [] splitWorkerPool = Pool() + if options.merge: + directoryNumer = 1 + mergeWork = [] + mergeWorkerOutput = [] + mergeWorkerPool = Pool() + mergeWork.append([options.targetDir]) + for root, dirs, files in os.walk(options.targetDir, False): + for directory in dirs: + directoryNumer += 1 + mergeWork.append([os.path.join(root, directory)]) + if GUI: + GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Combining images') + GUI.emit(QtCore.SIGNAL("progressBarTick"), directoryNumer) + for i in mergeWork: + mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectory_tick) + mergeWorkerPool.close() + mergeWorkerPool.join() + if GUI and not GUI.conversionAlive: + rmtree(options.targetDir, True) + raise UserWarning("Conversion interrupted.") + if len(mergeWorkerOutput) > 0: + rmtree(options.targetDir, True) + raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0]) for root, dirs, files in os.walk(options.targetDir, False): for name in files: if getImageFileName(name) is not None: @@ -261,7 +320,9 @@ def main(argv=None, qtGUI=None): else: os.remove(os.path.join(root, name)) if GUI: + GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Splitting images') GUI.emit(QtCore.SIGNAL("progressBarTick"), pagenumber) + GUI.emit(QtCore.SIGNAL("progressBarTick")) if len(work) > 0: for i in work: splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImage_tick) diff --git a/kcc/image.py b/kcc/image.py index a04300c..a703177 100755 --- a/kcc/image.py +++ b/kcc/image.py @@ -25,27 +25,23 @@ from sys import platform try: # noinspection PyUnresolvedReferences from PIL import Image, ImageOps, ImageStat, ImageChops - if tuple(map(int, ('2.2.1'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))): - print("ERROR: Pillow 2.2.1 or newer is required!") + if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))): + print("ERROR: Pillow 2.3.0 or newer is required!") if platform.startswith('linux'): - #noinspection PyUnresolvedReferences import tkinter - #noinspection PyUnresolvedReferences import tkinter.messagebox importRoot = tkinter.Tk() importRoot.withdraw() - tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!") + tkinter.messagebox.showerror("KCC - Error", "Pillow 2.3.0 or newer is required!") exit(1) except ImportError: print("ERROR: Pillow is not installed!") if platform.startswith('linux'): - #noinspection PyUnresolvedReferences import tkinter - #noinspection PyUnresolvedReferences import tkinter.messagebox importRoot = tkinter.Tk() importRoot.withdraw() - tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!") + tkinter.messagebox.showerror("KCC - Error", "Pillow 2.3.0 or newer is required!") exit(1) @@ -111,43 +107,14 @@ class ProfileData: 'KFHD8': ("K. Fire HD 8.9\"", (1200, 1920), PalleteNull, 1.0, (1800, 2880)), 'KFHDX': ("K. Fire HDX 7\"", (1200, 1920), PalleteNull, 1.0, (1800, 2880)), 'KFHDX8': ("K. Fire HDX 8.9\"", (1600, 2560), PalleteNull, 1.0, (2400, 3840)), + 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)), + 'KoG': ("Kobo Glow", (768, 1024), Palette16, 1.8, (1152, 1536)), + 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)), + 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)), 'KFA': ("Kindle for Android", (0, 0), PalleteNull, 1.0, (0, 0)), 'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)), } - ProfileLabels = { - "Kindle 1": 'K1', - "Kindle 2": 'K2', - "Kindle": 'K345', - "Kindle Paperwhite": 'KHD', - "Kindle DX/DXG": 'KDX', - "Kindle Fire": 'KF', - "K. Fire HD 7\"": 'KFHD', - "K. Fire HD 8.9\"": 'KFHD8', - "K. Fire HDX 7\"": 'KFHDX', - "K. Fire HDX 8.9\"": 'KFHDX8', - "Kindle for Android": 'KFA', - "Other": 'OTHER' - } - - ProfileLabelsGUI = [ - "Kindle Paperwhite", - "Kindle", - "Separator", - "K. Fire HD 7\"", - "K. Fire HD 8.9\"", - "K. Fire HDX 7\"", - "K. Fire HDX 8.9\"", - "Separator", - "Kindle for Android", - "Other", - "Separator", - "Kindle 1", - "Kindle 2", - "Kindle DX/DXG", - "Kindle Fire" - ] - class ComicPage: def __init__(self, source, device, fill=None): @@ -155,31 +122,16 @@ class ComicPage: self.profile_label, self.size, self.palette, self.gamma, self.panelviewsize = device except KeyError: raise RuntimeError('Unexpected output device %s' % device) - # Detect corrupted files - Phase 2 - try: - self.origFileName = source - self.filename = os.path.basename(self.origFileName) - self.image = Image.open(source) - except IOError: - raise RuntimeError('Cannot read image file %s' % source) - # Detect corrupted files - Phase 3 - try: - self.image = Image.open(source) - self.image.verify() - except: - raise RuntimeError('Image file %s is corrupted' % source) - # Detect corrupted files - Phase 4 - try: - self.image = Image.open(source) - self.image.load() - except: - raise RuntimeError('Image file %s is corrupted' % source) + self.origFileName = source + self.filename = os.path.basename(self.origFileName) self.image = Image.open(source) self.image = self.image.convert('RGB') self.rotated = None self.border = None self.noHPV = None self.noVPV = None + self.noPV = None + self.purge = False if fill: self.fill = fill else: @@ -196,19 +148,23 @@ class ComicPage: os.remove(os.path.join(targetdir, self.filename)) else: suffix += "_kcchq" - if self.noHPV: - suffix += "_kccnh" - if self.noVPV: - suffix += "_kccnv" - if self.border: - suffix += "_kccxl" + str(self.border[0]) + "_kccyu" + str(self.border[1]) + "_kccxr" +\ - str(self.border[2]) + "_kccyd" + str(self.border[3]) - if forcepng: - self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".png"), "PNG", - optimize=1) + if self.noPV: + suffix += "_kccnpv" else: - self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".jpg"), "JPEG", - optimize=1) + if self.noHPV: + suffix += "_kccnh" + if self.noVPV: + suffix += "_kccnv" + if self.border: + suffix += "_kccxl" + str(self.border[0]) + "_kccyu" + str(self.border[1]) + "_kccxr" +\ + str(self.border[2]) + "_kccyd" + str(self.border[3]) + if not self.purge: + if forcepng: + self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".png"), + "PNG", optimize=1) + else: + self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".jpg"), + "JPEG", optimize=1) except IOError as e: raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e)) @@ -240,8 +196,12 @@ class ComicPage: return int(round(float(x)/float(img.image.size[1]), 4) * 10000 * 1.5) def calculateBorder(self, sourceImage, isHQ=False): + if isHQ and sourceImage.purge: + self.border = [0, 0, 0, 0] + self.noPV = True + return if self.fill == 'white': - # This code trigger only when sourceImage is already saved. So we can break color quantization. + # Only already saved files can have P mode. So we can break color quantization. if sourceImage.image.mode == 'P': sourceImage.image = sourceImage.image.convert('RGB') border = ImageChops.invert(sourceImage.image).getbbox() @@ -275,12 +235,9 @@ class ComicPage: size = (self.size[0], self.size[1]) else: size = (self.panelviewsize[0], self.panelviewsize[1]) - # If image is smaller than device resolution and upscale is off - Just expand it by adding margins - if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not upscale: - borderw = (self.size[0] - self.image.size[0]) / 2 - borderh = (self.size[1] - self.image.size[1]) / 2 - self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill) - return self.image + # If image is small and HQ mode is on we have to force upscaling. Otherwise non-zoomed image will be distorted + if self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and qualityMode == 1 and not stretch: + upscale = True # If stretching is on - Resize without other considerations if stretch: if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]: @@ -289,8 +246,20 @@ class ComicPage: method = Image.ANTIALIAS self.image = self.image.resize(size, method) return self.image + # If image is smaller than target resolution and upscale is off - Just expand it by adding margins + if self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not upscale: + borderw = (size[0] - self.image.size[0]) / 2 + borderh = (size[1] - self.image.size[1]) / 2 + # PV is disabled when source image is smaller than device screen and upscale is off - So we drop HQ image + if qualityMode == 2 and self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]: + self.purge = True + self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill) + # Border can't be float so sometimes image might be 1px too small/large + if self.image.size[0] != size[0] or self.image.size[1] != size[1]: + self.image = ImageOps.fit(self.image, size, method=Image.BICUBIC, centering=(0.5, 0.5)) + return self.image # Otherwise - Upscale/Downscale - ratioDev = float(self.size[0]) / float(self.size[1]) + ratioDev = float(size[0]) / float(size[1]) if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: diff = int(self.image.size[1] * ratioDev) - self.image.size[0] self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=fill) @@ -433,7 +402,7 @@ class ComicPage: self.image = self.image.crop((0, 0, widthImg - diff, heightImg)) return self.image - def getImageHistogram(self, image, new=True): + def getImageHistogram(self, image): histogram = image.histogram() RBGW = [] pixelCount = 0 @@ -446,74 +415,35 @@ class ComicPage: white += RBGW[i] for i in range(5): black += RBGW[i] - if new: - if black > 0 and white == 0: - return 1 - elif white > 0 and black == 0: - return -1 - else: - return False + if black > pixelCount*0.8 and white == 0: + return 1 + elif white > pixelCount*0.8 and black == 0: + return -1 else: - if black > white and black > pixelCount*0.5: - return True - else: - return False + return False - def getImageFill(self, isWebToon): - if isWebToon: - fill = 0 - fill += self.getImageHistogram(self.image.crop((0, 0, self.image.size[0], 5)), False) - fill += self.getImageHistogram(self.image.crop((0, self.image.size[1]-5, self.image.size[0], - self.image.size[1])), False) - if fill == 2: - self.fill = 'black' - elif fill == 0: - self.fill = 'white' - else: - fill = 0 - fill += self.getImageHistogram(self.image.crop((0, 0, 5, 5)), False) - fill += self.getImageHistogram(self.image.crop((self.image.size[0]-5, 0, self.image.size[0], 5)), False) - fill += self.getImageHistogram(self.image.crop((0, self.image.size[1]-5, 5, self.image.size[1])), False) - fill += self.getImageHistogram(self.image.crop((self.image.size[0]-5, self.image.size[1]-5, - self.image.size[0], self.image.size[1])), False) - if fill > 1: - self.fill = 'black' - else: - self.fill = 'white' - else: - fill = 0 - # Search fom horizontal solid lines + def getImageFill(self, webtoon): + fill = 0 + if not webtoon and not self.rotated: + # Search for horizontal solid lines startY = 0 - stopY = 3 - searching = True - while stopY <= self.image.size[1]: - checkSolid = self.getImageHistogram(self.image.crop((0, startY, self.image.size[0], stopY))) + while startY < self.image.size[1]: + checkSolid = self.getImageHistogram(self.image.crop((0, startY, self.image.size[0], startY+1))) if checkSolid: fill += checkSolid - startY = stopY + 1 - stopY = startY + 3 - if stopY > self.image.size[1] and searching: - startY = self.image.size[1] - 3 - stopY = self.image.size[1] - searching = False - # Search fom vertical solid lines + startY += 1 + else: + # Search for vertical solid lines startX = 0 - stopX = 3 - searching = True - while stopX <= self.image.size[0]: - checkSolid = self.getImageHistogram(self.image.crop((startX, 0, stopX, self.image.size[1]))) + while startX < self.image.size[0]: + checkSolid = self.getImageHistogram(self.image.crop((startX, 0, startX+1, self.image.size[1]))) if checkSolid: fill += checkSolid - startX = stopX + 1 - stopX = startX + 3 - if stopX > self.image.size[0] and searching: - startX = self.image.size[0] - 3 - stopX = self.image.size[0] - searching = False - if fill > 0: - self.fill = 'black' - else: - self.fill = 'white' + startX += 1 + if fill > 0: + self.fill = 'black' + else: + self.fill = 'white' def isImageColor(self, image): v = ImageStat.Stat(image).var diff --git a/kcc/kindlesplit.py b/kcc/kindlesplit.py index c74ff95..e6b7841 100644 --- a/kcc/kindlesplit.py +++ b/kcc/kindlesplit.py @@ -3,7 +3,7 @@ # # Based on initial version of KindleUnpack. Copyright (C) 2009 Charles M. Hannum # Improvements Copyright (C) 2009-2012 P. Durrant, K. Hendricks, S. Siebert, fandrieu, DiapDealer, nickredding -# Copyright (C) 2013 Pawel Jastrzebski +# Changes for KCC Copyright (C) 2013 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 @@ -18,10 +18,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__license__ = 'ISC' -__copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jastrzebski ' -__docformat__ = 'restructuredtext en' - import struct # from uuid import uuid4 diff --git a/kcc/pdfjpgextract.py b/kcc/pdfjpgextract.py index 76f8a0e..34ea74d 100644 --- a/kcc/pdfjpgextract.py +++ b/kcc/pdfjpgextract.py @@ -24,13 +24,16 @@ __copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jas __docformat__ = 'restructuredtext en' import os +from random import choice +from string import ascii_uppercase, digits class PdfJpgExtract: def __init__(self, origFileName): self.origFileName = origFileName self.filename = os.path.splitext(origFileName) - self.path = self.filename[0] + "-KCC-TMP" + # noinspection PyUnusedLocal + self.path = self.filename[0] + "-KCC-TMP-" + ''.join(choice(ascii_uppercase + digits) for x in range(3)) def getPath(self): return self.path @@ -63,7 +66,6 @@ class PdfJpgExtract: istart += startfix iend += endfix - print("JPG %d from %d to %d" % (njpg, istart, iend)) jpg = pdf[istart:iend] jpgfile = file(self.path + "/jpg%d.jpg" % njpg, "wb") jpgfile.write(jpg) diff --git a/setup.py b/setup.py index 149cb48..1243ab2 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ if platform == "darwin": CFBundleTypeRole='Viewer', ) ], - LSMinimumSystemVersion='10.6.0', + LSMinimumSystemVersion='10.8.0', LSEnvironment=dict( PATH='/usr/local/bin:/usr/bin:/bin' ),