1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-18 06:58:58 +00:00

Compare commits

..

29 Commits
4.5 ... 4.6.2

Author SHA1 Message Date
Paweł Jastrzębski
c65e1c8dea Merge pull request #150 from ciromattia/dev
4.6.2
2015-07-14 18:01:51 +02:00
Paweł Jastrzębski
677622c103 Updated README + version bump 2015-07-14 18:01:02 +02:00
Paweł Jastrzębski
af0ebb85a0 Escape HTML in metadata (close #148) 2015-07-14 17:58:59 +02:00
Paweł Jastrzębski
8af029ac92 Fixed MOBI header (close #149) 2015-07-14 17:40:33 +02:00
Paweł Jastrzębski
a268e12a90 Merge pull request #147 from ciromattia/dev
4.6.1
2015-07-05 12:16:27 +02:00
Paweł Jastrzębski
d621335e6c Updated README + version bump 2015-07-05 12:15:35 +02:00
Paweł Jastrzębski
ec1d9c2d93 Added ComicRack Summary field parsing (close #146) 2015-07-05 07:49:48 +02:00
Paweł Jastrzębski
85b9dbbf83 Detect too small input images 2015-07-05 06:57:56 +02:00
Paweł Jastrzębski
feeced44bf Tweaked KEPUB renamer (close #144) 2015-06-28 19:20:38 +02:00
Paweł Jastrzębski
cbea18398b Fixed Kobo TOC (close #145) 2015-06-28 18:31:47 +02:00
Paweł Jastrzębski
4c9857f14d Merge pull request #143 from ciromattia/dev
4.6
2015-06-21 08:44:37 +02:00
Paweł Jastrzębski
6b58ef4557 Updated README + version bump 2015-06-21 08:29:22 +02:00
Paweł Jastrzębski
24d697c965 Dropped Kindle Fire support 2015-06-21 08:04:28 +02:00
Paweł Jastrzębski
8b07d4eb69 Added Kindle Paperwhite 3 profile 2015-06-18 16:21:48 +02:00
Paweł Jastrzębski
e6c5ac915f Changed Kobo default output to KEPUB (close #141) 2015-06-17 22:35:24 +02:00
Paweł Jastrzębski
b22e4757a3 EPUB 3.0 output 2015-06-17 20:22:04 +02:00
Paweł Jastrzębski
91b06016bb Dependency update 2015-06-15 22:19:08 +02:00
Paweł Jastrzębski
5631391245 Error handling tweak 2015-05-31 09:36:20 +02:00
Paweł Jastrzębski
c33887b7b7 Fixed yet another tray icon anomaly 2015-05-20 20:21:12 +02:00
Paweł Jastrzębski
8d82f58f09 Merge pull request #137 from ciromattia/dev
4.5.1
2015-05-09 09:29:42 +02:00
Paweł Jastrzębski
36985f5169 Detect broken Pillow (close #135) 2015-05-07 22:37:48 +02:00
Paweł Jastrzębski
9d190c1585 Updated README + version bump 2015-05-07 18:06:16 +02:00
Paweł Jastrzębski
3834850317 Tweaked metadata editor 2015-05-07 17:49:03 +02:00
Paweł Jastrzębski
84fc23b979 Fixed CBR parsing anomalies (close #133) 2015-04-27 18:50:14 +02:00
Paweł Jastrzębski
77748afdbd Fixed supid typo 2015-04-26 18:47:40 +02:00
Paweł Jastrzębski
431e2ffaf2 Binary blob cleanup 2015-04-26 15:59:43 +02:00
Paweł Jastrzębski
16df4cd083 Added Kobo Glow HD profile 2015-04-22 20:38:17 +02:00
Paweł Jastrzębski
1aa34347c1 Added page-progression-direction tag to EPUB spine 2015-03-17 11:02:28 +01:00
Paweł Jastrzębski
561af90b06 Updated README and Setup 2015-03-09 20:08:48 +01:00
20 changed files with 531 additions and 411 deletions

View File

@@ -1,7 +1,7 @@
# KCC # 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. **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 2.2 it outputs valid EPUB 2.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**_. 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. It can also optionally optimize images by applying a number of transformations.
@@ -29,6 +29,26 @@ You can find the latest released binary at the following links:
- **Linux:** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/) - **Linux:** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/)
- **OS X (10.8+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/) - **OS X (10.8+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/)
## 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.8.2+
- [psutil](https://pypi.python.org/pypi/psutil) 3.0.0+
- [python-slugify](http://pypi.python.org/pypi/python-slugify) 1.1.2+
- [scandir](https://pypi.python.org/pypi/scandir) 1.1.0+
On Debian based distributions these two commands should install all needed dependencies:
```
sudo apt-get install python3 python3-dev python3-pip python3-pyqt5 libpng-dev libjpeg-dev p7zip-full unrar
sudo pip3 install pillow python-slugify psutil scandir
```
### Optional dependencies
- [KindleGen](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211) v2.9+ in a directory reachable by your _PATH_ or in _KCC_ directory *(For MOBI generation)*
- [UnRAR](http://www.rarlab.com/download.htm) *(For CBR/RAR support)*
- [7za](http://www.7-zip.org/download.html) *(For 7z/CB7 support)*
## INPUT FORMATS ## INPUT FORMATS
**KCC** can understand and convert, at the moment, the following input types: **KCC** can understand and convert, at the moment, the following input types:
- Folders containing: PNG, JPG or GIF files - Folders containing: PNG, JPG or GIF files
@@ -37,29 +57,6 @@ You can find the latest released binary at the following links:
- CB7, 7Z *(With `7za` executable)* - CB7, 7Z *(With `7za` executable)*
- PDF *(Only extracting JPG images)* - PDF *(Only extracting 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)*
- [UnRAR](http://www.rarlab.com/download.htm) *(For CBR/RAR support)*
- [7za](http://www.7-zip.org/download.html) *(For 7z/CB7 support)*
### For running from source:
- 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+
On Debian based distributions these two commands should install all dependencies:
```
sudo apt-get install python3 python3-dev python3-pip python3-pyqt5 libpng-dev libjpeg-dev p7zip-full unrar
sudo pip3 install pillow python-slugify psutil scandir
```
### For freezing code:
- Windows - [py2exe](https://pypi.python.org/pypi/py2exe) 0.9.2.2+
- OS X - [py2app](https://bitbucket.org/ronaldoussoren/py2app) 0.9.0+
## USAGE ## USAGE
Should be pretty self-explanatory. All options have detailed informations in tooltips. Should be pretty self-explanatory. All options have detailed informations in tooltips.
@@ -78,8 +75,7 @@ Options:
MAIN: MAIN:
-p PROFILE, --profile=PROFILE -p PROFILE, --profile=PROFILE
Device profile (Available options: K1, K2, K345, KDX, Device profile (Available options: K1, K2, K345, KDX,
KPW, KV, KFHD, KFHDX, KFHDX8, KFA, KoMT, KoG, KoA, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O) [Default=KV]
KoAHD, KoAH2O) [Default=KV]
-q QUALITY, --quality=QUALITY -q QUALITY, --quality=QUALITY
Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0] Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
-m, --manga-style Manga style (Right-to-left reading and splitting) -m, --manga-style Manga style (Right-to-left reading and splitting)
@@ -148,19 +144,42 @@ 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. - 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 ## SAMPLE FILES CREATED BY KCC
* [Kindle Voyage](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi) * [Kindle Paperwhite 3 / Voyage](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
* [Kindle Paperwhite](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi) * [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
* [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K345.mobi) * [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K345.mobi)
* [Kindle DX/DXG](http://kcc.iosphe.re/Samples/Ubunchu!-KDX.cbz) * [Kindle DX/DXG](http://kcc.iosphe.re/Samples/Ubunchu!-KDX.cbz)
* [Kobo Mini/Touch](http://kcc.iosphe.re/Samples/Ubunchu!-KoMT.cbz) * [Kobo Mini/Touch](http://kcc.iosphe.re/Samples/Ubunchu-KoMT.kepub.epub)
* [Kobo Glow](http://kcc.iosphe.re/Samples/Ubunchu!-KoG.cbz) * [Kobo Glo](http://kcc.iosphe.re/Samples/Ubunchu-KoG.kepub.epub)
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu!-KoA.cbz) * [Kobo Glo HD](http://kcc.iosphe.re/Samples/Ubunchu-KoGHD.kepub.epub)
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu!-KoAHD.cbz) * [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub)
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu!-KoAH2O.cbz) * [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 ## CHANGELOG
####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
* Minor bug fixes and tweaks
####4.5: ####4.5:
* Added simple ComicRack medadata editor * Added simple ComicRack metadata editor
* Re-enabled Manga Cover Database support * Re-enabled Manga Cover Database support
* ComicRack bookmarks are now parsed * ComicRack bookmarks are now parsed
* Fixed glitches in Kindle Voyage profile * Fixed glitches in Kindle Voyage profile

View File

@@ -34,4 +34,4 @@ if __name__ == "__main__":
freeze_support() freeze_support()
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.') print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
main(sys.argv[1:]) main(sys.argv[1:])
sys.exit(0) sys.exit(0)

View File

@@ -34,4 +34,4 @@ if __name__ == "__main__":
freeze_support() freeze_support()
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.') print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
main(sys.argv[1:]) main(sys.argv[1:])
sys.exit(0) sys.exit(0)

View File

@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter" #define MyAppName "Kindle Comic Converter"
#define MyAppVersion "4.5" #define MyAppVersion "4.6.2"
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski" #define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
#define MyAppURL "http://kcc.iosphe.re/" #define MyAppURL "http://kcc.iosphe.re/"
#define MyAppExeName "KCC.exe" #define MyAppExeName "KCC.exe"

View File

@@ -375,9 +375,6 @@ class WorkerThread(QtCore.QThread):
elif GUI.QualityBox.checkState() == 2: elif GUI.QualityBox.checkState() == 2:
options.quality = 2 options.quality = 2
options.format = str(GUI.FormatBox.currentText()) options.format = str(GUI.FormatBox.currentText())
if GUI.currentMode == 1:
if 'KFH' in profile:
options.upscale = True
# Advanced mode settings # Advanced mode settings
if GUI.currentMode > 1: if GUI.currentMode > 1:
@@ -557,6 +554,7 @@ class WorkerThread(QtCore.QThread):
class SystemTrayIcon(QtWidgets.QSystemTrayIcon): class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self): def __init__(self):
super().__init__()
if self.isSystemTrayAvailable(): if self.isSystemTrayAvailable():
QtWidgets.QSystemTrayIcon.__init__(self, GUI.icons.programIcon, MW) QtWidgets.QSystemTrayIcon.__init__(self, GUI.icons.programIcon, MW)
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@@ -1161,57 +1159,47 @@ class KCCGUI(KCC_ui.Ui_KCC):
self.p.ionice(1) self.p.ionice(1)
self.profiles = { self.profiles = {
"Kindle Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "K. PW 3/Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'KV'}, 'DefaultUpscale': False, 'Label': 'KV'},
"Kindle Paperwhite": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 1/2": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'KPW'}, 'DefaultUpscale': False, 'Label': 'KPW'},
"Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K345'}, 'DefaultUpscale': False, 'Label': 'K345'},
"Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 2, "Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 2,
'DefaultUpscale': False, 'Label': 'KDX'}, 'DefaultUpscale': False, 'Label': 'KDX'},
"K. Fire HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kobo Mini/Touch": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
'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,
'DefaultUpscale': False, 'Label': 'KoMT'}, 'DefaultUpscale': False, 'Label': 'KoMT'},
"Kobo Glow": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2, "Kobo Glo": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoG'}, 'DefaultUpscale': False, 'Label': 'KoG'},
"Kobo Aura": {'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': 1,
'DefaultUpscale': False, 'Label': 'KoA'}, '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'}, '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'}, 'DefaultUpscale': False, 'Label': 'KoAH2O'},
"Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1, "Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'OTHER'}, '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, "Kindle 1": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K1'}, 'DefaultUpscale': False, 'Label': 'K1'},
"Kindle 2": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 2": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K2'} 'DefaultUpscale': False, 'Label': 'K2'}
} }
profilesGUI = [ profilesGUI = [
"Kindle Voyage", "K. PW 3/Voyage",
"Kindle Paperwhite", "Kindle PW 1/2",
"Kindle", "Kindle",
"Separator", "Separator",
"K. Fire HD",
"K. Fire HDX",
"K. Fire HDX 8.9",
"Separator",
"Kobo Mini/Touch", "Kobo Mini/Touch",
"Kobo Glow", "Kobo Glo",
"Kobo Glo HD",
"Kobo Aura", "Kobo Aura",
"Kobo Aura HD", "Kobo Aura HD",
"Kobo Aura H2O", "Kobo Aura H2O",
"Separator", "Separator",
"Other", "Other",
"Separator", "Separator",
"Kindle for Android",
"Kindle 1", "Kindle 1",
"Kindle 2", "Kindle 2",
"Kindle DX/DXG", "Kindle DX/DXG",
@@ -1339,6 +1327,8 @@ class KCCGUI_MetaEditor(KCC_MetaEditor_ui.Ui_MetaEditorDialog):
field.setText(self.parser.data[field.objectName()[:-4]]) field.setText(self.parser.data[field.objectName()[:-4]])
for field in (self.WriterLine, self.PencillerLine, self.InkerLine, self.ColoristLine): for field in (self.WriterLine, self.PencillerLine, self.InkerLine, self.ColoristLine):
field.setText(', '.join(self.parser.data[field.objectName()[:-4] + 's'])) field.setText(', '.join(self.parser.data[field.objectName()[:-4] + 's']))
if self.SeriesLine.text() == '':
self.SeriesLine.setText(file.split('\\')[-1].split('.')[0])
def saveData(self): def saveData(self):
for field in (self.VolumeLine, self.NumberLine, self.MUidLine): for field in (self.VolumeLine, self.NumberLine, self.MUidLine):

View File

@@ -1,4 +1,4 @@
__version__ = '4.5' __version__ = '4.6.2'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2015, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __copyright__ = '2012-2015, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'

View File

@@ -22,6 +22,7 @@ from zipfile import is_zipfile, ZipFile
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from psutil import Popen from psutil import Popen
from shutil import move, copy from shutil import move, copy
from scandir import walk
from . import rarfile from . import rarfile
from .shared import check7ZFile as is_7zfile, saferReplace from .shared import check7ZFile as is_7zfile, saferReplace
@@ -45,7 +46,7 @@ class CBxArchive:
cbzFile = ZipFile(self.origFileName) cbzFile = ZipFile(self.origFileName)
filelist = [] filelist = []
for f in cbzFile.namelist(): for f in cbzFile.namelist():
if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('thumbs.db'): if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('humbs.db'):
pass # skip MacOS special files pass # skip MacOS special files
elif f.endswith('/'): elif f.endswith('/'):
try: try:
@@ -58,25 +59,18 @@ class CBxArchive:
def extractCBR(self, targetdir): def extractCBR(self, targetdir):
cbrFile = rarfile.RarFile(self.origFileName) cbrFile = rarfile.RarFile(self.origFileName)
filelist = [] cbrFile.extractall(targetdir)
for f in cbrFile.namelist(): for root, dirnames, filenames in walk(targetdir):
if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('thumbs.db'): for filename in filenames:
pass # skip MacOS special files if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'):
elif f.endswith('/'): os.remove(os.path.join(root, filename))
try:
os.makedirs(os.path.join(targetdir, f))
except Exception:
pass # the dir exists so we are going to extract the images only.
else:
filelist.append(f)
cbrFile.extractall(targetdir, filelist)
def extractCB7(self, targetdir): def extractCB7(self, targetdir):
# Workaround for some wide UTF-8 + Popen abnormalities # Workaround for some wide UTF-8 + Popen abnormalities
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
copy(self.origFileName, os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP')) 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') 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 -o"' 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) + targetdir + '"', stdout=PIPE, stderr=STDOUT, shell=True)
extracted = False extracted = False
for line in output.stdout: for line in output.stdout:

View File

@@ -20,6 +20,7 @@
import os import os
import sys import sys
from time import strftime, gmtime
from copy import copy from copy import copy
from glob import glob from glob import glob
from json import loads from json import loads
@@ -37,6 +38,7 @@ from PIL import Image
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from psutil import Popen, virtual_memory from psutil import Popen, virtual_memory
from scandir import walk from scandir import walk
from html import escape
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
@@ -119,83 +121,98 @@ def buildHTML(path, imgfile, imgfilepath, forcePV=False):
os.makedirs(htmlpath) os.makedirs(htmlpath)
htmlfile = os.path.join(htmlpath, filename[0] + '.html') htmlfile = os.path.join(htmlpath, filename[0] + '.html')
f = open(htmlfile, "w", encoding='UTF-8') f = open(htmlfile, "w", encoding='UTF-8')
f.writelines(["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" ", if options.iskindle:
"\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n", f.writelines(["<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n", "<!DOCTYPE html>\n",
"<head>\n", "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
"<title>", filename[0], "</title>\n", "<head>\n",
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n", "<title>", filename[0], "</title>\n",
"<link href=\"", "../" * (backref - 1), "<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n", "<meta charset=\"utf-8\"/>\n",
"</head>\n", "</head>\n",
"<body" + additionalStyle + ">\n", "<body" + additionalStyle + ">\n",
"<div class=\"fs\">\n", "<div class=\"fs\">\n",
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"", "<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
imgfile, "\" class=\"singlePage\"/></div>\n" imgfile, "\" class=\"singlePage\"/></div>\n"
]) ])
if (options.panelview or forcePV) and not noPV: if (options.panelview or forcePV) and not noPV:
options.panelviewused = True options.panelviewused = True
if not noHorizontalPV and not noVerticalPV: if not noHorizontalPV and not noVerticalPV:
if rotatedPage: if rotatedPage:
if options.righttoleft: if options.righttoleft:
order = [1, 3, 2, 4] order = [1, 3, 2, 4]
else:
order = [2, 4, 1, 3]
else: else:
order = [2, 4, 1, 3] if options.righttoleft:
else: order = [2, 1, 4, 3]
if options.righttoleft: else:
order = [2, 1, 4, 3] order = [1, 2, 3, 4]
else: boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"]
order = [1, 2, 3, 4] elif noHorizontalPV and not noVerticalPV:
boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"] if rotatedPage:
elif noHorizontalPV and not noVerticalPV: if options.righttoleft:
if rotatedPage: order = [1, 2]
if options.righttoleft: else:
order = [1, 2] order = [2, 1]
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]
else: else:
order = [1, 2] order = [1, 2]
boxes = ["BoxL", "BoxR"] boxes = ["BoxT", "BoxB"]
else: elif not noHorizontalPV and noVerticalPV:
order = [1] if rotatedPage:
boxes = ["BoxC"] order = [1, 2]
for i in range(0, len(boxes)): else:
f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=", if options.righttoleft:
"'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]), order = [2, 1]
"}'></a></div>\n"]) else:
if options.quality == 2 and not forcePV: order = [1, 2]
imgfilepv = imgfile.split(".") boxes = ["BoxL", "BoxR"]
imgfilepv[0] += "-hq" else:
imgfilepv = ".".join(imgfilepv) order = [1]
else: boxes = ["BoxC"]
imgfilepv = imgfile for i in range(0, len(boxes)):
xl, yu, xr, yd = detectMargins(imgfilepath) f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=",
boxStyles = {"BoxTL": "left:" + xl + ";top:" + yu + ";", "'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]),
"BoxTR": "right:" + xr + ";top:" + yu + ";", "}'></a></div>\n"])
"BoxBL": "left:" + xl + ";bottom:" + yd + ";", if options.quality == 2 and not forcePV:
"BoxBR": "right:" + xr + ";bottom:" + yd + ";", imgfilepv = imgfile.split(".")
"BoxT": "left:-25%;top:" + yu + ";", imgfilepv[0] += "-hq"
"BoxB": "left:-25%;bottom:" + yd + ";", imgfilepv = ".".join(imgfilepv)
"BoxL": "left:" + xl + ";top:-25%;", else:
"BoxR": "right:" + xr + ";top:-25%;", imgfilepv = imgfile
"BoxC": "left:-25%;top:-25%;" xl, yu, xr, yd = detectMargins(imgfilepath)
} boxStyles = {"BoxTL": "left:" + xl + ";top:" + yu + ";",
for box in boxes: "BoxTR": "right:" + xr + ";top:" + yu + ";",
f.writelines(["<div id=\"" + box + "-Panel-Parent\" class=\"target-mag-parent\"><div id=\"", "BoxBL": "left:" + xl + ";bottom:" + yd + ";",
"Generic-Panel\" class=\"target-mag\"><img style=\"" + boxStyles[box] + "\" src=\"", "BoxBR": "right:" + xr + ";bottom:" + yd + ";",
"../" * backref, "Images/", postfix, imgfilepv, "\" alt=\"" + imgfilepv, "BoxT": "left:-25%;top:" + yu + ";",
"\"/></div></div>\n", "BoxB": "left:-25%;bottom:" + yd + ";",
]) "BoxL": "left:" + xl + ";top:-25%;",
f.writelines(["</div>\n</body>\n</html>"]) "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() f.close()
return path, imgfile return path, imgfile
@@ -205,11 +222,9 @@ def buildNCX(dstdir, title, chapters, chapterNames):
ncxfile = os.path.join(dstdir, 'OEBPS', 'toc.ncx') ncxfile = os.path.join(dstdir, 'OEBPS', 'toc.ncx')
f = open(ncxfile, "w", encoding='UTF-8') f = open(ncxfile, "w", encoding='UTF-8')
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", 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", "<ncx version=\"2005-1\" xml:lang=\"en-US\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n",
"<head>\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:depth\" content=\"1\"/>\n",
"<meta name=\"dtb:totalPageCount\" content=\"0\"/>\n", "<meta name=\"dtb:totalPageCount\" content=\"0\"/>\n",
"<meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n", "<meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n",
@@ -234,6 +249,44 @@ def buildNCX(dstdir, title, chapters, chapterNames):
f.close() 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): def buildOPF(dstdir, title, filelist, cover=None):
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf') opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
profilelabel, deviceres, palette, gamma, panelviewsize = options.profileData profilelabel, deviceres, palette, gamma, panelviewsize = options.profileData
@@ -243,40 +296,45 @@ def buildOPF(dstdir, title, filelist, cover=None):
writingmode = "horizontal-lr" writingmode = "horizontal-lr"
f = open(opffile, "w", encoding='UTF-8') f = open(opffile, "w", encoding='UTF-8')
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", 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", "xmlns=\"http://www.idpf.org/2007/opf\">\n",
"<metadata xmlns:opf=\"http://www.idpf.org/2007/opf\" ", "<metadata xmlns:opf=\"http://www.idpf.org/2007/opf\" ",
"xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n", "xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n",
"<dc:title>", title, "</dc:title>\n", "<dc:title>", title, "</dc:title>\n",
"<dc:language>en-US</dc:language>\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: for author in options.authors:
f.writelines(["<dc:creator>", author, "</dc:creator>\n"]) f.writelines(["<dc:creator>", author, "</dc:creator>\n"])
f.writelines(["<meta name=\"generator\" content=\"KindleComicConverter-" + __version__ + "\"/>\n", f.writelines(["<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n",
"<meta name=\"RegionMagnification\" content=\"true\"/>\n",
"<meta name=\"region-mag\" content=\"true\"/>\n",
"<meta name=\"cover\" content=\"cover\"/>\n", "<meta name=\"cover\" content=\"cover\"/>\n",
"<meta name=\"book-type\" content=\"comic\"/>\n", "<meta property=\"rendition:orientation\">portrait</meta>\n",
"<meta name=\"rendition:layout\" content=\"pre-paginated\"/>\n", "<meta property=\"rendition:spread\">portrait</meta>\n",
"<meta name=\"zero-gutter\" content=\"true\"/>\n", "<meta property=\"rendition:layout\">pre-paginated</meta>\n"])
"<meta name=\"zero-margin\" content=\"true\"/>\n", if options.iskindle and options.profile != 'Custom':
"<meta name=\"fixed-layout\" content=\"true\"/>\n" f.writelines(["<meta name=\"original-resolution\" content=\"",
"<meta name=\"rendition:orientation\" content=\"portrait\"/>\n", str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
"<meta name=\"orientation-lock\" content=\"portrait\"/>\n", "<meta name=\"book-type\" content=\"comic\"/>\n",
"<meta name=\"original-resolution\" content=\"", "<meta name=\"RegionMagnification\" content=\"true\"/>\n",
str(deviceres[0]) + "x" + str(deviceres[1]), "\"/>\n", "<meta name=\"primary-writing-mode\" content=\"" + writingmode + "\"/>\n",
"<meta name=\"primary-writing-mode\" content=\"", writingmode, "\"/>\n", "<meta name=\"zero-gutter\" content=\"true\"/>\n",
"<meta name=\"ke-border-color\" content=\"#ffffff\"/>\n", "<meta name=\"zero-margin\" content=\"true\"/>\n",
"<meta name=\"ke-border-width\" content=\"0\"/>\n", "<meta name=\"ke-border-color\" content=\"#ffffff\"/>\n",
"</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ", "<meta name=\"ke-border-width\" content=\"0\"/>\n"])
"media-type=\"application/x-dtbncx+xml\"/>\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: if cover is not None:
filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')) filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\'))
if '.png' == filename[1]: if '.png' == filename[1]:
mt = 'image/png' mt = 'image/png'
else: else:
mt = 'image/jpeg' 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 = [] reflist = []
for path in filelist: for path in filelist:
folder = path[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\').replace("\\", "/") folder = path[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\').replace("\\", "/")
@@ -293,10 +351,13 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\""
+ mt + "\"/>\n") + mt + "\"/>\n")
f.write("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n") f.write("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n")
f.write("</manifest>\n<spine toc=\"ncx\">\n") if options.righttoleft:
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
for entry in reflist: for entry in reflist:
f.write("<itemref idref=\"page_" + entry + "\"/>\n") 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() f.close()
os.mkdir(os.path.join(dstdir, 'META-INF')) os.mkdir(os.path.join(dstdir, 'META-INF'))
f = open(os.path.join(dstdir, 'META-INF', 'container.xml'), 'w', encoding='UTF-8') f = open(os.path.join(dstdir, 'META-INF', 'container.xml'), 'w', encoding='UTF-8')
@@ -317,114 +378,126 @@ def buildEPUB(path, chapterNames, tomeNumber):
_, deviceres, _, _, panelviewsize = options.profileData _, deviceres, _, _, panelviewsize = options.profileData
os.mkdir(os.path.join(path, 'OEBPS', 'Text')) os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w', encoding='UTF-8') f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w', encoding='UTF-8')
# DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT. if options.iskindle:
# Generic Panel View support + Margins fix for Non-Kindle devices. f.writelines(["@page {\n",
f.writelines(["@page {\n", "margin-bottom: 0;\n",
"margin-bottom: 0;\n", "margin-top: 0\n",
"margin-top: 0\n", "}\n",
"}\n", "body {\n",
"body {\n", "display: block;\n",
"display: block;\n", "margin-bottom: 0;\n",
"margin-bottom: 0;\n", "margin-left: 0;\n",
"margin-left: 0;\n", "margin-right: 0;\n",
"margin-right: 0;\n", "margin-top: 0;\n",
"margin-top: 0;\n", "padding-bottom: 0;\n",
"padding-bottom: 0;\n", "padding-left: 0;\n",
"padding-left: 0;\n", "padding-right: 0;\n",
"padding-right: 0;\n", "padding-top: 0;\n",
"padding-top: 0;\n", "text-align: left\n",
"text-align: left\n", "}\n",
"}\n", "div.fs {\n",
"div.fs {\n", "height: ", str(deviceres[1]), "px;\n",
"height: ", str(deviceres[1]), "px;\n", "width: ", str(deviceres[0]), "px;\n",
"width: ", str(deviceres[0]), "px;\n", "position: relative;\n",
"position: relative;\n", "display: block;\n",
"display: block;\n", "text-align: center\n",
"text-align: center\n", "}\n",
"}\n", "div.fs a {\n",
"div.fs a {\n", "display: block;\n",
"display: block;\n", "width : 100%;\n",
"width : 100%;\n", "height: 100%;\n",
"height: 100%;\n", "}\n",
"}\n", "div.fs div {\n",
"div.fs div {\n", "position: absolute;\n",
"position: absolute;\n", "}\n",
"}\n", "img.singlePage {\n",
"img.singlePage {\n", "position: absolute;\n",
"position: absolute;\n", "height: ", str(deviceres[1]), "px;\n",
"height: ", str(deviceres[1]), "px;\n", "width: ", str(deviceres[0]), "px;\n",
"width: ", str(deviceres[0]), "px;\n", "}\n",
"}\n", "div.target-mag-parent {\n",
"div.target-mag-parent {\n", "width:100%;\n",
"width:100%;\n", "height:100%;\n",
"height:100%;\n", "display:none;\n",
"display:none;\n", "}\n",
"}\n", "div.target-mag {\n",
"div.target-mag {\n", "position: absolute;\n",
"position: absolute;\n", "display: block;\n",
"display: block;\n", "overflow: hidden;\n",
"overflow: hidden;\n", "}\n",
"}\n", "div.target-mag img {\n",
"div.target-mag img {\n", "position: absolute;\n",
"position: absolute;\n", "height: ", str(panelviewsize[1]), "px;\n",
"height: ", str(panelviewsize[1]), "px;\n", "width: ", str(panelviewsize[0]), "px;\n",
"width: ", str(panelviewsize[0]), "px;\n", "}\n",
"}\n", "#Generic-Panel {\n",
"#Generic-Panel {\n", "top: 0;\n",
"top: 0;\n", "height: 100%;\n",
"height: 100%;\n", "width: 100%;\n",
"width: 100%;\n", "}\n",
"}\n", "#BoxC {\n",
"#BoxC {\n", "top: 0;\n",
"top: 0;\n", "height: 100%;\n",
"height: 100%;\n", "width: 100%;\n",
"width: 100%;\n", "}\n",
"}\n", "#BoxT {\n",
"#BoxT {\n", "top: 0;\n",
"top: 0;\n", "height: 50%;\n",
"height: 50%;\n", "width: 100%;\n",
"width: 100%;\n", "}\n",
"}\n", "#BoxB {\n",
"#BoxB {\n", "bottom: 0;\n",
"bottom: 0;\n", "height: 50%;\n",
"height: 50%;\n", "width: 100%;\n",
"width: 100%;\n", "}\n",
"}\n", "#BoxL {\n",
"#BoxL {\n", "left: 0;\n",
"left: 0;\n", "height: 100%;\n",
"height: 100%;\n", "width: 50%;\n",
"width: 50%;\n", "}\n",
"}\n", "#BoxR {\n",
"#BoxR {\n", "right: 0;\n",
"right: 0;\n", "height: 100%;\n",
"height: 100%;\n", "width: 50%;\n",
"width: 50%;\n", "}\n",
"}\n", "#BoxTL {\n",
"#BoxTL {\n", "top: 0;\n",
"top: 0;\n", "left: 0;\n",
"left: 0;\n", "height: 50%;\n",
"height: 50%;\n", "width: 50%;\n",
"width: 50%;\n", "}\n",
"}\n", "#BoxTR {\n",
"#BoxTR {\n", "top: 0;\n",
"top: 0;\n", "right: 0;\n",
"right: 0;\n", "height: 50%;\n",
"height: 50%;\n", "width: 50%;\n",
"width: 50%;\n", "}\n",
"}\n", "#BoxBL {\n",
"#BoxBL {\n", "bottom: 0;\n",
"bottom: 0;\n", "left: 0;\n",
"left: 0;\n", "height: 50%;\n",
"height: 50%;\n", "width: 50%;\n",
"width: 50%;\n", "}\n",
"}\n", "#BoxBR {\n",
"#BoxBR {\n", "bottom: 0;\n",
"bottom: 0;\n", "right: 0;\n",
"right: 0;\n", "height: 50%;\n",
"height: 50%;\n", "width: 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() f.close()
for (dirpath, dirnames, filenames) in walk(os.path.join(path, 'OEBPS', 'Images')): for (dirpath, dirnames, filenames) in walk(os.path.join(path, 'OEBPS', 'Images')):
chapter = False chapter = False
@@ -443,7 +516,7 @@ def buildEPUB(path, chapterNames, tomeNumber):
image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options, 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 # Hack that force Panel View on at last one page
if lastfile and not options.panelviewused and 'Ko' not in options.profile \ 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) filelist[-1] = buildHTML(lastfile[0], lastfile[1], lastfile[2], True)
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks # Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
if not chapterNames and options.chapters: if not chapterNames and options.chapters:
@@ -461,6 +534,7 @@ def buildEPUB(path, chapterNames, tomeNumber):
chapterNames[filename] = aChapter[1] chapterNames[filename] = aChapter[1]
globaldiff = pageid - (aChapter[0] + globaldiff) globaldiff = pageid - (aChapter[0] + globaldiff)
buildNCX(path, options.title, chapterlist, chapterNames) buildNCX(path, options.title, chapterlist, chapterNames)
buildNAV(path, options.title, chapterlist, chapterNames)
buildOPF(path, options.title, filelist, cover) buildOPF(path, options.title, filelist, cover)
@@ -576,7 +650,7 @@ def getWorkFolder(afile):
if len(afile) > 240: if len(afile) > 240:
raise UserWarning("Path is too long.") raise UserWarning("Path is too long.")
if os.path.isdir(afile): if os.path.isdir(afile):
workdir = mkdtemp('', 'KCC-TMP-') workdir = mkdtemp('', 'KCC-')
try: try:
os.rmdir(workdir) os.rmdir(workdir)
fullPath = os.path.join(workdir, 'OEBPS', 'Images') fullPath = os.path.join(workdir, 'OEBPS', 'Images')
@@ -595,7 +669,7 @@ def getWorkFolder(afile):
rmtree(path, True) rmtree(path, True)
raise UserWarning("Failed to extract images.") raise UserWarning("Failed to extract images.")
else: else:
workdir = mkdtemp('', 'KCC-TMP-') workdir = mkdtemp('', 'KCC-')
cbx = cbxarchive.CBxArchive(afile) cbx = cbxarchive.CBxArchive(afile)
if cbx.isCbxFile(): if cbx.isCbxFile():
try: try:
@@ -605,7 +679,7 @@ def getWorkFolder(afile):
raise UserWarning("Failed to extract file.") raise UserWarning("Failed to extract file.")
else: else:
rmtree(workdir, True) rmtree(workdir, True)
raise TypeError raise TypeError("Failed to detect archive format.")
if len(os.path.join(path, 'OEBPS', 'Images')) > 240: if len(os.path.join(path, 'OEBPS', 'Images')) > 240:
raise UserWarning("Path is too long.") raise UserWarning("Path is too long.")
move(path, path + "_temp") move(path, path + "_temp")
@@ -616,8 +690,8 @@ def getWorkFolder(afile):
def getOutputFilename(srcpath, wantedname, ext, tomeNumber): def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
if srcpath[-1] == os.path.sep: if srcpath[-1] == os.path.sep:
srcpath = srcpath[:-1] srcpath = srcpath[:-1]
if not ext.startswith('.'): if 'Ko' in options.profile and options.format == 'EPUB':
ext = '.' + ext ext = '.kepub.epub'
if wantedname is not None: if wantedname is not None:
if wantedname.endswith(ext): if wantedname.endswith(ext):
filename = os.path.abspath(wantedname) filename = os.path.abspath(wantedname)
@@ -629,7 +703,14 @@ def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
elif os.path.isdir(srcpath): elif os.path.isdir(srcpath):
filename = srcpath + tomeNumber + ext filename = srcpath + tomeNumber + ext
else: 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): if os.path.isfile(filename):
counter = 0 counter = 0
basename = os.path.splitext(filename)[0] basename = os.path.splitext(filename)[0]
@@ -644,6 +725,7 @@ def getComicInfo(path, originalPath):
options.authors = ['KCC'] options.authors = ['KCC']
options.remoteCovers = {} options.remoteCovers = {}
options.chapters = [] options.chapters = []
options.summary = ''
titleSuffix = '' titleSuffix = ''
if options.title == 'defaulttitle': if options.title == 'defaulttitle':
defaultTitle = True defaultTitle = True
@@ -662,7 +744,7 @@ def getComicInfo(path, originalPath):
options.authors = [] options.authors = []
if defaultTitle: if defaultTitle:
if xml.data['Series']: if xml.data['Series']:
options.title = xml.data['Series'] options.title = escape(xml.data['Series'])
if xml.data['Volume']: if xml.data['Volume']:
titleSuffix += ' V' + xml.data['Volume'] titleSuffix += ' V' + xml.data['Volume']
if xml.data['Number']: if xml.data['Number']:
@@ -670,7 +752,7 @@ def getComicInfo(path, originalPath):
options.title += titleSuffix options.title += titleSuffix
for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']: for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']:
for person in xml.data[field]: for person in xml.data[field]:
options.authors.append(person) options.authors.append(escape(person))
if len(options.authors) > 0: if len(options.authors) > 0:
options.authors = list(set(options.authors)) options.authors = list(set(options.authors))
options.authors.sort() options.authors.sort()
@@ -680,6 +762,8 @@ def getComicInfo(path, originalPath):
options.remoteCovers = getCoversFromMCB(xml.data['MUid']) options.remoteCovers = getCoversFromMCB(xml.data['MUid'])
if xml.data['Bookmarks']: if xml.data['Bookmarks']:
options.chapters = xml.data['Bookmarks'] options.chapters = xml.data['Bookmarks']
if xml.data['Summary']:
options.summary = escape(xml.data['Summary'])
os.remove(xmlPath) os.remove(xmlPath)
@@ -882,6 +966,8 @@ def splitProcess(path, mode):
def detectCorruption(tmpPath, orgPath): def detectCorruption(tmpPath, orgPath):
imageNumber = 0
imageSmaller = 0
for root, dirs, files in walk(tmpPath, False): for root, dirs, files in walk(tmpPath, False):
for name in files: for name in files:
if getImageFileName(name) is not None: if getImageFileName(name) is not None:
@@ -895,11 +981,24 @@ def detectCorruption(tmpPath, orgPath):
img.verify() img.verify()
img = Image.open(path) img = Image.open(path)
img.load() img.load()
except Exception: 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) rmtree(os.path.join(tmpPath, '..', '..'), True)
raise RuntimeError('Image file %s is corrupted.' % pathOrg) if 'decoder' in err and 'not available' in err:
raise RuntimeError('Pillow was compiled without JPG and/or PNG decoder.')
else:
raise RuntimeError('Image file %s is corrupted.' % pathOrg)
else: else:
os.remove(os.path.join(root, name)) 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): def detectMargins(path):
@@ -932,7 +1031,7 @@ def detectMargins(path):
def createNewTome(): def createNewTome():
tomePathRoot = mkdtemp('', 'KCC-TMP-') tomePathRoot = mkdtemp('', 'KCC-')
tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images') tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images')
os.makedirs(tomePath) os.makedirs(tomePath)
return tomePath, tomePathRoot return tomePath, tomePathRoot
@@ -970,8 +1069,8 @@ def makeParser():
otherOptions = OptionGroup(psr, "OTHER") otherOptions = OptionGroup(psr, "OTHER")
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV", 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," help="Device profile (Available options: K1, K2, K345, KDX, KPW, KV, KoMT, KoG, KoGHD,"
" KFA, KoMT, KoG, KoA, KoAHD, KoAH2O) [Default=KV]") " KoA, KoAHD, KoAH2O) [Default=KV]")
mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0", mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0",
help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [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, mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
@@ -1031,14 +1130,17 @@ def checkOptions():
global options global options
options.panelview = True options.panelview = True
options.panelviewused = False options.panelviewused = False
options.iskindle = False
options.bordersColor = None options.bordersColor = None
if options.format == 'Auto': 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' options.format = 'MOBI'
elif options.profile in ['Other']: elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O']:
options.format = 'EPUB' options.format = 'EPUB'
elif options.profile in ['KDX', 'KoMT', 'KoG', 'KoA', 'KoAHD', 'KoAH2O']: elif options.profile in ['KDX']:
options.format = 'CBZ' options.format = 'CBZ'
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'OTHER']:
options.iskindle = True
if options.white_borders: if options.white_borders:
options.bordersColor = 'white' options.bordersColor = 'white'
if options.black_borders: if options.black_borders:
@@ -1046,11 +1148,6 @@ def checkOptions():
# Splitting MOBI is not optional # Splitting MOBI is not optional
if options.format == 'MOBI': if options.format == 'MOBI':
options.batchsplit = True 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. # 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': if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX':
options.quality = 0 options.quality = 0
@@ -1069,10 +1166,6 @@ def checkOptions():
# Kobo models can't use ultra quality mode # Kobo models can't use ultra quality mode
if options.quality == 2: if options.quality == 2:
options.quality = 1 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 # CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.format == 'CBZ': if options.profile == 'KDX' and options.format == 'CBZ':
options.customheight = 1200 options.customheight = 1200
@@ -1205,7 +1298,7 @@ def makeMOBIFix(item):
mobiPath = item.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
move(mobiPath, mobiPath + '_toclean') move(mobiPath, mobiPath + '_toclean')
try: try:
dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(str(uuid4()), 'UTF-8')) dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(options.uuid, 'UTF-8'))
return [True] return [True]
except Exception as err: except Exception as err:
return [False, format(err)] return [False, format(err)]

