mirror of
https://github.com/ciromattia/kcc
synced 2026-04-16 05:58:52 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eab63a0f74 | ||
|
|
2128104db7 | ||
|
|
c6179b0064 | ||
|
|
1d4319be2e | ||
|
|
f5a738e2d4 | ||
|
|
477d834a91 | ||
|
|
c8698f6d99 | ||
|
|
0988601842 | ||
|
|
57e9637c81 | ||
|
|
a7440e06a9 | ||
|
|
a9ed1e7610 | ||
|
|
b1bc140ad3 | ||
|
|
9014ed53d4 | ||
|
|
cad05904f3 | ||
|
|
10386d8af3 | ||
|
|
c991feb9ce | ||
|
|
d26eb7cdcd | ||
|
|
351084b703 | ||
|
|
e861e7f6e8 | ||
|
|
370c9d4df7 | ||
|
|
8e5704683c | ||
|
|
c65e1c8dea | ||
|
|
677622c103 | ||
|
|
af0ebb85a0 | ||
|
|
8af029ac92 | ||
|
|
a268e12a90 | ||
|
|
d621335e6c | ||
|
|
ec1d9c2d93 | ||
|
|
85b9dbbf83 | ||
|
|
feeced44bf | ||
|
|
cbea18398b | ||
|
|
4c9857f14d | ||
|
|
6b58ef4557 | ||
|
|
24d697c965 | ||
|
|
8b07d4eb69 | ||
|
|
e6c5ac915f | ||
|
|
b22e4757a3 | ||
|
|
91b06016bb | ||
|
|
5631391245 | ||
|
|
c33887b7b7 |
66
README.md
66
README.md
@@ -1,7 +1,7 @@
|
||||
# KCC
|
||||
|
||||
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
|
||||
It was initially developed for Kindle but since version 4.0 it outputs valid EPUB 3.0 so _**despite its name, KCC is
|
||||
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
|
||||
actually a comic/manga to EPUB converter that every e-reader owner can happily use**_.
|
||||
It can also optionally optimize images by applying a number of transformations.
|
||||
|
||||
@@ -32,11 +32,11 @@ You can find the latest released binary at the following links:
|
||||
## DEPENDENCIES
|
||||
Following software is required to run Linux version of **KCC** and/or bare sources:
|
||||
- Python 3.3+
|
||||
- [PyQt](http://www.riverbankcomputing.co.uk/software/pyqt/download5) 5.2.0+
|
||||
- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.7.0+
|
||||
- [psutil](https://pypi.python.org/pypi/psutil) 2.0+
|
||||
- [python-slugify](http://pypi.python.org/pypi/python-slugify) 0.1.0+
|
||||
- [scandir](https://pypi.python.org/pypi/scandir) 0.9+
|
||||
- [PyQt](http://www.riverbankcomputing.co.uk/software/pyqt/download5) 5.4.0+
|
||||
- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.8.2+
|
||||
- [psutil](https://pypi.python.org/pypi/psutil) 3.0.0+
|
||||
- [python-slugify](http://pypi.python.org/pypi/python-slugify) 1.1.3+
|
||||
- [scandir](https://pypi.python.org/pypi/scandir) 1.1.0+
|
||||
|
||||
On Debian based distributions these two commands should install all needed dependencies:
|
||||
```
|
||||
@@ -75,8 +75,7 @@ Options:
|
||||
MAIN:
|
||||
-p PROFILE, --profile=PROFILE
|
||||
Device profile (Available options: K1, K2, K345, KDX,
|
||||
KPW, KV, KFHD, KFHDX, KFHDX8, KFA, KoMT, KoG, KoGHD, KoA,
|
||||
KoAHD, KoAH2O) [Default=KV]
|
||||
KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O) [Default=KV]
|
||||
-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)
|
||||
@@ -145,18 +144,45 @@ The app relies and includes the following scripts:
|
||||
- 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.
|
||||
|
||||
## SAMPLE FILES CREATED BY KCC
|
||||
* [Kindle Voyage](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||
* [Kindle Paperwhite](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
||||
* [Kindle Paperwhite 3 / Voyage](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!-K345.mobi)
|
||||
* [Kindle DX/DXG](http://kcc.iosphe.re/Samples/Ubunchu!-KDX.cbz)
|
||||
* [Kobo Mini/Touch](http://kcc.iosphe.re/Samples/Ubunchu!-KoMT.cbz)
|
||||
* [Kobo Glo](http://kcc.iosphe.re/Samples/Ubunchu!-KoG.cbz)
|
||||
* [Kobo Glo HD](http://kcc.iosphe.re/Samples/Ubunchu!-KoGHD.cbz)
|
||||
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu!-KoA.cbz)
|
||||
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu!-KoAHD.cbz)
|
||||
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu!-KoAH2O.cbz)
|
||||
* [Kobo Mini/Touch](http://kcc.iosphe.re/Samples/Ubunchu-KoMT.kepub.epub)
|
||||
* [Kobo Glo](http://kcc.iosphe.re/Samples/Ubunchu-KoG.kepub.epub)
|
||||
* [Kobo Glo HD](http://kcc.iosphe.re/Samples/Ubunchu-KoGHD.kepub.epub)
|
||||
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub)
|
||||
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub)
|
||||
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub)
|
||||
|
||||
## CHANGELOG
|
||||
####4.6.4:
|
||||
* Fixed multiple Windows specific problems
|
||||
* Improved error handling
|
||||
* Improved color detection algorithm
|
||||
* New, slimmer OS X release
|
||||
|
||||
####4.6.3:
|
||||
* Implemented remote bug reporting
|
||||
* Minor bug fixes and GUI tweaks
|
||||
|
||||
####4.6.2:
|
||||
* Fixed critical MOBI header bug
|
||||
* Fixed metadata encoding error
|
||||
|
||||
####4.6.1:
|
||||
* Fixed KEPUB TOC generator
|
||||
* Added warning about too small input files
|
||||
* ComicRack Summary metadata field is now parsed
|
||||
* Small tweaks of KEPUB output
|
||||
|
||||
####4.6:
|
||||
* KEPUB is now default output for all Kobo profiles
|
||||
* EPUB output now produce fully valid EPUB 3.0.1
|
||||
* Added profile for Kindle Paperwhite 3
|
||||
* Dropped official support of all Kindle Fire models and Kindle for Android
|
||||
* Other minor tweaks
|
||||
|
||||
####4.5.1:
|
||||
* Added Kobo Glo HD profile
|
||||
* Fixed RAR/CBR parsing anomalies
|
||||
@@ -407,6 +433,14 @@ The app relies and includes the following scripts:
|
||||
####1.0
|
||||
* Initial version
|
||||
|
||||
## 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
|
||||
|
||||
Error report include **KCC** version, OS version and content of error message.
|
||||
|
||||
## KNOWN ISSUES
|
||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||
|
||||
|
||||
@@ -462,6 +462,12 @@
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="BasicModeButton">
|
||||
<property name="geometry">
|
||||
|
||||
@@ -397,6 +397,12 @@
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="BasicModeButton">
|
||||
<property name="geometry">
|
||||
|
||||
@@ -126,6 +126,9 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
|
||||
BIN
icons/WizardOSX.png
Normal file
BIN
icons/WizardOSX.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 328 KiB |
@@ -34,4 +34,4 @@ if __name__ == "__main__":
|
||||
freeze_support()
|
||||
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||
main(sys.argv[1:])
|
||||
sys.exit(0)
|
||||
sys.exit(0)
|
||||
|
||||
@@ -34,4 +34,4 @@ if __name__ == "__main__":
|
||||
freeze_support()
|
||||
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||
main(sys.argv[1:])
|
||||
sys.exit(0)
|
||||
sys.exit(0)
|
||||
|
||||
2
kcc.iss
2
kcc.iss
@@ -1,5 +1,5 @@
|
||||
#define MyAppName "Kindle Comic Converter"
|
||||
#define MyAppVersion "4.5.1"
|
||||
#define MyAppVersion "4.6.4"
|
||||
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
|
||||
#define MyAppURL "http://kcc.iosphe.re/"
|
||||
#define MyAppExeName "KCC.exe"
|
||||
|
||||
@@ -76,6 +76,7 @@ class Ui_MetaEditorDialog(object):
|
||||
self.formLayoutWidget.setObjectName("formLayoutWidget")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.formLayoutWidget)
|
||||
self.formLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.ExpandingFieldsGrow)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label = QtWidgets.QLabel(self.formLayoutWidget)
|
||||
self.label.setObjectName("label")
|
||||
|
||||
157
kcc/KCC_gui.py
157
kcc/KCC_gui.py
@@ -20,21 +20,22 @@
|
||||
import os
|
||||
import sys
|
||||
from urllib.parse import unquote
|
||||
from urllib.request import urlopen, urlretrieve
|
||||
from urllib.request import urlopen, urlretrieve, Request
|
||||
from socket import gethostbyname_ex, gethostname
|
||||
from traceback import format_tb
|
||||
from time import sleep
|
||||
from time import sleep, time
|
||||
from datetime import datetime
|
||||
from shutil import move
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from socketserver import ThreadingMixIn
|
||||
from subprocess import STDOUT, PIPE
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
|
||||
from xml.dom.minidom import parse
|
||||
from xml.dom.minidom import parse, Document
|
||||
from psutil import Popen, Process
|
||||
from copy import copy
|
||||
from distutils.version import StrictVersion
|
||||
from xml.sax.saxutils import escape
|
||||
from .shared import md5Checksum, HTMLStripper
|
||||
from platform import platform
|
||||
from .shared import md5Checksum, HTMLStripper, sanitizeTrace
|
||||
from . import __version__
|
||||
from . import comic2ebook
|
||||
from . import KCC_rc_web
|
||||
@@ -65,7 +66,6 @@ class QApplicationMessaging(QtWidgets.QApplication):
|
||||
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
|
||||
if not socket.waitForConnected(self._timeout):
|
||||
self._server = QtNetwork.QLocalServer(self)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self._server.newConnection.connect(self.handleMessage)
|
||||
self._server.listen(self._key)
|
||||
else:
|
||||
@@ -138,7 +138,7 @@ class Icons:
|
||||
|
||||
|
||||
class WebServerHandler(BaseHTTPRequestHandler):
|
||||
# noinspection PyAttributeOutsideInit, PyArgumentList
|
||||
# noinspection PyAttributeOutsideInit
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
self.path = '/index.html'
|
||||
@@ -245,22 +245,22 @@ class VersionThread(QtCore.QThread):
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
XML = urlopen('http://kcc.iosphe.re/Version.php')
|
||||
XML = parse(XML)
|
||||
XML = parse(urlopen(Request('https://kcc.iosphe.re/Version/',
|
||||
headers={'User-Agent': 'KindleComicConverter/' + __version__})))
|
||||
except Exception:
|
||||
return
|
||||
latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml()
|
||||
latestVersion = XML.childNodes[0].getElementsByTagName('LatestVersion')[0].childNodes[0].toxml()
|
||||
if StrictVersion(latestVersion) > StrictVersion(__version__):
|
||||
if sys.platform.startswith('win'):
|
||||
self.newVersion = latestVersion
|
||||
self.md5 = XML.childNodes[0].getElementsByTagName('WindowsMD5')[0].childNodes[0].toxml()
|
||||
self.md5 = XML.childNodes[0].getElementsByTagName('MD5')[0].childNodes[0].toxml()
|
||||
MW.showDialog.emit('<b>New version released!</b> <a href="https://github.com/ciromattia/kcc/releases/">'
|
||||
'See changelog.</a><br/><br/>Installed version: ' + __version__ +
|
||||
'<br/>Current version: ' + latestVersion +
|
||||
'<br/><br/>Would you like to start automatic update?', 'question')
|
||||
self.getNewVersion()
|
||||
else:
|
||||
MW.addMessage.emit('<a href="http://kcc.iosphe.re/">'
|
||||
MW.addMessage.emit('<a href="https://kcc.iosphe.re/">'
|
||||
'<b>New version is available!</b></a> '
|
||||
'(<a href="https://github.com/ciromattia/kcc/releases/">'
|
||||
'Changelog</a>)', 'warning', False)
|
||||
@@ -275,8 +275,8 @@ class VersionThread(QtCore.QThread):
|
||||
try:
|
||||
MW.modeConvert.emit(-1)
|
||||
MW.progressBarTick.emit('Downloading update')
|
||||
path = urlretrieve('http://kcc.iosphe.re/Windows/KindleComicConverter_win_'
|
||||
+ self.newVersion + '.exe', reporthook=self.getNewVersionTick)
|
||||
path = urlretrieve('https://kcc.iosphe.re/Windows/KindleComicConverter_win_' +
|
||||
self.newVersion + '.exe', reporthook=self.getNewVersionTick)
|
||||
if self.md5 != md5Checksum(path[0]):
|
||||
raise Exception
|
||||
move(path[0], path[0] + '.exe')
|
||||
@@ -323,7 +323,6 @@ class ProgressThread(QtCore.QThread):
|
||||
|
||||
|
||||
class WorkerThread(QtCore.QThread):
|
||||
# noinspection PyArgumentList
|
||||
def __init__(self):
|
||||
QtCore.QThread.__init__(self)
|
||||
self.conversionAlive = False
|
||||
@@ -348,12 +347,6 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.addTrayMessage.emit('Conversion interrupted.', 'Critical')
|
||||
MW.modeConvert.emit(1)
|
||||
|
||||
def sanitizeTrace(self, traceback):
|
||||
return ''.join(format_tb(traceback))\
|
||||
.replace('C:\\Users\\AcidWeb\\Documents\\Projekty\\KCC\\', '')\
|
||||
.replace('C:\\Python34\\', '')\
|
||||
.replace('C:\\Python34_64\\', '')
|
||||
|
||||
def run(self):
|
||||
MW.modeConvert.emit(0)
|
||||
|
||||
@@ -375,9 +368,6 @@ class WorkerThread(QtCore.QThread):
|
||||
elif GUI.QualityBox.checkState() == 2:
|
||||
options.quality = 2
|
||||
options.format = str(GUI.FormatBox.currentText())
|
||||
if GUI.currentMode == 1:
|
||||
if 'KFH' in profile:
|
||||
options.upscale = True
|
||||
|
||||
# Advanced mode settings
|
||||
if GUI.currentMode > 1:
|
||||
@@ -440,16 +430,20 @@ class WorkerThread(QtCore.QThread):
|
||||
GUI.progress.content = ''
|
||||
self.errors = True
|
||||
MW.addMessage.emit(str(warn), 'warning', False)
|
||||
MW.addMessage.emit('Failed to create output file!', 'error', False)
|
||||
MW.addTrayMessage.emit('Failed to create output file!', 'Critical')
|
||||
MW.addMessage.emit('Error during conversion! Please consult '
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> '
|
||||
'for more details.', 'error', False)
|
||||
MW.addTrayMessage.emit('Error during conversion!', 'Critical')
|
||||
except Exception as err:
|
||||
GUI.progress.content = ''
|
||||
self.errors = True
|
||||
_, _, traceback = sys.exc_info()
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), self.sanitizeTrace(traceback)), 'error')
|
||||
MW.addMessage.emit('Failed to create EPUB!', 'error', False)
|
||||
MW.addTrayMessage.emit('Failed to create EPUB!', 'Critical')
|
||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||
MW.addMessage.emit('Error during conversion! Please consult '
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> '
|
||||
'for more details.', 'error', False)
|
||||
MW.addTrayMessage.emit('Error during conversion!', 'Critical')
|
||||
if not self.conversionAlive:
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
@@ -464,7 +458,7 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True)
|
||||
if str(GUI.FormatBox.currentText()) == 'MOBI':
|
||||
MW.progressBarTick.emit('Creating MOBI files')
|
||||
MW.progressBarTick.emit(str(len(outputPath)*2+1))
|
||||
MW.progressBarTick.emit(str(len(outputPath) * 2 + 1))
|
||||
MW.progressBarTick.emit('tick')
|
||||
MW.addMessage.emit('Creating MOBI files', 'info', False)
|
||||
GUI.progress.content = 'Creating MOBI files'
|
||||
@@ -503,7 +497,7 @@ class WorkerThread(QtCore.QThread):
|
||||
GUI.progress.content = ''
|
||||
mobiPath = item.replace('.epub', '.mobi')
|
||||
os.remove(mobiPath + '_toclean')
|
||||
if GUI.targetDirectory and GUI.targetDirectory != os.path.split(mobiPath)[0]:
|
||||
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(mobiPath):
|
||||
try:
|
||||
move(mobiPath, GUI.targetDirectory)
|
||||
mobiPath = os.path.join(GUI.targetDirectory, os.path.basename(mobiPath))
|
||||
@@ -523,7 +517,7 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical')
|
||||
else:
|
||||
GUI.progress.content = ''
|
||||
epubSize = (os.path.getsize(self.kindlegenErrorCode[2]))//1024//1024
|
||||
epubSize = (os.path.getsize(self.kindlegenErrorCode[2])) // 1024 // 1024
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
os.remove(item)
|
||||
@@ -539,7 +533,7 @@ class WorkerThread(QtCore.QThread):
|
||||
False)
|
||||
else:
|
||||
for item in outputPath:
|
||||
if GUI.targetDirectory and GUI.targetDirectory != os.path.split(item)[0]:
|
||||
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(item):
|
||||
try:
|
||||
move(item, GUI.targetDirectory)
|
||||
item = os.path.join(GUI.targetDirectory, os.path.basename(item))
|
||||
@@ -550,16 +544,17 @@ class WorkerThread(QtCore.QThread):
|
||||
GUI.progress.stop()
|
||||
MW.hideProgressBar.emit()
|
||||
GUI.needClean = True
|
||||
MW.addMessage.emit('<b>All jobs completed.</b>', 'info', False)
|
||||
MW.addTrayMessage.emit('All jobs completed.', 'Information')
|
||||
if not self.errors:
|
||||
MW.addMessage.emit('<b>All jobs completed.</b>', 'info', False)
|
||||
MW.addTrayMessage.emit('All jobs completed.', 'Information')
|
||||
MW.modeConvert.emit(1)
|
||||
|
||||
|
||||
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
if self.isSystemTrayAvailable():
|
||||
QtWidgets.QSystemTrayIcon.__init__(self, GUI.icons.programIcon, MW)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.activated.connect(self.catchClicks)
|
||||
|
||||
def catchClicks(self):
|
||||
@@ -635,8 +630,10 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.lastPath = os.path.abspath(os.path.join(fname, os.pardir))
|
||||
try:
|
||||
self.editor.loadData(fname)
|
||||
except:
|
||||
self.showDialog('Failed to parse metadata!', 'error')
|
||||
except Exception as err:
|
||||
_, _, traceback = sys.exc_info()
|
||||
self.showDialog("Failed to parse metadata!\n\n%s\n\nTraceback:\n%s"
|
||||
% (str(err), sanitizeTrace(traceback)), 'error')
|
||||
else:
|
||||
self.editor.ui.exec_()
|
||||
|
||||
@@ -845,7 +842,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
|
||||
def changeGamma(self, value):
|
||||
value = float(value)
|
||||
value = '%.2f' % (value/100)
|
||||
value = '%.2f' % (value / 100)
|
||||
if float(value) <= 0.09:
|
||||
GUI.GammaLabel.setText('Gamma: Auto')
|
||||
else:
|
||||
@@ -913,7 +910,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
else:
|
||||
item = QtWidgets.QListWidgetItem(' ' + self.stripTags(message))
|
||||
if replace:
|
||||
GUI.JobList.takeItem(GUI.JobList.count()-1)
|
||||
GUI.JobList.takeItem(GUI.JobList.count() - 1)
|
||||
# Due to lack of HTML support in QListWidgetItem we overlay text field with QLabel
|
||||
# We still fill original text field with transparent content to trigger creation of horizontal scrollbar
|
||||
item.setForeground(QtGui.QColor('transparent'))
|
||||
@@ -930,6 +927,31 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
def showDialog(self, message, kind):
|
||||
if kind == 'error':
|
||||
QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok)
|
||||
try:
|
||||
doc = Document()
|
||||
root = doc.createElement('KCCErrorReport')
|
||||
doc.appendChild(root)
|
||||
main = doc.createElement('Timestamp')
|
||||
root.appendChild(main)
|
||||
text = doc.createTextNode(datetime.fromtimestamp(time()).strftime('%Y-%m-%d %H:%M:%S'))
|
||||
main.appendChild(text)
|
||||
main = doc.createElement('OS')
|
||||
root.appendChild(main)
|
||||
text = doc.createTextNode(platform())
|
||||
main.appendChild(text)
|
||||
main = doc.createElement('Version')
|
||||
root.appendChild(main)
|
||||
text = doc.createTextNode(__version__)
|
||||
main.appendChild(text)
|
||||
main = doc.createElement('Error')
|
||||
root.appendChild(main)
|
||||
text = doc.createTextNode(message)
|
||||
main.appendChild(text)
|
||||
urlopen(Request(url='https://kcc.iosphe.re/ErrorHandle/', data=doc.toxml(encoding='utf-8'),
|
||||
headers={'Content-Type': 'application/xml',
|
||||
'User-Agent': 'KindleComicConverter/' + __version__}))
|
||||
except:
|
||||
pass
|
||||
elif kind == 'question':
|
||||
GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message,
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
@@ -1028,7 +1050,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
'ColorBox': GUI.ColorBox.checkState(),
|
||||
'customWidth': GUI.customWidth.text(),
|
||||
'customHeight': GUI.customHeight.text(),
|
||||
'GammaSlider': float(self.GammaValue)*100})
|
||||
'GammaSlider': float(self.GammaValue) * 100})
|
||||
self.settings.sync()
|
||||
self.tray.hide()
|
||||
|
||||
@@ -1161,50 +1183,38 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.p.ionice(1)
|
||||
|
||||
self.profiles = {
|
||||
"Kindle Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KV'},
|
||||
"Kindle Paperwhite": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KPW'},
|
||||
"K. PW 3/Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KV'},
|
||||
"Kindle PW 1/2": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KPW'},
|
||||
"Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K345'},
|
||||
"Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
'DefaultUpscale': False, 'Label': 'KDX'},
|
||||
"K. Fire HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHD'},
|
||||
"K. Fire HDX": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHDX'},
|
||||
"K. Fire HDX 8.9": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHDX8'},
|
||||
"Kobo Mini/Touch": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
"Kobo Mini/Touch": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoMT'},
|
||||
"Kobo Glo": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
"Kobo Glo": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoG'},
|
||||
"Kobo Glo HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
"Kobo Glo HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoGHD'},
|
||||
"Kobo Aura": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
"Kobo Aura": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoA'},
|
||||
"Kobo Aura HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
"Kobo Aura HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoAHD'},
|
||||
"Kobo Aura H2O": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
"Kobo Aura H2O": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoAH2O'},
|
||||
"Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'OTHER'},
|
||||
"Kindle for Android": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KFA'},
|
||||
"Kindle 1": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K1'},
|
||||
"Kindle 2": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K2'}
|
||||
}
|
||||
profilesGUI = [
|
||||
"Kindle Voyage",
|
||||
"Kindle Paperwhite",
|
||||
"K. PW 3/Voyage",
|
||||
"Kindle PW 1/2",
|
||||
"Kindle",
|
||||
"Separator",
|
||||
"K. Fire HD",
|
||||
"K. Fire HDX",
|
||||
"K. Fire HDX 8.9",
|
||||
"Separator",
|
||||
"Kobo Mini/Touch",
|
||||
"Kobo Glo",
|
||||
"Kobo Glo HD",
|
||||
@@ -1214,13 +1224,12 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
"Separator",
|
||||
"Other",
|
||||
"Separator",
|
||||
"Kindle for Android",
|
||||
"Kindle 1",
|
||||
"Kindle 2",
|
||||
"Kindle DX/DXG",
|
||||
]
|
||||
|
||||
statusBarLabel = QtWidgets.QLabel('<b><a href="http://kcc.iosphe.re/">HOMEPAGE</a> - <a href="https://github.'
|
||||
statusBarLabel = QtWidgets.QLabel('<b><a href="https://kcc.iosphe.re/">HOMEPAGE</a> - <a href="https://github.'
|
||||
'com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations">DO'
|
||||
'NATE</a> - <a href="https://github.com/ciromattia/kcc/wiki">WIKI</a> - <a hr'
|
||||
'ef="http://www.mobileread.com/forums/showthread.php?t=207461">FORUM</a></b>')
|
||||
@@ -1288,7 +1297,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
if profile == "Other":
|
||||
GUI.DeviceBox.addItem(self.icons.deviceOther, profile)
|
||||
elif profile == "Separator":
|
||||
GUI.DeviceBox.insertSeparator(GUI.DeviceBox.count()+1)
|
||||
GUI.DeviceBox.insertSeparator(GUI.DeviceBox.count() + 1)
|
||||
elif 'Ko' in profile:
|
||||
GUI.DeviceBox.addItem(self.icons.deviceKobo, profile)
|
||||
else:
|
||||
@@ -1322,6 +1331,12 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.versionCheck.start()
|
||||
self.contentServer.start()
|
||||
self.tray.show()
|
||||
|
||||
# Linux hack as PyQt 5.5 not hit mainstream distributions yet
|
||||
if sys.platform.startswith('linux') and StrictVersion(QtCore.qVersion()) > StrictVersion('5.4.9'):
|
||||
self.JobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.JobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
|
||||
MW.setWindowTitle("Kindle Comic Converter " + __version__)
|
||||
MW.show()
|
||||
MW.raise_()
|
||||
@@ -1363,8 +1378,10 @@ class KCCGUI_MetaEditor(KCC_MetaEditor_ui.Ui_MetaEditorDialog):
|
||||
self.parser.data[field.objectName()[:-4] + 's'] = tmpData
|
||||
try:
|
||||
self.parser.saveXML()
|
||||
except:
|
||||
GUI.showDialog('Failed to save metadata!', 'error')
|
||||
except Exception as err:
|
||||
_, _, traceback = sys.exc_info()
|
||||
GUI.showDialog("Failed to save metadata!\n\n%s\n\nTraceback:\n%s"
|
||||
% (str(err), sanitizeTrace(traceback)), 'error')
|
||||
self.ui.close()
|
||||
|
||||
def cleanData(self, s):
|
||||
|
||||
@@ -141,6 +141,8 @@ class Ui_KCC(object):
|
||||
self.JobList.setStyleSheet("QListWidget#JobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}QScrollBar:vertical{border:1px solid #999;background:#FFF;width:5px;margin:0}QScrollBar::handle:vertical{background:DarkGray;min-height:0}QScrollBar::add-line:vertical{height:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:vertical{height:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}QScrollBar:horizontal{border:1px solid #999;background:#FFF;height:5px;margin:0}QScrollBar::handle:horizontal{background:DarkGray;min-width:0}QScrollBar::add-line:horizontal{width:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:horizontal{width:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}")
|
||||
self.JobList.setProperty("showDropIndicator", False)
|
||||
self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.JobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.JobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.JobList.setObjectName("JobList")
|
||||
self.BasicModeButton = QtWidgets.QPushButton(self.Form)
|
||||
self.BasicModeButton.setGeometry(QtCore.QRect(10, 10, 141, 32))
|
||||
|
||||
@@ -188,6 +188,8 @@ class Ui_KCC(object):
|
||||
self.JobList.setStyleSheet("QListWidget#JobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}QScrollBar:vertical{border:1px solid #999;background:#FFF;width:5px;margin:0}QScrollBar::handle:vertical{background:DarkGray;min-height:0}QScrollBar::add-line:vertical{height:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:vertical{height:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}QScrollBar:horizontal{border:1px solid #999;background:#FFF;height:5px;margin:0}QScrollBar::handle:horizontal{background:DarkGray;min-width:0}QScrollBar::add-line:horizontal{width:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:horizontal{width:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}")
|
||||
self.JobList.setProperty("showDropIndicator", False)
|
||||
self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.JobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.JobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.JobList.setObjectName("JobList")
|
||||
self.BasicModeButton = QtWidgets.QPushButton(self.Form)
|
||||
self.BasicModeButton.setGeometry(QtCore.QRect(5, 10, 156, 41))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '4.5.1'
|
||||
__version__ = '4.6.4'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2015, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -70,8 +70,8 @@ class CBxArchive:
|
||||
if sys.platform.startswith('darwin'):
|
||||
copy(self.origFileName, os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP'))
|
||||
self.origFileName = os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP')
|
||||
output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"'
|
||||
+ targetdir + '"', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' +
|
||||
targetdir + '"', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
extracted = False
|
||||
for line in output.stdout:
|
||||
if b"Everything is Ok" in line:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from time import strftime, gmtime
|
||||
from copy import copy
|
||||
from glob import glob
|
||||
from json import loads
|
||||
@@ -27,7 +28,7 @@ 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
|
||||
from tempfile import mkdtemp
|
||||
from tempfile import mkdtemp, gettempdir
|
||||
from shutil import move, copytree, rmtree
|
||||
from optparse import OptionParser, OptionGroup
|
||||
from multiprocessing import Pool
|
||||
@@ -37,11 +38,12 @@ from PIL import Image
|
||||
from subprocess import STDOUT, PIPE
|
||||
from psutil import Popen, virtual_memory
|
||||
from scandir import walk
|
||||
from html import escape
|
||||
try:
|
||||
from PyQt5 import QtCore
|
||||
except ImportError:
|
||||
QtCore = None
|
||||
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, saferReplace
|
||||
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, saferReplace
|
||||
from . import comic2panel
|
||||
from . import image
|
||||
from . import cbxarchive
|
||||
@@ -64,6 +66,7 @@ def main(argv=None):
|
||||
print('No matching files found.')
|
||||
return
|
||||
for source in sources:
|
||||
source = source.rstrip('\\').rstrip('/')
|
||||
options = copy(optionstemplate)
|
||||
checkOptions()
|
||||
if len(sources) > 1:
|
||||
@@ -119,83 +122,98 @@ def buildHTML(path, imgfile, imgfilepath, forcePV=False):
|
||||
os.makedirs(htmlpath)
|
||||
htmlfile = os.path.join(htmlpath, filename[0] + '.html')
|
||||
f = open(htmlfile, "w", encoding='UTF-8')
|
||||
f.writelines(["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" ",
|
||||
"\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n",
|
||||
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n",
|
||||
"<head>\n",
|
||||
"<title>", filename[0], "</title>\n",
|
||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n",
|
||||
"<link href=\"", "../" * (backref - 1),
|
||||
"style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
||||
"</head>\n",
|
||||
"<body" + additionalStyle + ">\n",
|
||||
"<div class=\"fs\">\n",
|
||||
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\" class=\"singlePage\"/></div>\n"
|
||||
])
|
||||
if (options.panelview or forcePV) and not noPV:
|
||||
options.panelviewused = True
|
||||
if not noHorizontalPV and not noVerticalPV:
|
||||
if rotatedPage:
|
||||
if options.righttoleft:
|
||||
order = [1, 3, 2, 4]
|
||||
if options.iskindle:
|
||||
f.writelines(["<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
|
||||
"<!DOCTYPE html>\n",
|
||||
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
|
||||
"<head>\n",
|
||||
"<title>", filename[0], "</title>\n",
|
||||
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
||||
"<meta charset=\"utf-8\"/>\n",
|
||||
"</head>\n",
|
||||
"<body" + additionalStyle + ">\n",
|
||||
"<div class=\"fs\">\n",
|
||||
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\" class=\"singlePage\"/></div>\n"
|
||||
])
|
||||
if (options.panelview or forcePV) and not noPV:
|
||||
options.panelviewused = True
|
||||
if not noHorizontalPV and not noVerticalPV:
|
||||
if rotatedPage:
|
||||
if options.righttoleft:
|
||||
order = [1, 3, 2, 4]
|
||||
else:
|
||||
order = [2, 4, 1, 3]
|
||||
else:
|
||||
order = [2, 4, 1, 3]
|
||||
else:
|
||||
if options.righttoleft:
|
||||
order = [2, 1, 4, 3]
|
||||
else:
|
||||
order = [1, 2, 3, 4]
|
||||
boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"]
|
||||
elif noHorizontalPV and not noVerticalPV:
|
||||
if rotatedPage:
|
||||
if options.righttoleft:
|
||||
order = [1, 2]
|
||||
else:
|
||||
order = [2, 1]
|
||||
else:
|
||||
order = [1, 2]
|
||||
boxes = ["BoxT", "BoxB"]
|
||||
elif not noHorizontalPV and noVerticalPV:
|
||||
if rotatedPage:
|
||||
order = [1, 2]
|
||||
else:
|
||||
if options.righttoleft:
|
||||
order = [2, 1]
|
||||
if options.righttoleft:
|
||||
order = [2, 1, 4, 3]
|
||||
else:
|
||||
order = [1, 2, 3, 4]
|
||||
boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"]
|
||||
elif noHorizontalPV and not noVerticalPV:
|
||||
if rotatedPage:
|
||||
if options.righttoleft:
|
||||
order = [1, 2]
|
||||
else:
|
||||
order = [2, 1]
|
||||
else:
|
||||
order = [1, 2]
|
||||
boxes = ["BoxL", "BoxR"]
|
||||
else:
|
||||
order = [1]
|
||||
boxes = ["BoxC"]
|
||||
for i in range(0, len(boxes)):
|
||||
f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=",
|
||||
"'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]),
|
||||
"}'></a></div>\n"])
|
||||
if options.quality == 2 and not forcePV:
|
||||
imgfilepv = imgfile.split(".")
|
||||
imgfilepv[0] += "-hq"
|
||||
imgfilepv = ".".join(imgfilepv)
|
||||
else:
|
||||
imgfilepv = imgfile
|
||||
xl, yu, xr, yd = detectMargins(imgfilepath)
|
||||
boxStyles = {"BoxTL": "left:" + xl + ";top:" + yu + ";",
|
||||
"BoxTR": "right:" + xr + ";top:" + yu + ";",
|
||||
"BoxBL": "left:" + xl + ";bottom:" + yd + ";",
|
||||
"BoxBR": "right:" + xr + ";bottom:" + yd + ";",
|
||||
"BoxT": "left:-25%;top:" + yu + ";",
|
||||
"BoxB": "left:-25%;bottom:" + yd + ";",
|
||||
"BoxL": "left:" + xl + ";top:-25%;",
|
||||
"BoxR": "right:" + xr + ";top:-25%;",
|
||||
"BoxC": "left:-25%;top:-25%;"
|
||||
}
|
||||
for box in boxes:
|
||||
f.writelines(["<div id=\"" + box + "-Panel-Parent\" class=\"target-mag-parent\"><div id=\"",
|
||||
"Generic-Panel\" class=\"target-mag\"><img style=\"" + boxStyles[box] + "\" src=\"",
|
||||
"../" * backref, "Images/", postfix, imgfilepv, "\" alt=\"" + imgfilepv,
|
||||
"\"/></div></div>\n",
|
||||
])
|
||||
f.writelines(["</div>\n</body>\n</html>"])
|
||||
boxes = ["BoxT", "BoxB"]
|
||||
elif not noHorizontalPV and noVerticalPV:
|
||||
if rotatedPage:
|
||||
order = [1, 2]
|
||||
else:
|
||||
if options.righttoleft:
|
||||
order = [2, 1]
|
||||
else:
|
||||
order = [1, 2]
|
||||
boxes = ["BoxL", "BoxR"]
|
||||
else:
|
||||
order = [1]
|
||||
boxes = ["BoxC"]
|
||||
for i in range(0, len(boxes)):
|
||||
f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=",
|
||||
"'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]),
|
||||
"}'></a></div>\n"])
|
||||
if options.quality == 2 and not forcePV:
|
||||
imgfilepv = imgfile.split(".")
|
||||
imgfilepv[0] += "-hq"
|
||||
imgfilepv = ".".join(imgfilepv)
|
||||
else:
|
||||
imgfilepv = imgfile
|
||||
xl, yu, xr, yd = detectMargins(imgfilepath)
|
||||
boxStyles = {"BoxTL": "left:" + xl + ";top:" + yu + ";",
|
||||
"BoxTR": "right:" + xr + ";top:" + yu + ";",
|
||||
"BoxBL": "left:" + xl + ";bottom:" + yd + ";",
|
||||
"BoxBR": "right:" + xr + ";bottom:" + yd + ";",
|
||||
"BoxT": "left:-25%;top:" + yu + ";",
|
||||
"BoxB": "left:-25%;bottom:" + yd + ";",
|
||||
"BoxL": "left:" + xl + ";top:-25%;",
|
||||
"BoxR": "right:" + xr + ";top:-25%;",
|
||||
"BoxC": "left:-25%;top:-25%;"
|
||||
}
|
||||
for box in boxes:
|
||||
f.writelines(["<div id=\"" + box + "-Panel-Parent\" class=\"target-mag-parent\"><div id=\"",
|
||||
"Generic-Panel\" class=\"target-mag\"><img style=\"" + boxStyles[box] + "\" src=\"",
|
||||
"../" * backref, "Images/", postfix, imgfilepv, "\" alt=\"" + imgfilepv,
|
||||
"\"/></div></div>\n",
|
||||
])
|
||||
f.writelines(["</div>\n</body>\n</html>"])
|
||||
else:
|
||||
f.writelines(["<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
|
||||
"<!DOCTYPE html>\n",
|
||||
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
|
||||
"<head>\n",
|
||||
"<title>", filename[0], "</title>\n",
|
||||
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
||||
"<meta charset=\"utf-8\"/>\n",
|
||||
"</head>\n",
|
||||
"<body" + additionalStyle + ">\n",
|
||||
"<div class=\"epub:type=bodymatter\">\n",
|
||||
"<img src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n",
|
||||
"</div>\n",
|
||||
"</body>\n</html>"
|
||||
])
|
||||
f.close()
|
||||
return path, imgfile
|
||||
|
||||
@@ -205,11 +223,9 @@ def buildNCX(dstdir, title, chapters, chapterNames):
|
||||
ncxfile = os.path.join(dstdir, 'OEBPS', 'toc.ncx')
|
||||
f = open(ncxfile, "w", encoding='UTF-8')
|
||||
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
|
||||
"<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\" ",
|
||||
"\"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n",
|
||||
"<ncx version=\"2005-1\" xml:lang=\"en-US\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n",
|
||||
"<head>\n",
|
||||
"<meta name=\"dtb:uid\" content=\"", options.uuid, "\"/>\n",
|
||||
"<meta name=\"dtb:uid\" content=\"urn:uuid:", options.uuid, "\"/>\n",
|
||||
"<meta name=\"dtb:depth\" content=\"1\"/>\n",
|
||||
"<meta name=\"dtb:totalPageCount\" content=\"0\"/>\n",
|
||||
"<meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n",
|
||||
@@ -227,13 +243,51 @@ def buildNCX(dstdir, title, chapters, chapterNames):
|
||||
navID = filename[0].replace('/', '_').replace('\\', '_')
|
||||
elif os.path.basename(folder) != "Text":
|
||||
title = chapterNames[os.path.basename(folder)]
|
||||
f.write("<navPoint id=\"" + navID + "\"><navLabel><text>"
|
||||
+ title + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/")
|
||||
+ ".html\"/></navPoint>\n")
|
||||
f.write("<navPoint id=\"" + navID + "\"><navLabel><text>" +
|
||||
title + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/") +
|
||||
".html\"/></navPoint>\n")
|
||||
f.write("</navMap>\n</ncx>")
|
||||
f.close()
|
||||
|
||||
|
||||
def buildNAV(dstdir, title, chapters, chapterNames):
|
||||
navfile = os.path.join(dstdir, 'OEBPS', 'nav.xhtml')
|
||||
f = open(navfile, "w", encoding='UTF-8')
|
||||
f.writelines(["<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
|
||||
"<!DOCTYPE html>\n",
|
||||
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
|
||||
"<head>\n",
|
||||
"<title>" + title + "</title>\n",
|
||||
"<meta charset=\"utf-8\"/>\n",
|
||||
"</head>\n",
|
||||
"<body>\n",
|
||||
"<nav xmlns:epub=\"http://www.idpf.org/2007/ops\" epub:type=\"toc\" id=\"toc\">\n",
|
||||
"<ol>\n"])
|
||||
for chapter in chapters:
|
||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||
if options.chapters:
|
||||
title = chapterNames[chapter[1]]
|
||||
elif os.path.basename(folder) != "Text":
|
||||
title = chapterNames[os.path.basename(folder)]
|
||||
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".html\">" + title + "</a></li>\n")
|
||||
f.writelines(["</ol>\n",
|
||||
"</nav>\n",
|
||||
"<nav epub:type=\"page-list\">\n",
|
||||
"<ol>\n"
|
||||
])
|
||||
for chapter in chapters:
|
||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||
if options.chapters:
|
||||
title = chapterNames[chapter[1]]
|
||||
elif os.path.basename(folder) != "Text":
|
||||
title = chapterNames[os.path.basename(folder)]
|
||||
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".html\">" + title + "</a></li>\n")
|
||||
f.write("</ol>\n</nav>\n</body>\n</html>")
|
||||
f.close()
|
||||
|
||||
|
||||
def buildOPF(dstdir, title, filelist, cover=None):
|
||||
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
|
||||
profilelabel, deviceres, palette, gamma, panelviewsize = options.profileData
|
||||
@@ -243,55 +297,60 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
writingmode = "horizontal-lr"
|
||||
f = open(opffile, "w", encoding='UTF-8')
|
||||
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
|
||||
"<package version=\"2.0\" unique-identifier=\"BookID\" ",
|
||||
"<package version=\"3.0\" unique-identifier=\"BookID\" ",
|
||||
"prefix=\"rendition: http://www.idpf.org/vocab/rendition/#\" ",
|
||||
"xmlns=\"http://www.idpf.org/2007/opf\">\n",
|
||||
"<metadata xmlns:opf=\"http://www.idpf.org/2007/opf\" ",
|
||||
"xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n",
|
||||
"<dc:title>", title, "</dc:title>\n",
|
||||
"<dc:language>en-US</dc:language>\n",
|
||||
"<dc:identifier id=\"BookID\" opf:scheme=\"UUID\">", options.uuid, "</dc:identifier>\n"])
|
||||
"<dc:identifier id=\"BookID\">urn:uuid:", options.uuid, "</dc:identifier>\n",
|
||||
"<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n",
|
||||
"<dc:description>", options.summary, "</dc:description>\n"])
|
||||
for author in options.authors:
|
||||
f.writelines(["<dc:creator>", author, "</dc:creator>\n"])
|
||||
f.writelines(["<meta name=\"generator\" content=\"KindleComicConverter-" + __version__ + "\"/>\n",
|
||||
"<meta name=\"RegionMagnification\" content=\"true\"/>\n",
|
||||
"<meta name=\"region-mag\" content=\"true\"/>\n",
|
||||
f.writelines(["<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n",
|
||||
"<meta name=\"cover\" content=\"cover\"/>\n",
|
||||
"<meta name=\"book-type\" content=\"comic\"/>\n",
|
||||
"<meta name=\"rendition:layout\" content=\"pre-paginated\"/>\n",
|
||||
"<meta name=\"zero-gutter\" content=\"true\"/>\n",
|
||||
"<meta name=\"zero-margin\" content=\"true\"/>\n",
|
||||
"<meta name=\"fixed-layout\" content=\"true\"/>\n"
|
||||
"<meta name=\"rendition:orientation\" content=\"portrait\"/>\n",
|
||||
"<meta name=\"orientation-lock\" content=\"portrait\"/>\n",
|
||||
"<meta name=\"original-resolution\" content=\"",
|
||||
str(deviceres[0]) + "x" + str(deviceres[1]), "\"/>\n",
|
||||
"<meta name=\"primary-writing-mode\" content=\"", writingmode, "\"/>\n",
|
||||
"<meta name=\"ke-border-color\" content=\"#ffffff\"/>\n",
|
||||
"<meta name=\"ke-border-width\" content=\"0\"/>\n",
|
||||
"</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ",
|
||||
"media-type=\"application/x-dtbncx+xml\"/>\n"])
|
||||
"<meta property=\"rendition:orientation\">portrait</meta>\n",
|
||||
"<meta property=\"rendition:spread\">portrait</meta>\n",
|
||||
"<meta property=\"rendition:layout\">pre-paginated</meta>\n"])
|
||||
if options.iskindle and options.profile != 'Custom':
|
||||
f.writelines(["<meta name=\"original-resolution\" content=\"",
|
||||
str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
|
||||
"<meta name=\"book-type\" content=\"comic\"/>\n",
|
||||
"<meta name=\"RegionMagnification\" content=\"true\"/>\n",
|
||||
"<meta name=\"primary-writing-mode\" content=\"" + writingmode + "\"/>\n",
|
||||
"<meta name=\"zero-gutter\" content=\"true\"/>\n",
|
||||
"<meta name=\"zero-margin\" content=\"true\"/>\n",
|
||||
"<meta name=\"ke-border-color\" content=\"#ffffff\"/>\n",
|
||||
"<meta name=\"ke-border-width\" content=\"0\"/>\n"])
|
||||
f.writelines(["</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ",
|
||||
"media-type=\"application/x-dtbncx+xml\"/>\n",
|
||||
"<item id=\"nav\" href=\"nav.xhtml\" ",
|
||||
"properties=\"nav\" media-type=\"application/xhtml+xml\"/>\n"])
|
||||
if cover is not None:
|
||||
filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\'))
|
||||
if '.png' == filename[1]:
|
||||
mt = 'image/png'
|
||||
else:
|
||||
mt = 'image/jpeg'
|
||||
f.write("<item id=\"cover\" href=\"Images/cover" + filename[1] + "\" media-type=\"" + mt + "\"/>\n")
|
||||
f.write("<item id=\"cover\" href=\"Images/cover" + filename[1] + "\" media-type=\"" + mt +
|
||||
"\" properties=\"cover-image\"/>\n")
|
||||
reflist = []
|
||||
for path in filelist:
|
||||
folder = path[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\').replace("\\", "/")
|
||||
filename = getImageFileName(path[1])
|
||||
uniqueid = os.path.join(folder, filename[0]).replace('/', '_').replace('\\', '_')
|
||||
reflist.append(uniqueid)
|
||||
f.write("<item id=\"page_" + str(uniqueid) + "\" href=\""
|
||||
+ folder.replace('Images', 'Text') + "/" + filename[0]
|
||||
+ ".html\" media-type=\"application/xhtml+xml\"/>\n")
|
||||
f.write("<item id=\"page_" + str(uniqueid) + "\" href=\"" +
|
||||
folder.replace('Images', 'Text') + "/" + filename[0] +
|
||||
".html\" media-type=\"application/xhtml+xml\"/>\n")
|
||||
if '.png' == filename[1]:
|
||||
mt = 'image/png'
|
||||
else:
|
||||
mt = 'image/jpeg'
|
||||
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\""
|
||||
+ mt + "\"/>\n")
|
||||
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
|
||||
mt + "\"/>\n")
|
||||
f.write("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n")
|
||||
if options.righttoleft:
|
||||
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
|
||||
@@ -299,7 +358,7 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
|
||||
for entry in reflist:
|
||||
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
|
||||
f.write("</spine>\n<guide>\n</guide>\n</package>\n")
|
||||
f.write("</spine>\n</package>\n")
|
||||
f.close()
|
||||
os.mkdir(os.path.join(dstdir, 'META-INF'))
|
||||
f = open(os.path.join(dstdir, 'META-INF', 'container.xml'), 'w', encoding='UTF-8')
|
||||
@@ -320,114 +379,126 @@ def buildEPUB(path, chapterNames, tomeNumber):
|
||||
_, deviceres, _, _, panelviewsize = options.profileData
|
||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w', encoding='UTF-8')
|
||||
# DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT.
|
||||
# Generic Panel View support + Margins fix for Non-Kindle devices.
|
||||
f.writelines(["@page {\n",
|
||||
"margin-bottom: 0;\n",
|
||||
"margin-top: 0\n",
|
||||
"}\n",
|
||||
"body {\n",
|
||||
"display: block;\n",
|
||||
"margin-bottom: 0;\n",
|
||||
"margin-left: 0;\n",
|
||||
"margin-right: 0;\n",
|
||||
"margin-top: 0;\n",
|
||||
"padding-bottom: 0;\n",
|
||||
"padding-left: 0;\n",
|
||||
"padding-right: 0;\n",
|
||||
"padding-top: 0;\n",
|
||||
"text-align: left\n",
|
||||
"}\n",
|
||||
"div.fs {\n",
|
||||
"height: ", str(deviceres[1]), "px;\n",
|
||||
"width: ", str(deviceres[0]), "px;\n",
|
||||
"position: relative;\n",
|
||||
"display: block;\n",
|
||||
"text-align: center\n",
|
||||
"}\n",
|
||||
"div.fs a {\n",
|
||||
"display: block;\n",
|
||||
"width : 100%;\n",
|
||||
"height: 100%;\n",
|
||||
"}\n",
|
||||
"div.fs div {\n",
|
||||
"position: absolute;\n",
|
||||
"}\n",
|
||||
"img.singlePage {\n",
|
||||
"position: absolute;\n",
|
||||
"height: ", str(deviceres[1]), "px;\n",
|
||||
"width: ", str(deviceres[0]), "px;\n",
|
||||
"}\n",
|
||||
"div.target-mag-parent {\n",
|
||||
"width:100%;\n",
|
||||
"height:100%;\n",
|
||||
"display:none;\n",
|
||||
"}\n",
|
||||
"div.target-mag {\n",
|
||||
"position: absolute;\n",
|
||||
"display: block;\n",
|
||||
"overflow: hidden;\n",
|
||||
"}\n",
|
||||
"div.target-mag img {\n",
|
||||
"position: absolute;\n",
|
||||
"height: ", str(panelviewsize[1]), "px;\n",
|
||||
"width: ", str(panelviewsize[0]), "px;\n",
|
||||
"}\n",
|
||||
"#Generic-Panel {\n",
|
||||
"top: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxC {\n",
|
||||
"top: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxT {\n",
|
||||
"top: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxB {\n",
|
||||
"bottom: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxL {\n",
|
||||
"left: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxR {\n",
|
||||
"right: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxTL {\n",
|
||||
"top: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxTR {\n",
|
||||
"top: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxBL {\n",
|
||||
"bottom: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxBR {\n",
|
||||
"bottom: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}",
|
||||
])
|
||||
if options.iskindle:
|
||||
f.writelines(["@page {\n",
|
||||
"margin-bottom: 0;\n",
|
||||
"margin-top: 0\n",
|
||||
"}\n",
|
||||
"body {\n",
|
||||
"display: block;\n",
|
||||
"margin-bottom: 0;\n",
|
||||
"margin-left: 0;\n",
|
||||
"margin-right: 0;\n",
|
||||
"margin-top: 0;\n",
|
||||
"padding-bottom: 0;\n",
|
||||
"padding-left: 0;\n",
|
||||
"padding-right: 0;\n",
|
||||
"padding-top: 0;\n",
|
||||
"text-align: left\n",
|
||||
"}\n",
|
||||
"div.fs {\n",
|
||||
"height: ", str(deviceres[1]), "px;\n",
|
||||
"width: ", str(deviceres[0]), "px;\n",
|
||||
"position: relative;\n",
|
||||
"display: block;\n",
|
||||
"text-align: center\n",
|
||||
"}\n",
|
||||
"div.fs a {\n",
|
||||
"display: block;\n",
|
||||
"width : 100%;\n",
|
||||
"height: 100%;\n",
|
||||
"}\n",
|
||||
"div.fs div {\n",
|
||||
"position: absolute;\n",
|
||||
"}\n",
|
||||
"img.singlePage {\n",
|
||||
"position: absolute;\n",
|
||||
"height: ", str(deviceres[1]), "px;\n",
|
||||
"width: ", str(deviceres[0]), "px;\n",
|
||||
"}\n",
|
||||
"div.target-mag-parent {\n",
|
||||
"width:100%;\n",
|
||||
"height:100%;\n",
|
||||
"display:none;\n",
|
||||
"}\n",
|
||||
"div.target-mag {\n",
|
||||
"position: absolute;\n",
|
||||
"display: block;\n",
|
||||
"overflow: hidden;\n",
|
||||
"}\n",
|
||||
"div.target-mag img {\n",
|
||||
"position: absolute;\n",
|
||||
"height: ", str(panelviewsize[1]), "px;\n",
|
||||
"width: ", str(panelviewsize[0]), "px;\n",
|
||||
"}\n",
|
||||
"#Generic-Panel {\n",
|
||||
"top: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxC {\n",
|
||||
"top: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxT {\n",
|
||||
"top: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxB {\n",
|
||||
"bottom: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxL {\n",
|
||||
"left: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxR {\n",
|
||||
"right: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxTL {\n",
|
||||
"top: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxTR {\n",
|
||||
"top: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxBL {\n",
|
||||
"bottom: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxBR {\n",
|
||||
"bottom: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}",
|
||||
])
|
||||
else:
|
||||
f.writelines([
|
||||
"@namespace epub \"http://www.idpf.org/2007/ops\";\n",
|
||||
"@charset \"UTF-8\";\n",
|
||||
"body {\n",
|
||||
"margin: 0;\n",
|
||||
"}\n",
|
||||
"img {\n",
|
||||
"position: absolute;\n",
|
||||
"margin: 0;\n",
|
||||
"z-index: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"}"])
|
||||
f.close()
|
||||
for (dirpath, dirnames, filenames) in walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||
chapter = False
|
||||
@@ -446,7 +517,7 @@ def buildEPUB(path, chapterNames, tomeNumber):
|
||||
image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options, tomeNumber)
|
||||
# Hack that force Panel View on at last one page
|
||||
if lastfile and not options.panelviewused and 'Ko' not in options.profile \
|
||||
and options.profile not in ['K1', 'K2', 'KDX', 'OTHER']:
|
||||
and options.profile not in ['K1', 'K2', 'KDX', 'Custom']:
|
||||
filelist[-1] = buildHTML(lastfile[0], lastfile[1], lastfile[2], True)
|
||||
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
|
||||
if not chapterNames and options.chapters:
|
||||
@@ -464,6 +535,7 @@ def buildEPUB(path, chapterNames, tomeNumber):
|
||||
chapterNames[filename] = aChapter[1]
|
||||
globaldiff = pageid - (aChapter[0] + globaldiff)
|
||||
buildNCX(path, options.title, chapterlist, chapterNames)
|
||||
buildNAV(path, options.title, chapterlist, chapterNames)
|
||||
buildOPF(path, options.title, filelist, cover)
|
||||
|
||||
|
||||
@@ -576,51 +648,45 @@ def imgFileProcessing(work):
|
||||
|
||||
|
||||
def getWorkFolder(afile):
|
||||
if len(afile) > 240:
|
||||
raise UserWarning("Path is too long.")
|
||||
if os.path.isdir(afile):
|
||||
workdir = mkdtemp('', 'KCC-')
|
||||
try:
|
||||
os.rmdir(workdir)
|
||||
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
||||
if len(fullPath) > 240:
|
||||
raise UserWarning("Path is too long.")
|
||||
copytree(afile, fullPath)
|
||||
sanitizePermissions(fullPath)
|
||||
return workdir
|
||||
except OSError:
|
||||
except:
|
||||
rmtree(workdir, True)
|
||||
raise
|
||||
raise UserWarning("Failed to prepare a workspace.")
|
||||
elif afile.lower().endswith('.pdf'):
|
||||
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
||||
path, njpg = pdf.extract()
|
||||
if njpg == 0:
|
||||
rmtree(path, True)
|
||||
raise UserWarning("Failed to extract images.")
|
||||
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 OSError:
|
||||
except:
|
||||
rmtree(workdir, True)
|
||||
raise UserWarning("Failed to extract file.")
|
||||
raise UserWarning("Failed to extract archive.")
|
||||
else:
|
||||
rmtree(workdir, True)
|
||||
raise TypeError
|
||||
if len(os.path.join(path, 'OEBPS', 'Images')) > 240:
|
||||
raise UserWarning("Path is too long.")
|
||||
move(path, path + "_temp")
|
||||
move(path + "_temp", os.path.join(path, 'OEBPS', 'Images'))
|
||||
return path
|
||||
raise UserWarning("Failed to detect archive format.")
|
||||
newpath = mkdtemp('', 'KCC-')
|
||||
move(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
||||
return newpath
|
||||
|
||||
|
||||
def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
|
||||
if srcpath[-1] == os.path.sep:
|
||||
srcpath = srcpath[:-1]
|
||||
if not ext.startswith('.'):
|
||||
ext = '.' + ext
|
||||
if 'Ko' in options.profile and options.format == 'EPUB':
|
||||
ext = '.kepub.epub'
|
||||
if wantedname is not None:
|
||||
if wantedname.endswith(ext):
|
||||
filename = os.path.abspath(wantedname)
|
||||
@@ -632,7 +698,14 @@ def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
|
||||
elif os.path.isdir(srcpath):
|
||||
filename = srcpath + tomeNumber + ext
|
||||
else:
|
||||
filename = os.path.splitext(srcpath)[0] + tomeNumber + ext
|
||||
if 'Ko' in options.profile and options.format == 'EPUB':
|
||||
path = srcpath.split(os.path.sep)
|
||||
path[-1] = ''.join(e for e in path[-1].split('.')[0] if e.isalnum()) + tomeNumber + ext
|
||||
if not path[-1].split('.')[0]:
|
||||
path[-1] = 'KCCPlaceholder' + tomeNumber + ext
|
||||
filename = os.path.sep.join(path)
|
||||
else:
|
||||
filename = os.path.splitext(srcpath)[0] + tomeNumber + ext
|
||||
if os.path.isfile(filename):
|
||||
counter = 0
|
||||
basename = os.path.splitext(filename)[0]
|
||||
@@ -647,6 +720,7 @@ def getComicInfo(path, originalPath):
|
||||
options.authors = ['KCC']
|
||||
options.remoteCovers = {}
|
||||
options.chapters = []
|
||||
options.summary = ''
|
||||
titleSuffix = ''
|
||||
if options.title == 'defaulttitle':
|
||||
defaultTitle = True
|
||||
@@ -665,7 +739,7 @@ def getComicInfo(path, originalPath):
|
||||
options.authors = []
|
||||
if defaultTitle:
|
||||
if xml.data['Series']:
|
||||
options.title = xml.data['Series']
|
||||
options.title = escape(xml.data['Series'])
|
||||
if xml.data['Volume']:
|
||||
titleSuffix += ' V' + xml.data['Volume']
|
||||
if xml.data['Number']:
|
||||
@@ -673,7 +747,7 @@ def getComicInfo(path, originalPath):
|
||||
options.title += titleSuffix
|
||||
for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']:
|
||||
for person in xml.data[field]:
|
||||
options.authors.append(person)
|
||||
options.authors.append(escape(person))
|
||||
if len(options.authors) > 0:
|
||||
options.authors = list(set(options.authors))
|
||||
options.authors.sort()
|
||||
@@ -683,6 +757,8 @@ def getComicInfo(path, originalPath):
|
||||
options.remoteCovers = getCoversFromMCB(xml.data['MUid'])
|
||||
if xml.data['Bookmarks']:
|
||||
options.chapters = xml.data['Bookmarks']
|
||||
if xml.data['Summary']:
|
||||
options.summary = escape(xml.data['Summary'])
|
||||
os.remove(xmlPath)
|
||||
|
||||
|
||||
@@ -885,6 +961,8 @@ def splitProcess(path, mode):
|
||||
|
||||
|
||||
def detectCorruption(tmpPath, orgPath):
|
||||
imageNumber = 0
|
||||
imageSmaller = 0
|
||||
for root, dirs, files in walk(tmpPath, False):
|
||||
for name in files:
|
||||
if getImageFileName(name) is not None:
|
||||
@@ -898,14 +976,24 @@ def detectCorruption(tmpPath, orgPath):
|
||||
img.verify()
|
||||
img = Image.open(path)
|
||||
img.load()
|
||||
imageNumber += 1
|
||||
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
|
||||
imageSmaller += 1
|
||||
except Exception as err:
|
||||
rmtree(os.path.join(tmpPath, '..', '..'), True)
|
||||
if 'decoder' in err and 'not available' in err:
|
||||
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)
|
||||
else:
|
||||
os.remove(os.path.join(root, name))
|
||||
if imageSmaller > imageNumber * 0.5 and not options.upscale and not options.stretch:
|
||||
print("\nMore than half of images are smaller than target device resolution. "
|
||||
"Consider enabling stretching or upscaling to improve readability.")
|
||||
if GUI:
|
||||
GUI.addMessage.emit('More than half of images are smaller than target device resolution.', 'warning', False)
|
||||
GUI.addMessage.emit('Consider enabling stretching or upscaling to improve readability.', 'warning', False)
|
||||
GUI.addMessage.emit('', '', False)
|
||||
|
||||
|
||||
def detectMargins(path):
|
||||
@@ -946,7 +1034,7 @@ def createNewTome():
|
||||
|
||||
def slugify(value):
|
||||
value = slugifyExt(value)
|
||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value))
|
||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||
return value
|
||||
|
||||
|
||||
@@ -976,8 +1064,8 @@ def makeParser():
|
||||
otherOptions = OptionGroup(psr, "OTHER")
|
||||
|
||||
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV",
|
||||
help="Device profile (Available options: K1, K2, K345, KDX, KPW, KV, KFHD, KFHDX, KFHDX8,"
|
||||
" KFA, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O) [Default=KV]")
|
||||
help="Device profile (Available options: K1, K2, K345, KDX, KPW, KV, KoMT, KoG, KoGHD,"
|
||||
" KoA, KoAHD, KoAH2O) [Default=KV]")
|
||||
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,
|
||||
@@ -1037,14 +1125,17 @@ def checkOptions():
|
||||
global options
|
||||
options.panelview = True
|
||||
options.panelviewused = False
|
||||
options.iskindle = False
|
||||
options.bordersColor = None
|
||||
if options.format == 'Auto':
|
||||
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'KFHD', 'KFHDX', 'KFHDX8', 'KFA']:
|
||||
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV']:
|
||||
options.format = 'MOBI'
|
||||
elif options.profile in ['Other']:
|
||||
elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O']:
|
||||
options.format = 'EPUB'
|
||||
elif options.profile in ['KDX', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O']:
|
||||
elif options.profile in ['KDX']:
|
||||
options.format = 'CBZ'
|
||||
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'OTHER']:
|
||||
options.iskindle = True
|
||||
if options.white_borders:
|
||||
options.bordersColor = 'white'
|
||||
if options.black_borders:
|
||||
@@ -1052,11 +1143,6 @@ def checkOptions():
|
||||
# Splitting MOBI is not optional
|
||||
if options.format == 'MOBI':
|
||||
options.batchsplit = True
|
||||
# Disabling grayscale conversion for Kindle Fire family.
|
||||
if 'KFH' in options.profile or options.forcecolor:
|
||||
options.forcecolor = True
|
||||
else:
|
||||
options.forcecolor = False
|
||||
# Older Kindle don't need higher resolution files due lack of Panel View.
|
||||
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX':
|
||||
options.quality = 0
|
||||
@@ -1075,10 +1161,6 @@ def checkOptions():
|
||||
# Kobo models can't use ultra quality mode
|
||||
if options.quality == 2:
|
||||
options.quality = 1
|
||||
# Kindle for Android profile require target resolution.
|
||||
if options.profile == 'KFA' and (options.customwidth == 0 or options.customheight == 0):
|
||||
print("ERROR: Kindle for Android profile require --customwidth and --customheight options!")
|
||||
sys.exit(1)
|
||||
# CBZ files on Kindle DX/DXG support higher resolution
|
||||
if options.profile == 'KDX' and options.format == 'CBZ':
|
||||
options.customheight = 1200
|
||||
@@ -1094,7 +1176,7 @@ def checkOptions():
|
||||
if options.customheight != 0:
|
||||
Y = options.customheight
|
||||
newProfile = ("Custom", (int(X), int(Y)), image.ProfileData.Palette16,
|
||||
image.ProfileData.Profiles[options.profile][3], (int(int(X)*1.5), int(int(Y)*1.5)))
|
||||
image.ProfileData.Profiles[options.profile][3], (int(int(X) * 1.5), int(int(Y) * 1.5)))
|
||||
image.ProfileData.Profiles["Custom"] = newProfile
|
||||
options.profile = "Custom"
|
||||
options.profileData = image.ProfileData.Profiles[options.profile]
|
||||
@@ -1121,6 +1203,21 @@ def checkTools(source):
|
||||
exit(1)
|
||||
|
||||
|
||||
def checkPre(source):
|
||||
# Make sure that all temporary files are gone
|
||||
for root, dirs, _ in walkLevel(gettempdir(), 0):
|
||||
for tempdir in dirs:
|
||||
if tempdir.startswith('KCC-'):
|
||||
rmtree(os.path.join(root, tempdir), True)
|
||||
# Make sure that target directory is writable
|
||||
if os.path.isdir(source):
|
||||
writable = os.access(os.path.abspath(os.path.join(source, '..')), os.W_OK)
|
||||
else:
|
||||
writable = os.access(os.path.dirname(source), os.W_OK)
|
||||
if not writable:
|
||||
raise UserWarning("Target directory is not writable.")
|
||||
|
||||
|
||||
def makeBook(source, qtGUI=None):
|
||||
"""Generates MOBI/EPUB/CBZ comic ebook from a bunch of images."""
|
||||
global GUI
|
||||
@@ -1129,6 +1226,7 @@ def makeBook(source, qtGUI=None):
|
||||
GUI.progressBarTick.emit('1')
|
||||
else:
|
||||
checkTools(source)
|
||||
checkPre(source)
|
||||
path = getWorkFolder(source)
|
||||
print("\nChecking images...")
|
||||
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
|
||||
@@ -1211,7 +1309,7 @@ def makeMOBIFix(item):
|
||||
mobiPath = item.replace('.epub', '.mobi')
|
||||
move(mobiPath, mobiPath + '_toclean')
|
||||
try:
|
||||
dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(str(uuid4()), 'UTF-8'))
|
||||
dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(options.uuid, 'UTF-8'))
|
||||
return [True]
|
||||
except Exception as err:
|
||||
return [False, format(err)]
|
||||
@@ -1261,7 +1359,7 @@ def makeMOBI(work, qtGUI=None):
|
||||
global GUI, makeMOBIWorkerPool, makeMOBIWorkerOutput
|
||||
GUI = qtGUI
|
||||
makeMOBIWorkerOutput = []
|
||||
availableMemory = virtual_memory().total/1000000000
|
||||
availableMemory = virtual_memory().total / 1000000000
|
||||
if availableMemory <= 2:
|
||||
threadNumber = 1
|
||||
elif 2 < availableMemory <= 4:
|
||||
|
||||
@@ -85,19 +85,19 @@ def sanitizePanelSize(panel, opt):
|
||||
newPanels = []
|
||||
if panel[2] > 6 * opt.height:
|
||||
diff = int(panel[2] / 8)
|
||||
newPanels.append([panel[0], panel[1] - diff*7, diff])
|
||||
newPanels.append([panel[1] - diff*7, panel[1] - diff*6, diff])
|
||||
newPanels.append([panel[1] - diff*6, panel[1] - diff*5, diff])
|
||||
newPanels.append([panel[1] - diff*5, panel[1] - diff*4, diff])
|
||||
newPanels.append([panel[1] - diff*4, panel[1] - diff*3, diff])
|
||||
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff])
|
||||
newPanels.append([panel[1] - diff*2, panel[1] - diff, diff])
|
||||
newPanels.append([panel[0], panel[1] - diff * 7, diff])
|
||||
newPanels.append([panel[1] - diff * 7, panel[1] - diff * 6, diff])
|
||||
newPanels.append([panel[1] - diff * 6, panel[1] - diff * 5, diff])
|
||||
newPanels.append([panel[1] - diff * 5, panel[1] - diff * 4, diff])
|
||||
newPanels.append([panel[1] - diff * 4, panel[1] - diff * 3, diff])
|
||||
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
|
||||
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
|
||||
newPanels.append([panel[1] - diff, panel[1], diff])
|
||||
elif panel[2] > 3 * opt.height:
|
||||
diff = int(panel[2] / 4)
|
||||
newPanels.append([panel[0], panel[1] - diff*3, diff])
|
||||
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff])
|
||||
newPanels.append([panel[1] - diff*2, panel[1] - diff, diff])
|
||||
newPanels.append([panel[0], panel[1] - diff * 3, diff])
|
||||
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
|
||||
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
|
||||
newPanels.append([panel[1] - diff, panel[1], diff])
|
||||
elif panel[2] > 1.5 * opt.height:
|
||||
newPanels.append([panel[0], panel[1] - int(panel[2] / 2), int(panel[2] / 2)])
|
||||
@@ -297,4 +297,4 @@ def main(argv=None, qtGUI=None):
|
||||
else:
|
||||
raise UserWarning("Provided path is not a directory.")
|
||||
else:
|
||||
raise UserWarning("Target height is not set.")
|
||||
raise UserWarning("Target height is not set.")
|
||||
|
||||
@@ -36,15 +36,15 @@ title_offset = 84
|
||||
|
||||
|
||||
def getint(data, ofs, sz='L'):
|
||||
i, = struct.unpack_from('>'+sz, data, ofs)
|
||||
i, = struct.unpack_from('>' + sz, data, ofs)
|
||||
return i
|
||||
|
||||
|
||||
def writeint(data, ofs, n, slen='L'):
|
||||
if slen == 'L':
|
||||
return data[:ofs]+struct.pack('>L', n)+data[ofs+4:]
|
||||
return data[:ofs] + struct.pack('>L', n) + data[ofs + 4:]
|
||||
else:
|
||||
return data[:ofs]+struct.pack('>H', n)+data[ofs+2:]
|
||||
return data[:ofs] + struct.pack('>H', n) + data[ofs + 2:]
|
||||
|
||||
|
||||
def getsecaddr(datain, secno):
|
||||
@@ -52,11 +52,11 @@ def getsecaddr(datain, secno):
|
||||
if (secno < 0) | (secno >= nsec):
|
||||
emsg = 'requested section number %d out of range (nsec=%d)' % (secno, nsec)
|
||||
raise DualMetaFixException(emsg)
|
||||
secstart = getint(datain, first_pdb_record+secno*8)
|
||||
if secno == nsec-1:
|
||||
secstart = getint(datain, first_pdb_record + secno * 8)
|
||||
if secno == nsec - 1:
|
||||
secend = len(datain)
|
||||
else:
|
||||
secend = getint(datain, first_pdb_record+(secno+1)*8)
|
||||
secend = getint(datain, first_pdb_record + (secno + 1) * 8)
|
||||
return secstart, secend
|
||||
|
||||
|
||||
@@ -71,28 +71,28 @@ def replacesection(datain, secno, secdata):
|
||||
seclen = secend - secstart
|
||||
if len(secdata) != seclen:
|
||||
raise DualMetaFixException('section length change in replacesection')
|
||||
datain[secstart:secstart+seclen] = secdata
|
||||
datain[secstart:secstart + seclen] = secdata
|
||||
|
||||
|
||||
def get_exth_params(rec0):
|
||||
ebase = mobi_header_base + getint(rec0, mobi_header_length)
|
||||
if rec0[ebase:ebase+4] != b'EXTH':
|
||||
if rec0[ebase:ebase + 4] != b'EXTH':
|
||||
raise DualMetaFixException('EXTH tag not found where expected')
|
||||
elen = getint(rec0, ebase+4)
|
||||
enum = getint(rec0, ebase+8)
|
||||
elen = getint(rec0, ebase + 4)
|
||||
enum = getint(rec0, ebase + 8)
|
||||
rlen = len(rec0)
|
||||
return ebase, elen, enum, rlen
|
||||
|
||||
|
||||
def add_exth(rec0, exth_num, exth_bytes):
|
||||
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||
newrecsize = 8+len(exth_bytes)
|
||||
newrec0 = rec0[0:ebase+4]+struct.pack('>L', elen+newrecsize)+struct.pack('>L', enum+1)+struct.pack('>L', exth_num)\
|
||||
+ struct.pack('>L', newrecsize)+exth_bytes+rec0[ebase+12:]
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)+newrecsize)
|
||||
newrecsize = 8 + len(exth_bytes)
|
||||
newrec0 = rec0[0:ebase + 4] + struct.pack('>L', elen + newrecsize) + struct.pack('>L', enum + 1) + \
|
||||
struct.pack('>L', exth_num) + struct.pack('>L', newrecsize) + exth_bytes + rec0[ebase + 12:]
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset) + newrecsize)
|
||||
# keep constant record length by removing newrecsize null bytes from end
|
||||
sectail = newrec0[-newrecsize:]
|
||||
if sectail != b'\0'*newrecsize:
|
||||
if sectail != b'\0' * newrecsize:
|
||||
raise DualMetaFixException('add_exth: trimmed non-null bytes at end of section')
|
||||
newrec0 = newrec0[0:rlen]
|
||||
return newrec0
|
||||
@@ -106,30 +106,31 @@ def read_exth(rec0, exth_num):
|
||||
exth_id = getint(rec0, ebase)
|
||||
if exth_id == exth_num:
|
||||
# We might have multiple exths, so build a list.
|
||||
exth_values.append(rec0[ebase+8:ebase+getint(rec0, ebase+4)])
|
||||
exth_values.append(rec0[ebase + 8:ebase + getint(rec0, ebase + 4)])
|
||||
enum -= 1
|
||||
ebase = ebase+getint(rec0, ebase+4)
|
||||
ebase = ebase + getint(rec0, ebase + 4)
|
||||
return exth_values
|
||||
|
||||
|
||||
def del_exth(rec0, exth_num):
|
||||
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||
ebase_idx = ebase+12
|
||||
ebase_idx = ebase + 12
|
||||
enum_idx = 0
|
||||
while enum_idx < enum:
|
||||
exth_id = getint(rec0, ebase_idx)
|
||||
exth_size = getint(rec0, ebase_idx+4)
|
||||
exth_size = getint(rec0, ebase_idx + 4)
|
||||
if exth_id == exth_num:
|
||||
newrec0 = rec0
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)-exth_size)
|
||||
newrec0 = newrec0[:ebase_idx]+newrec0[ebase_idx+exth_size:]
|
||||
newrec0 = newrec0[0:ebase+4]+struct.pack('>L', elen-exth_size)+struct.pack('>L', enum-1)+newrec0[ebase+12:]
|
||||
newrec0 += b'\0'*exth_size
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset) - exth_size)
|
||||
newrec0 = newrec0[:ebase_idx] + newrec0[ebase_idx + exth_size:]
|
||||
newrec0 = newrec0[0:ebase + 4] + struct.pack('>L', elen - exth_size) + \
|
||||
struct.pack('>L', enum - 1) + newrec0[ebase + 12:]
|
||||
newrec0 += b'\0' * exth_size
|
||||
if rlen != len(newrec0):
|
||||
raise DualMetaFixException('del_exth: incorrect section size change')
|
||||
return newrec0
|
||||
enum_idx += 1
|
||||
ebase_idx = ebase_idx+exth_size
|
||||
ebase_idx = ebase_idx + exth_size
|
||||
return rec0
|
||||
|
||||
|
||||
@@ -181,4 +182,4 @@ class DualMobiMetaFix:
|
||||
replacesection(self.datain, datain_kf8, rec0)
|
||||
|
||||
self.datain.flush()
|
||||
self.datain.close()
|
||||
self.datain.close()
|
||||
|
||||
64
kcc/image.py
64
kcc/image.py
@@ -82,12 +82,8 @@ class ProfileData:
|
||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)),
|
||||
'K345': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)),
|
||||
'KPW': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.8, (1608, 2172)),
|
||||
'KFHD': ("K. Fire HD", (800, 1280), PalleteNull, 1.0, (1200, 1920)),
|
||||
'KFHDX': ("K. Fire HDX", (1200, 1920), PalleteNull, 1.0, (1800, 2880)),
|
||||
'KFHDX8': ("K. Fire HDX 8.9", (1600, 2560), PalleteNull, 1.0, (2400, 3840)),
|
||||
'KFA': ("Kindle for Android", (0, 0), PalleteNull, 1.0, (0, 0)),
|
||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KV': ("Kindle Paperwhite 3/Voyage", (1072, 1448), Palette16, 1.8, (1608, 2172)),
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8, (1152, 1536)),
|
||||
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8, (1608, 2172)),
|
||||
@@ -152,8 +148,8 @@ class ComicPage:
|
||||
if self.noVPV:
|
||||
flags.append('NoVerticalPanelView')
|
||||
if self.border:
|
||||
flags.append('Margins-' + str(self.border[0]) + '-' + str(self.border[1]) + '-'
|
||||
+ str(self.border[2]) + '-' + str(self.border[3]))
|
||||
flags.append('Margins-' + str(self.border[0]) + '-' + str(self.border[1]) + '-' +
|
||||
str(self.border[2]) + '-' + str(self.border[3]))
|
||||
if self.fill != 'white':
|
||||
flags.append('BlackFill')
|
||||
if self.opt.quality == 2:
|
||||
@@ -203,10 +199,10 @@ class ComicPage:
|
||||
else:
|
||||
multiplier = 1.5
|
||||
if border is not None:
|
||||
self.border = [round(float(border[0])/float(self.image.size[0])*150, 3),
|
||||
round(float(border[1])/float(self.image.size[1])*150, 3),
|
||||
round(float(self.image.size[0]-border[2])/float(self.image.size[0])*150, 3),
|
||||
round(float(self.image.size[1]-border[3])/float(self.image.size[1])*150, 3)]
|
||||
self.border = [round(float(border[0]) / float(self.image.size[0]) * 150, 3),
|
||||
round(float(border[1]) / float(self.image.size[1]) * 150, 3),
|
||||
round(float(self.image.size[0] - border[2]) / float(self.image.size[0]) * 150, 3),
|
||||
round(float(self.image.size[1] - border[3]) / float(self.image.size[1]) * 150, 3)]
|
||||
if int((border[2] - border[0]) * multiplier) < self.size[0] + 10:
|
||||
self.noHPV = True
|
||||
if int((border[3] - border[1]) * multiplier) < self.size[1] + 10:
|
||||
@@ -432,13 +428,13 @@ class ComicPage:
|
||||
while startY < bw.size[1]:
|
||||
if startY + 5 > bw.size[1]:
|
||||
startY = bw.size[1] - 5
|
||||
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY+5)))
|
||||
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY + 5)))
|
||||
startY += 5
|
||||
startX = 0
|
||||
while startX < bw.size[0]:
|
||||
if startX + 5 > bw.size[0]:
|
||||
startX = bw.size[0] - 5
|
||||
fill += self.getImageHistogram(bw.crop((startX, 0, startX+5, bw.size[1])))
|
||||
fill += self.getImageHistogram(bw.crop((startX, 0, startX + 5, bw.size[1])))
|
||||
startX += 5
|
||||
if fill > 0:
|
||||
self.fill = 'black'
|
||||
@@ -446,29 +442,25 @@ class ComicPage:
|
||||
self.fill = 'white'
|
||||
|
||||
def isImageColor(self):
|
||||
v = ImageStat.Stat(self.image).var
|
||||
isMonochromatic = reduce(lambda x, y: x and y < 0.005, v, True)
|
||||
if isMonochromatic:
|
||||
# Monochromatic
|
||||
return False
|
||||
else:
|
||||
if len(v) == 3:
|
||||
maxmin = abs(max(v) - min(v))
|
||||
if maxmin > 1000:
|
||||
# Color
|
||||
return True
|
||||
elif maxmin > 100:
|
||||
# Probably color
|
||||
return True
|
||||
else:
|
||||
# Grayscale
|
||||
return False
|
||||
elif len(v) == 1:
|
||||
# Black and white
|
||||
img = self.image.copy()
|
||||
bands = img.getbands()
|
||||
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
|
||||
thumb = img.resize((40, 40))
|
||||
SSE, bias = 0, [0, 0, 0]
|
||||
bias = ImageStat.Stat(thumb).mean[:3]
|
||||
bias = [b - sum(bias) / 3 for b in bias]
|
||||
for pixel in thumb.getdata():
|
||||
mu = sum(pixel) / 3
|
||||
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
|
||||
MSE = float(SSE) / (40 * 40)
|
||||
if MSE <= 22:
|
||||
return False
|
||||
else:
|
||||
# Detection failed
|
||||
return False
|
||||
return True
|
||||
elif len(bands) == 1:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class Cover:
|
||||
@@ -517,4 +509,4 @@ class Cover:
|
||||
try:
|
||||
self.image.save(self.target, "JPEG", optimize=1, quality=80)
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to save cover')
|
||||
raise RuntimeError('Failed to process downloaded cover.')
|
||||
|
||||
@@ -38,6 +38,7 @@ class MetadataParser:
|
||||
'Pencillers': [],
|
||||
'Inkers': [],
|
||||
'Colorists': [],
|
||||
'Summary': '',
|
||||
'MUid': '',
|
||||
'Bookmarks': []}
|
||||
self.rawdata = None
|
||||
@@ -90,6 +91,8 @@ class MetadataParser:
|
||||
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
|
||||
if len(self.rawdata.getElementsByTagName('Number')) != 0:
|
||||
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
|
||||
if len(self.rawdata.getElementsByTagName('Summary')) != 0:
|
||||
self.data['Summary'] = self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue
|
||||
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
|
||||
if len(self.rawdata.getElementsByTagName(field)) != 0:
|
||||
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
|
||||
@@ -113,7 +116,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'])],
|
||||
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']],
|
||||
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
|
||||
if self.rawdata.getElementsByTagName(row[0]):
|
||||
node = self.rawdata.getElementsByTagName(row[0])[0]
|
||||
@@ -135,7 +138,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'])],
|
||||
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']],
|
||||
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
|
||||
if row[1]:
|
||||
main = doc.createElement(row[0])
|
||||
|
||||
@@ -360,9 +360,8 @@ class RarCannotExec(RarExecError):
|
||||
|
||||
def is_rarfile(xfile):
|
||||
'''Check quickly whether file is rar archive.'''
|
||||
fd = XFile(xfile)
|
||||
buf = fd.read(len(RAR_ID))
|
||||
fd.close()
|
||||
with open(xfile, 'rb') as fh:
|
||||
buf = fh.read(len(RAR_ID))
|
||||
if buf == RAR_ID or buf == RAR5_ID:
|
||||
return True
|
||||
else:
|
||||
|
||||
@@ -25,6 +25,7 @@ from shutil import rmtree, move
|
||||
from tempfile import mkdtemp
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
from re import split
|
||||
from traceback import format_tb
|
||||
try:
|
||||
from scandir import walk
|
||||
except ImportError:
|
||||
@@ -45,6 +46,9 @@ class HTMLStripper(HTMLParser):
|
||||
def get_data(self):
|
||||
return ''.join(self.fed)
|
||||
|
||||
def error(self, message):
|
||||
pass
|
||||
|
||||
|
||||
def getImageFileName(imgfile):
|
||||
name, ext = os.path.splitext(imgfile)
|
||||
@@ -117,41 +121,47 @@ def removeFromZIP(zipfname, *filenames):
|
||||
rmtree(tempdir)
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def sanitizeTrace(traceback):
|
||||
return ''.join(format_tb(traceback))\
|
||||
.replace('C:\\Users\\pawel\\Documents\\Projekty\\KCC\\', '')\
|
||||
.replace('C:\\Python34\\', '')\
|
||||
.replace('C:\\Python34_64\\', '')
|
||||
|
||||
|
||||
def dependencyCheck(level):
|
||||
missing = []
|
||||
if level > 2:
|
||||
try:
|
||||
from PyQt5.QtCore import qVersion as qtVersion
|
||||
if StrictVersion('5.2.0') > StrictVersion(qtVersion()):
|
||||
missing.append('PyQt 5.2.0+')
|
||||
if StrictVersion('5.4.0') > StrictVersion(qtVersion()):
|
||||
missing.append('PyQt 5.4.0+')
|
||||
except ImportError:
|
||||
missing.append('PyQt 5.2.0+')
|
||||
missing.append('PyQt 5.4.0+')
|
||||
if level > 1:
|
||||
try:
|
||||
from psutil import __version__ as psutilVersion
|
||||
if StrictVersion('2.0.0') > StrictVersion(psutilVersion):
|
||||
missing.append('psutil 2.0.0+')
|
||||
if StrictVersion('3.0.0') > StrictVersion(psutilVersion):
|
||||
missing.append('psutil 3.0.0+')
|
||||
except ImportError:
|
||||
missing.append('psutil 2.0.0+')
|
||||
missing.append('psutil 3.0.0+')
|
||||
try:
|
||||
from slugify import __version__ as slugifyVersion
|
||||
if StrictVersion('0.1.0') > StrictVersion(slugifyVersion):
|
||||
missing.append('python-slugify 0.1.0+')
|
||||
if StrictVersion('1.1.3') > StrictVersion(slugifyVersion):
|
||||
missing.append('python-slugify 1.1.3+')
|
||||
except ImportError:
|
||||
missing.append('python-slugify 0.1.0+')
|
||||
missing.append('python-slugify 1.1.3+')
|
||||
try:
|
||||
from PIL import PILLOW_VERSION as pillowVersion
|
||||
if StrictVersion('2.7.0') > StrictVersion(pillowVersion):
|
||||
missing.append('Pillow 2.7.0+')
|
||||
if StrictVersion('2.8.2') > StrictVersion(pillowVersion):
|
||||
missing.append('Pillow 2.8.2+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 2.7.0+')
|
||||
missing.append('Pillow 2.8.2+')
|
||||
try:
|
||||
from scandir import __version__ as scandirVersion
|
||||
if StrictVersion('0.9') > StrictVersion(scandirVersion):
|
||||
missing.append('scandir 0.9+')
|
||||
if StrictVersion('1.1') > StrictVersion(scandirVersion):
|
||||
missing.append('scandir 1.1+')
|
||||
except ImportError:
|
||||
missing.append('scandir 0.9+')
|
||||
missing.append('scandir 1.1+')
|
||||
if len(missing) > 0:
|
||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||
exit(1)
|
||||
exit(1)
|
||||
|
||||
10
setup.json
Normal file
10
setup.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"title": "Kindle Comic Converter",
|
||||
"icon": "icons/comic2ebook.icns",
|
||||
"background": "icons/WizardOSX.png",
|
||||
"icon-size": 160,
|
||||
"contents": [
|
||||
{ "x": 180, "y": 300, "type": "file", "path": "dist/Kindle Comic Converter.app" },
|
||||
{ "x": 520, "y": 300, "type": "link", "path": "/Applications" }
|
||||
]
|
||||
}
|
||||
27
setup.py
27
setup.py
@@ -22,10 +22,9 @@ VERSION = __version__
|
||||
MAIN = 'kcc.py'
|
||||
extra_options = {}
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
if platform == 'darwin':
|
||||
from setuptools import setup
|
||||
from os import chmod, makedirs
|
||||
from os import chmod, makedirs, system
|
||||
from shutil import copyfile
|
||||
extra_options = dict(
|
||||
setup_requires=['py2app'],
|
||||
@@ -34,10 +33,10 @@ if platform == 'darwin':
|
||||
py2app=dict(
|
||||
argv_emulation=True,
|
||||
iconfile='icons/comic2ebook.icns',
|
||||
includes=['sip', 'PyQt5.QtPrintSupport'],
|
||||
includes=['sip'],
|
||||
resources=['LICENSE.txt', 'other/qt.conf', 'other/Additional-LICENSE.txt', 'other/unrar', 'other/7za'],
|
||||
plist=dict(
|
||||
CFBundleName=NAME,
|
||||
CFBundleName='Kindle Comic Converter',
|
||||
CFBundleShortVersionString=VERSION,
|
||||
CFBundleGetInfoString=NAME + ' ' + VERSION +
|
||||
', written 2012-2015 by Ciro Mattia Gonano and Pawel Jastrzebski',
|
||||
@@ -60,7 +59,6 @@ if platform == 'darwin':
|
||||
)
|
||||
)
|
||||
elif platform == 'win32':
|
||||
# noinspection PyUnresolvedReferences
|
||||
import py2exe
|
||||
from platform import architecture
|
||||
from distutils.core import setup
|
||||
@@ -95,7 +93,7 @@ elif platform == 'win32':
|
||||
zipfile=None,
|
||||
data_files=additional_files)
|
||||
else:
|
||||
if argv[1] == 'make_pyz':
|
||||
if len(argv) > 1 and argv[1] == 'make_pyz':
|
||||
from os import system
|
||||
script = '''
|
||||
cp kcc.py __main__.py
|
||||
@@ -135,10 +133,10 @@ else:
|
||||
scripts=['build/_scripts/kcc', 'build/_scripts/kcc-c2e', 'build/_scripts/kcc-c2p'],
|
||||
packages=['kcc'],
|
||||
install_requires=[
|
||||
'Pillow>=2.7.0',
|
||||
'psutil>=2.0',
|
||||
'python-slugify>=0.1.0',
|
||||
'scandir>=0.9',
|
||||
'Pillow>=2.8.2',
|
||||
'psutil>=3.0.0',
|
||||
'python-slugify>=1.1.3',
|
||||
'scandir>=1.1.0',
|
||||
],
|
||||
zip_safe=False,
|
||||
)
|
||||
@@ -156,7 +154,8 @@ setup(
|
||||
)
|
||||
|
||||
if platform == 'darwin':
|
||||
makedirs('dist/' + NAME + '.app/Contents/PlugIns/platforms', exist_ok=True)
|
||||
copyfile('other/libqcocoa.dylib', 'dist/' + NAME + '.app/Contents/PlugIns/platforms/libqcocoa.dylib')
|
||||
chmod('dist/' + NAME + '.app/Contents/Resources/unrar', 0o777)
|
||||
chmod('dist/' + NAME + '.app/Contents/Resources/7za', 0o777)
|
||||
makedirs('dist/Kindle Comic Converter.app/Contents/PlugIns/platforms', exist_ok=True)
|
||||
copyfile('other/libqcocoa.dylib', 'dist/Kindle Comic Converter.app/Contents/PlugIns/platforms/libqcocoa.dylib')
|
||||
chmod('dist/Kindle Comic Converter.app/Contents/Resources/unrar', 0o777)
|
||||
chmod('dist/Kindle Comic Converter.app/Contents/Resources/7za', 0o777)
|
||||
system('appdmg setup.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
|
||||
|
||||
Reference in New Issue
Block a user