View File

@@ -297,4 +297,4 @@ def main(argv=None, qtGUI=None):
else: else:
raise UserWarning("Provided path is not a directory.") raise UserWarning("Provided path is not a directory.")
else: else:
raise UserWarning("Target height is not set.") raise UserWarning("Target height is not set.")

View File

@@ -181,4 +181,4 @@ class DualMobiMetaFix:
replacesection(self.datain, datain_kf8, rec0) replacesection(self.datain, datain_kf8, rec0)
self.datain.flush() self.datain.flush()
self.datain.close() self.datain.close()

View File

@@ -82,14 +82,11 @@ class ProfileData:
'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)), 'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)),
'K345': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)), 'K345': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)), 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)),
'KPW': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)), 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8, (1137, 1536)),
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.8, (1608, 2172)), 'KV': ("Kindle Paperwhite 3/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)),
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)), 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)),
'KoG': ("Kobo Glow", (768, 1024), Palette16, 1.8, (1152, 1536)), 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8, (1152, 1536)),
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8, (1608, 2172)),
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)), 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)),
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)), 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)),
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8, (1620, 2145)), 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8, (1620, 2145)),

View File

@@ -38,6 +38,7 @@ class MetadataParser:
'Pencillers': [], 'Pencillers': [],
'Inkers': [], 'Inkers': [],
'Colorists': [], 'Colorists': [],
'Summary': '',
'MUid': '', 'MUid': '',
'Bookmarks': []} 'Bookmarks': []}
self.rawdata = None self.rawdata = None
@@ -64,7 +65,7 @@ class MetadataParser:
self.rawdata = parse(xml_file) self.rawdata = parse(xml_file)
elif is_7zfile(self.source): elif is_7zfile(self.source):
self.compressor = '7z' self.compressor = '7z'
workdir = mkdtemp('', 'KCC-TMP-') workdir = mkdtemp('', 'KCC-')
tmpXML = os.path.join(workdir, 'ComicInfo.xml') tmpXML = os.path.join(workdir, 'ComicInfo.xml')
output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"', output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"',
stdout=PIPE, stderr=STDOUT, shell=True) stdout=PIPE, stderr=STDOUT, shell=True)
@@ -90,6 +91,8 @@ class MetadataParser:
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
if len(self.rawdata.getElementsByTagName('Number')) != 0: if len(self.rawdata.getElementsByTagName('Number')) != 0:
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue 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']: for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
if len(self.rawdata.getElementsByTagName(field)) != 0: if len(self.rawdata.getElementsByTagName(field)) != 0:
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '): 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']], for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])], ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])], ['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 '']): ['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
if self.rawdata.getElementsByTagName(row[0]): if self.rawdata.getElementsByTagName(row[0]):
node = self.rawdata.getElementsByTagName(row[0])[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']], for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])], ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])], ['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 '']): ['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
if row[1]: if row[1]:
main = doc.createElement(row[0]) main = doc.createElement(row[0])
@@ -147,7 +150,7 @@ class MetadataParser:
with open(self.source, 'w', encoding='utf-8') as f: with open(self.source, 'w', encoding='utf-8') as f:
self.rawdata.writexml(f, encoding='utf-8') self.rawdata.writexml(f, encoding='utf-8')
else: else:
workdir = mkdtemp('', 'KCC-TMP-') workdir = mkdtemp('', 'KCC-')
tmpXML = os.path.join(workdir, 'ComicInfo.xml') tmpXML = os.path.join(workdir, 'ComicInfo.xml')
with open(tmpXML, 'w', encoding='utf-8') as f: with open(tmpXML, 'w', encoding='utf-8') as f:
self.rawdata.writexml(f, encoding='utf-8') self.rawdata.writexml(f, encoding='utf-8')

View File

@@ -29,7 +29,7 @@ class PdfJpgExtract:
self.origFileName = origFileName self.origFileName = origFileName
self.filename = os.path.splitext(origFileName) self.filename = os.path.splitext(origFileName)
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
self.path = self.filename[0] + "-KCC-TMP-" + ''.join(choice(ascii_uppercase + digits) for x in range(3)) self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for x in range(3))
def getPath(self): def getPath(self):
return self.path return self.path

View File

@@ -104,9 +104,9 @@ def saferReplace(old, new):
def removeFromZIP(zipfname, *filenames): def removeFromZIP(zipfname, *filenames):
tempdir = mkdtemp('', 'KCC-TMP-') tempdir = mkdtemp('', 'KCC-')
try: try:
tempname = os.path.join(tempdir, 'KCC-TMP.zip') tempname = os.path.join(tempdir, 'KCC.zip')
with ZipFile(zipfname, 'r') as zipread: with ZipFile(zipfname, 'r') as zipread:
with ZipFile(tempname, 'w', compression=ZIP_DEFLATED) as zipwrite: with ZipFile(tempname, 'w', compression=ZIP_DEFLATED) as zipwrite:
for item in zipread.infolist(): for item in zipread.infolist():
@@ -130,28 +130,28 @@ def dependencyCheck(level):
if level > 1: if level > 1:
try: try:
from psutil import __version__ as psutilVersion from psutil import __version__ as psutilVersion
if StrictVersion('2.0.0') > StrictVersion(psutilVersion): if StrictVersion('3.0.0') > StrictVersion(psutilVersion):
missing.append('psutil 2.0.0+') missing.append('psutil 3.0.0+')
except ImportError: except ImportError:
missing.append('psutil 2.0.0+') missing.append('psutil 3.0.0+')
try: try:
from slugify import __version__ as slugifyVersion from slugify import __version__ as slugifyVersion
if StrictVersion('0.1.0') > StrictVersion(slugifyVersion): if StrictVersion('1.1.2') > StrictVersion(slugifyVersion):
missing.append('python-slugify 0.1.0+') missing.append('python-slugify 1.1.2+')
except ImportError: except ImportError:
missing.append('python-slugify 0.1.0+') missing.append('python-slugify 1.1.2+')
try: try:
from PIL import PILLOW_VERSION as pillowVersion from PIL import PILLOW_VERSION as pillowVersion
if StrictVersion('2.7.0') > StrictVersion(pillowVersion): if StrictVersion('2.8.2') > StrictVersion(pillowVersion):
missing.append('Pillow 2.7.0+') missing.append('Pillow 2.8.2+')
except ImportError: except ImportError:
missing.append('Pillow 2.7.0+') missing.append('Pillow 2.8.2+')
try: try:
from scandir import __version__ as scandirVersion from scandir import __version__ as scandirVersion
if StrictVersion('0.9') > StrictVersion(scandirVersion): if StrictVersion('1.1') > StrictVersion(scandirVersion):
missing.append('scandir 0.9+') missing.append('scandir 1.1+')
except ImportError: except ImportError:
missing.append('scandir 0.9+') missing.append('scandir 1.1+')
if len(missing) > 0: if len(missing) > 0:
print('ERROR: ' + ', '.join(missing) + ' is not installed!') print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1) exit(1)

BIN
other/7za Executable file

Binary file not shown.

BIN
other/7za.exe Normal file

Binary file not shown.

BIN
other/UnRAR.exe Normal file

Binary file not shown.

BIN
other/unrar Executable file

Binary file not shown.

131
setup.py
View File

@@ -1,25 +1,32 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
py2exe/py2app build script for KCC. pip/py2exe/py2app build script for KCC.
Usage (Windows): Usage (Windows):
python setup.py py2exe py -3.4 setup.py py2exe
Usage (Linux):
python3 setup.py make_pyz or python3 setup.py install
Usage (Mac OS X): Usage (Mac OS X):
python setup.py py2app python3 setup.py py2app
""" """
from sys import platform, version_info from sys import platform, version_info, argv
from kcc import __version__ from kcc import __version__
if version_info[0] != 3: if version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
NAME = "KindleComicConverter" NAME = 'KindleComicConverter'
VERSION = __version__ VERSION = __version__
MAIN = "kcc.py" MAIN = 'kcc.py'
extra_options = {}
if platform == "darwin": # noinspection PyUnresolvedReferences
if platform == 'darwin':
from setuptools import setup from setuptools import setup
from os import chmod, makedirs
from shutil import copyfile
extra_options = dict( extra_options = dict(
setup_requires=['py2app'], setup_requires=['py2app'],
app=[MAIN], app=[MAIN],
@@ -32,8 +39,8 @@ if platform == "darwin":
plist=dict( plist=dict(
CFBundleName=NAME, CFBundleName=NAME,
CFBundleShortVersionString=VERSION, CFBundleShortVersionString=VERSION,
CFBundleGetInfoString=NAME + " " + VERSION + CFBundleGetInfoString=NAME + ' ' + VERSION +
", written 2012-2015 by Ciro Mattia Gonano and Pawel Jastrzebski", ', written 2012-2015 by Ciro Mattia Gonano and Pawel Jastrzebski',
CFBundleExecutable=NAME, CFBundleExecutable=NAME,
CFBundleDocumentTypes=[ CFBundleDocumentTypes=[
dict( dict(
@@ -52,12 +59,12 @@ if platform == "darwin":
) )
) )
) )
elif platform == "win32": elif platform == 'win32':
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import py2exe import py2exe
import platform from platform import architecture
from distutils.core import setup from distutils.core import setup
if platform.architecture()[0] == '64bit': if architecture()[0] == '64bit':
suffix = '_64' suffix = '_64'
else: else:
suffix = '' suffix = ''
@@ -70,44 +77,86 @@ elif platform == "win32":
'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libGLESv2.dll', 'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libGLESv2.dll',
'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libEGL.dll'])] 'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libEGL.dll'])]
extra_options = dict( extra_options = dict(
options={'py2exe': {"bundle_files": 1, options={'py2exe': {'bundle_files': 1,
"dist_dir": "dist" + suffix, 'dist_dir': 'dist' + suffix,
"compressed": True, 'compressed': True,
"includes": ["sip"], 'includes': ['sip'],
"excludes": ["tkinter"], 'excludes': ['tkinter'],
"optimize": 2}}, 'optimize': 2}},
windows=[{"script": MAIN, windows=[{'script': MAIN,
"dest_base": "KCC", 'dest_base': 'KCC',
"version": VERSION, 'version': VERSION,
"copyright": "Ciro Mattia Gonano, Pawel Jastrzebski © 2012-2015", 'copyright': 'Ciro Mattia Gonano, Pawel Jastrzebski © 2012-2015',
"legal_copyright": "ISC License (ISCL)", 'legal_copyright': 'ISC License (ISCL)',
"product_version": VERSION, 'product_version': VERSION,
"product_name": "Kindle Comic Converter", 'product_name': 'Kindle Comic Converter',
"file_description": "Kindle Comic Converter", 'file_description': 'Kindle Comic Converter',
"icon_resources": [(1, "icons\comic2ebook.ico")]}], 'icon_resources': [(1, 'icons\comic2ebook.ico')]}],
zipfile=None, zipfile=None,
data_files=additional_files) data_files=additional_files)
else: else:
print('Please use setup.sh to build Linux package.') if argv[1] == 'make_pyz':
exit() from os import system
script = '''
cp kcc.py __main__.py
zip kcc.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-bin
cat kcc.zip >> kcc-bin
chmod +x kcc-bin
cp kcc-c2e.py __main__.py
zip kcc-c2e.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-c2e-bin
cat kcc-c2e.zip >> kcc-c2e-bin
chmod +x kcc-c2e-bin
cp kcc-c2p.py __main__.py
zip kcc-c2p.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-c2p-bin
cat kcc-c2p.zip >> kcc-c2p-bin
chmod +x kcc-c2p-bin
tar --xform s:^.*/:: --xform s/LICENSE.txt/LICENSE/ --xform s/kcc-bin/kcc/ --xform s/kcc-c2p-bin/kcc-c2p/ \
--xform s/kcc-c2e-bin/kcc-c2e/ --xform s/comic2ebook/kcc/ -czf KindleComicConverter_linux_'''\
+ VERSION + '''.tar.gz kcc-bin kcc-c2e-bin kcc-c2p-bin LICENSE.txt README.md icons/comic2ebook.png
rm __main__.py kcc.zip kcc-c2e.zip kcc-c2p.zip kcc-bin kcc-c2e-bin kcc-c2p-bin
'''
system("bash -c '%s'" % script)
exit(0)
else:
from setuptools import setup
from os import makedirs
from shutil import copyfile
makedirs('build/_scripts/', exist_ok=True)
copyfile('kcc.py', 'build/_scripts/kcc')
copyfile('kcc-c2e.py', 'build/_scripts/kcc-c2e')
copyfile('kcc-c2p.py', 'build/_scripts/kcc-c2p')
extra_options = dict(
scripts=['build/_scripts/kcc', 'build/_scripts/kcc-c2e', 'build/_scripts/kcc-c2p'],
packages=['kcc'],
install_requires=[
'Pillow>=2.8.2',
'psutil>=3.0.0',
'python-slugify>=1.1.2',
'scandir>=1.1.0',
],
zip_safe=False,
)
# noinspection PyUnboundLocalVariable
setup( setup(
name=NAME, name=NAME,
version=VERSION, version=VERSION,
author="Ciro Mattia Gonano, Pawel Jastrzebski", author='Ciro Mattia Gonano, Pawel Jastrzebski',
author_email="ciromattia@gmail.com, pawelj@iosphe.re", author_email='ciromattia@gmail.com, pawelj@iosphe.re',
description="Kindle Comic Converter", description='Comic and manga converter for E-Book readers.',
license="ISC License (ISCL)", license='ISC License (ISCL)',
keywords="kindle comic mobipocket mobi cbz cbr manga", keywords='kindle comic mobipocket mobi cbz cbr manga',
url="http://github.com/ciromattia/kcc", url='http://github.com/ciromattia/kcc',
**extra_options **extra_options
) )
if platform == "darwin": if platform == 'darwin':
from os import chmod, makedirs makedirs('dist/' + NAME + '.app/Contents/PlugIns/platforms', exist_ok=True)
from shutil import copyfile
makedirs('dist/' + NAME + '.app/Contents/PlugIns/platforms')
copyfile('other/libqcocoa.dylib', 'dist/' + NAME + '.app/Contents/PlugIns/platforms/libqcocoa.dylib') 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/unrar', 0o777)
chmod('dist/' + NAME + '.app/Contents/Resources/7za', 0o777) chmod('dist/' + NAME + '.app/Contents/Resources/7za', 0o777)

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# Linux Python package build script
VERSION="4.5"
cp kcc.py __main__.py
zip kcc.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-bin
cat kcc.zip >> kcc-bin
chmod +x kcc-bin
cp kcc-c2e.py __main__.py
zip kcc-c2e.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-c2e-bin
cat kcc-c2e.zip >> kcc-c2e-bin
chmod +x kcc-c2e-bin
cp kcc-c2p.py __main__.py
zip kcc-c2p.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-c2p-bin
cat kcc-c2p.zip >> kcc-c2p-bin
chmod +x kcc-c2p-bin
tar --xform s:^.*/:: --xform s/kcc-bin/kcc/ --xform s/kcc-c2p-bin/kcc-c2p/ --xform s/kcc-c2e-bin/kcc-c2e/ --xform s/comic2ebook/kcc/ -czf KindleComicConverter_linux_${VERSION}.tar.gz kcc-bin kcc-c2e-bin kcc-c2p-bin LICENSE.txt icons/comic2ebook.png
rm __main__.py kcc.zip kcc-c2e.zip kcc-c2p.zip kcc-bin kcc-c2e-bin kcc-c2p-bin