1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-15 13:38:46 +00:00

Compare commits

...

49 Commits
5.4.1 ... 5.5.1

Author SHA1 Message Date
Paweł Jastrzębski
35bba68a72 Merge pull request #308 from ciromattia/dev
5.5.1
2019-03-08 08:44:48 +01:00
Paweł Jastrzębski
0b056a8fa8 Stabilise multiprocessing on OSX 2019-03-08 08:16:53 +01:00
Paweł Jastrzębski
a6f9e84251 Tweaks for OSX binary 2019-03-07 16:24:41 +01:00
Paweł Jastrzębski
259800e48b Tweaks for Windows binary 2019-03-07 11:36:56 +01:00
Paweł Jastrzębski
28e170f1d2 Merge pull request #306 from ciromattia/dev
5.5.0
2019-03-07 08:39:57 +01:00
Paweł Jastrzębski
7120c76025 Updated changelog 2019-03-07 08:38:22 +01:00
Paweł Jastrzębski
4891913b5c Added additional cleanup 2019-03-07 08:26:31 +01:00
Paweł Jastrzębski
cd83b2899c Fixed bookmark parsing (close #229) 2019-03-07 08:21:56 +01:00
Paweł Jastrzębski
535c2c220b Added RAR5 support 2019-03-06 20:12:11 +01:00
Paweł Jastrzębski
409f077c3e Bye, bye DEBs 2019-03-06 19:50:17 +01:00
Paweł Jastrzębski
3ecb2ba877 Fixed metadata encoding (close #281) 2019-03-06 16:37:26 +01:00
Paweł Jastrzębski
c07a9657ef Removed MCD support 2019-03-06 16:16:26 +01:00
Paweł Jastrzębski
7f719a22ad Added PW4 profile (close #293) 2019-03-06 16:01:18 +01:00
Paweł Jastrzębski
332d3d455e Version bump 2019-03-06 15:47:18 +01:00
Paweł Jastrzębski
2070a977ae Updated build enviroment 2019-03-06 10:56:44 +01:00
Paweł Jastrzębski
8f8d0d68a3 Fixed possible glob issues 2019-02-27 13:49:47 +01:00
Paweł Jastrzębski
5a8deb4623 Merge pull request #295 from murphytsai/feature/support-kobo-forma
Support Kobo Forma (close #294)
2019-01-04 10:09:25 +01:00
MurphyTsai
a7ea795df5 support kobo forma. 2019-01-03 14:52:28 +08:00
Paweł Jastrzębski
a2ffd259b8 Code cleanup 2018-07-10 09:00:13 +02:00
Paweł Jastrzębski
93e6b51466 Expanded output of corruption error (close #272) 2018-07-10 08:42:15 +02:00
Paweł Jastrzębski
7904662f25 Let 7-Zip handle all archive operations 2018-07-10 08:09:04 +02:00
Paweł Jastrzębski
6792c2d366 Bump MAX_IMAGE_PIXELS (close #273) 2018-07-09 08:30:12 +02:00
Paweł Jastrzębski
ef4a91e44d WebP support (close #263) 2018-07-08 08:42:34 +02:00
Paweł Jastrzębski
a2a405e5f5 Merge branch 'Gokuroro-master' into dev 2018-04-21 09:26:48 +02:00
Hugo
a63a46a741 Use more standard __version__ rather than PILLOW_VERSION 2018-04-18 11:42:56 +03:00
Carlos Rosa
2591b53a09 Make file type error more informative (display file name) 2018-04-04 11:05:13 -03:00
Carlos Rosa
1c615ffc20 Make format checking more straightforward 2018-04-04 11:04:31 -03:00
Carlos Rosa
6eb05b3a8f Allow for multiple file arguments on startup 2018-04-04 10:12:30 -03:00
Paweł Jastrzębski
968b083fb2 Updated build enviroment 2018-03-10 08:41:12 +01:00
Paweł Jastrzębski
205907ef1e Merge branch 'master' of https://github.com/ciromattia/kcc 2018-03-10 08:27:16 +01:00
Paweł Jastrzębski
33ef8275c3 Fixed non-Kindle EPUB ouput (close #262) 2018-03-10 08:26:55 +01:00
Paweł Jastrzębski
eec2099515 Fixed unrar detection 2018-03-10 08:26:14 +01:00
Paweł Jastrzębski
9027265b7c Update README.md 2018-03-09 20:45:37 +01:00
Paweł Jastrzębski
34e2af3389 Merge pull request #261 from ciromattia/dev
5.4.4
2018-03-08 16:15:42 +01:00
Paweł Jastrzębski
5ecddaceab Version bump 2018-03-08 16:12:56 +01:00
Paweł Jastrzębski
fe554c20aa Fixed dot procesing for real this time 2017-11-10 19:54:07 +01:00
Paweł Jastrzębski
6acf1a1802 Updated build enviroment 2017-11-10 19:10:23 +01:00
Paweł Jastrzębski
ac81a6be4b Fixed dot processing in directory names 2017-11-10 17:32:34 +01:00
Paweł Jastrzębski
829a5f25e7 Experimental KFX output 2017-11-05 18:54:11 +01:00
Paweł Jastrzębski
a695a4c151 Code cleanup 2017-11-02 10:28:43 +01:00
Paweł Jastrzębski
7c0b78350d Updated build enviroment 2017-10-14 22:13:09 +02:00
Paweł Jastrzębski
aa978ab246 Disabled old multiprocessing hack 2017-10-14 22:10:31 +02:00
Paweł Jastrzębski
7524c50657 Merge pull request #250 from ciromattia/dev
5.4.2
2017-10-14 18:01:32 +02:00
Paweł Jastrzębski
9d8663a925 Updated README + version bump 2017-10-14 18:00:39 +02:00
Paweł Jastrzębski
08ed304f8e Allow metadata editor to embed directories 2017-10-14 17:57:00 +02:00
Paweł Jastrzębski
658d2f3281 Fixed directory sorting (close #235) 2017-10-13 11:56:51 +02:00
Paweł Jastrzębski
f44bf5993e Fixed HQ mode upscaling (close #248) 2017-10-13 10:41:45 +02:00
Paweł Jastrzębski
458c28281f Added Oasis 2 profile (close #249) 2017-10-13 09:12:17 +02:00
Paweł Jastrzębski
968a1afa1d Updated build enviroment 2017-10-13 08:57:58 +02:00
47 changed files with 620 additions and 2738 deletions

7
.gitignore vendored
View File

@@ -1,16 +1,15 @@
*.pyc *.pyc
*.cbz *.cbz
*.cbr *.cbr
*.spec
.idea .idea
.DS_Store .DS_Store
.python-version
Thumbs.db Thumbs.db
dist dist
Output Output
test
solaio
kindlegen* kindlegen*
*.spec setup.bat
kindlecomicconverter/sentry.py kindlecomicconverter/sentry.py
build/ build/
.python-version
KindleComicConverter.egg-info/ KindleComicConverter.egg-info/

View File

@@ -1,31 +1,22 @@
matrix: matrix:
include: include:
- os: linux
language: python
python: 3.6
dist: trusty
sudo: required
- os: osx - os: osx
language: generic language: generic
osx_image: xcode6.4 osx_image: xcode9.2
before_install: before_install:
- if [ "$TRAVIS_OS_NAME" == "linux" ] ; then sudo apt-get -y install ruby ruby-dev ; fi - brew update
- if [ "$TRAVIS_OS_NAME" == "linux" ] ; then pip install --upgrade pip setuptools wheel ; fi - brew upgrade python3
- if [ "$TRAVIS_OS_NAME" == "linux" ] ; then openssl aes-256-cbc -K $encrypted_a95564d8ff0d_key -iv $encrypted_a95564d8ff0d_iv -in other/linux/sentry.py.enc -out kindlecomicconverter/sentry.py -d ; fi - brew uninstall node
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update ; fi - travis_wait 30 brew install node@6
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew install python3 ; fi - brew link node@6 --force --overwrite
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew upgrade node ; fi - pip3 install --upgrade pip setuptools wheel
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then pip3 install --upgrade pip setuptools wheel ; fi - openssl aes-256-cbc -K $encrypted_a95564d8ff0d_key -iv $encrypted_a95564d8ff0d_iv -in other/osx/sentry.py.enc -out kindlecomicconverter/sentry.py -d
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then openssl aes-256-cbc -K $encrypted_a95564d8ff0d_key -iv $encrypted_a95564d8ff0d_iv -in other/osx/sentry.py.enc -out kindlecomicconverter/sentry.py -d ; fi
install: install:
- if [ "$TRAVIS_OS_NAME" == "linux" ] ; then pip install -r requirements.txt ; fi - pip3 install -r requirements.txt
- if [ "$TRAVIS_OS_NAME" == "linux" ] ; then pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip ; fi - pip3 install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip
- if [ "$TRAVIS_OS_NAME" == "linux" ] ; then gem install fpm ; fi - npm install -g appdmg
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then pip3 install -r requirements.txt ; fi
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then pip3 install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip ; fi
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then npm install -g appdmg ; fi
script: python3 setup.py build_binary script: python3 setup.py build_binary

View File

@@ -1,4 +1,29 @@
# CHANGELOG # CHANGELOG
#### 5.5.1:
* Fixes some stability issues
#### 5.5.0:
* Added support for WebP format
* Added profiles for Kindle Paperwhite 4 and Kobo Forma
* All archives are now handled by 7z
* Removed MCD support
* Fixed multiple smaller issues
#### 5.4.5:
* Fixed EPUB output for non-Kindle devices
#### 5.4.4:
* Minor bug fixes
#### 5.4.3:
* Fixed conversion crash on Windows
#### 5.4.2:
* Added Kindle Oasis 2 profile
* Allowed metadata editor to edit directories
* Fixed image stretching when HQ Panel View option was enabled
* Fixed possible problem with directory sort order
#### 5.4.1: #### 5.4.1:
* Minor bug fixes and tweaks * Minor bug fixes and tweaks
* Implemented new binary build pipeline * Implemented new binary build pipeline

View File

@@ -1,7 +1,7 @@
ISC LICENSE ISC LICENSE
Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
Copyright (c) 2013-2017 Paweł Jastrzębski <pawelj@iosphe.re> Copyright (c) 2013-2019 Paweł Jastrzębski <pawelj@iosphe.re>
Permission to use, copy, modify, and/or distribute this software for Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the any purpose with or without fee is hereby granted, provided that the

View File

@@ -27,9 +27,9 @@ If you find **KCC** valuable you can consider donating to the authors:
## BINARY RELEASES ## BINARY RELEASES
You can find the latest released binary at the following links: You can find the latest released binary at the following links:
- **Windows (64-bit only):** [http://kcc.iosphe.re/Windows/](http://kcc.iosphe.re/Windows/) - **[Windows](http://kcc.iosphe.re/Windows/) (64-bit only)**
- **Linux (Glibc 2.19+):** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/) - **[macOS](http://kcc.iosphe.re/OSX/) (10.12+)**
- **OS X (10.10+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/) - **Linux:** Currently unavailable.
## PYPI ## PYPI
**KCC** is also available on PyPI. **KCC** is also available on PyPI.
@@ -48,21 +48,20 @@ Following software is required to run Linux version of **KCC** and/or bare sourc
On Debian based distributions these two commands should install all needed dependencies: On Debian based distributions these two commands should install all needed dependencies:
``` ```
sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full unrar sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full
sudo pip3 install --upgrade pillow python-slugify psutil pyqt5 raven sudo pip3 install --upgrade pillow python-slugify psutil pyqt5 raven
``` ```
### Optional dependencies ### 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)* - [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)* - [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 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, GIF or WebP files
- CBZ, ZIP - CBZ, ZIP *(With `7z` executable)*
- CBR, RAR *(With `unrar` executable)* - CBR, RAR *(With `7z` executable)*
- CB7, 7Z *(With `7za` executable)* - CB7, 7Z *(With `7z` executable)*
- PDF *(Only extracting JPG images)* - PDF *(Only extracting JPG images)*
## USAGE ## USAGE
@@ -83,8 +82,8 @@ Options:
MAIN: MAIN:
-p PROFILE, --profile=PROFILE -p PROFILE, --profile=PROFILE
Device profile (Available options: K1, K2, K34, K578, Device profile (Available options: K1, K2, K34, K578,
KDX, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KDX, KPW, KV, KO, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
KoAO) [Default=KV] KoAO, KoF) [Default=KV]
-m, --manga-style Manga style (right-to-left reading and splitting) -m, --manga-style Manga style (right-to-left reading and splitting)
-q, --hq Try to increase the quality of magnification -q, --hq Try to increase the quality of magnification
-2, --two-panel Display two not four panels in Panel View mode -2, --two-panel Display two not four panels in Panel View mode
@@ -97,7 +96,7 @@ Options:
Comic title [Default=filename or directory name] Comic title [Default=filename or directory name]
-f FORMAT, --format=FORMAT -f FORMAT, --format=FORMAT
Output format (Available options: Auto, MOBI, EPUB, Output format (Available options: Auto, MOBI, EPUB,
CBZ) [Default=Auto] CBZ, KFX) [Default=Auto]
-b BATCHSPLIT, --batchsplit=BATCHSPLIT -b BATCHSPLIT, --batchsplit=BATCHSPLIT
Split output into multiple files. 0: Don't split 1: Split output into multiple files. 0: Don't split 1:
Automatic mode 2: Consider every subdirectory as Automatic mode 2: Consider every subdirectory as
@@ -157,28 +156,28 @@ This script born as a cross-platform alternative to `KindleComicParser` by **Dc5
The app relies and includes the following scripts: The app relies and includes the following scripts:
- `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License. - `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License.
- `rarfile.py` script &copy; 2005-2014 **Marko Kreen** <markokr@gmail.com>. Released with ISC License.
- `image.py` class from **Alex Yatskov**'s [Mangle](https://github.com/FooSoft/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches. - `image.py` class from **Alex Yatskov**'s [Mangle](https://github.com/FooSoft/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches.
- Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License. - 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 Paperwhite 3 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi) * [Kindle Oasis 2](http://kcc.iosphe.re/Samples/Ubunchu!-KO.mobi)
* [Kindle Paperwhite 3 / 4 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi) * [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
* [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K45.mobi) * [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K578.mobi)
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.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 HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub)
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub) * [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub)
* [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub) * [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub)
* [Kobo Forma](http://kcc.iosphe.re/Samples/Ubunchu-KoF.kepub.epub)
## PRIVACY ## PRIVACY
**KCC** is initiating internet connections in three cases: **KCC** is initiating internet connections in two cases:
* During startup - Version check * During startup - Version check.
* When MCD metadata are used - Cover download * When error occurs - Automatic reporting on Windows and MacOS.
* When error occurs - Automatic reporting
## KNOWN ISSUES ## KNOWN ISSUES
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues). Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
## COPYRIGHT ## COPYRIGHT
Copyright (c) 2012-2017 Ciro Mattia Gonano and Paweł Jastrzębski. Copyright (c) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski.
**KCC** is released under ISC LICENSE; see LICENSE.txt for further details. **KCC** is released under ISC LICENSE; see LICENSE.txt for further details.

View File

@@ -1,5 +1,5 @@
environment: environment:
PYTHON: "C:\\Python36-x64" PYTHON: "C:\\Python37-x64"
install: install:
- set PATH="%PYTHON%\\Scripts";"C:\\Program Files (x86)\\Inno Setup 5";%PATH% - set PATH="%PYTHON%\\Scripts";"C:\\Program Files (x86)\\Inno Setup 5";%PATH%
@@ -7,15 +7,13 @@ install:
- "%PYTHON%\\python.exe -m pip install -r requirements.txt" - "%PYTHON%\\python.exe -m pip install -r requirements.txt"
- "%PYTHON%\\python.exe -m pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip" - "%PYTHON%\\python.exe -m pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip"
- nuget install secure-file -ExcludeVersion - nuget install secure-file -ExcludeVersion
- nuget install verpatch -ExcludeVersion
- secure-file\tools\secure-file -decrypt other\windows\Cert.pfx.enc -secret %ENCRYPTION%
- secure-file\tools\secure-file -decrypt other\windows\sentry.py.enc -out kindlecomicconverter\sentry.py -secret %ENCRYPTION% - secure-file\tools\secure-file -decrypt other\windows\sentry.py.enc -out kindlecomicconverter\sentry.py -secret %ENCRYPTION%
build_script: build_script:
- "%PYTHON%\\python.exe setup.py build_binary" - "%PYTHON%\\python.exe setup.py build_binary"
after_build: after_build:
- ps: Get-ChildItem .\dist\KindleComicConverter_win_* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } - ps: Get-ChildItem .\dist\KCC* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
deploy: deploy:
provider: S3 provider: S3

View File

@@ -320,6 +320,9 @@
<property name="text"> <property name="text">
<string>Editor</string> <string>Editor</string>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to edit directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="icon"> <property name="icon">
<iconset resource="KCC.qrc"> <iconset resource="KCC.qrc">
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset> <normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
@@ -454,7 +457,7 @@
</font> </font>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to select the output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to select the output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Convert</string> <string>Convert</string>

View File

@@ -112,19 +112,6 @@
<item row="6" column="1"> <item row="6" column="1">
<widget class="QLineEdit" name="coloristLine"/> <widget class="QLineEdit" name="coloristLine"/>
</item> </item>
<item row="7" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;MUid:&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="muidLine"/>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -23,9 +23,10 @@ if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
from multiprocessing import freeze_support from multiprocessing import freeze_support, set_start_method
from kindlecomicconverter.startup import startC2E from kindlecomicconverter.startup import startC2E
if __name__ == "__main__": if __name__ == "__main__":
set_start_method('spawn')
freeze_support() freeze_support()
startC2E() startC2E()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -23,9 +23,10 @@ if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
from multiprocessing import freeze_support from multiprocessing import freeze_support, set_start_method
from kindlecomicconverter.startup import startC2P from kindlecomicconverter.startup import startC2P
if __name__ == "__main__": if __name__ == "__main__":
set_start_method('spawn')
freeze_support() freeze_support()
startC2P() startC2P()

10
kcc.iss
View File

@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter" #define MyAppName "Kindle Comic Converter"
#define MyAppVersion "5.4.1" #define MyAppVersion "5.5.1"
#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"
@@ -12,7 +12,7 @@ AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL} AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL} AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL} AppUpdatesURL={#MyAppURL}
AppCopyright=Copyright (C) 2012-2017 Ciro Mattia Gonano and Paweł Jastrzębski AppCopyright=Copyright (C) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski
ArchitecturesAllowed=x64 ArchitecturesAllowed=x64
DefaultDirName={pf}\{#MyAppName} DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
@@ -47,9 +47,8 @@ Name: "CB7association"; Description: "CB7"; GroupDescription: "File associations
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak
Source: "other\windows\Additional-LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion Source: "other\windows\Additional-LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion
Source: "other\windows\UnRAR.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "other\windows\7z.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "other\windows\7za.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "other\windows\7z.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "other\windows\vc_redist.x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall
[Icons] [Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
@@ -57,7 +56,6 @@ Name: "{group}\Readme"; Filename: "https://github.com/ciromattia/kcc#kcc"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run] [Run]
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/install /passive /norestart"; StatusMsg: "Installing Microsoft Visual C++ 2015 Redistributable Package..."
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall
[Messages] [Messages]

29
kcc.py
View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -28,29 +28,13 @@ import os
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
os.environ['PATH'] = os.path.dirname(os.path.abspath(sys.executable)) + \ os.environ['PATH'] = os.path.dirname(os.path.abspath(sys.executable)) + \
'/../Resources:/usr/local/bin:/usr/bin:/bin' '/../Resources:/usr/local/bin:/usr/bin:/bin'
os.chdir(os.path.dirname(os.path.abspath(sys.executable)) + '/../Resources')
os.system('defaults write com.kindlecomicconverter.KindleComicConverter ApplePersistenceIgnoreState YES') os.system('defaults write com.kindlecomicconverter.KindleComicConverter ApplePersistenceIgnoreState YES')
os.system('defaults write com.kindlecomicconverter.KindleComicConverter NSInitialToolTipDelay -int 1000') os.system('defaults write com.kindlecomicconverter.KindleComicConverter NSInitialToolTipDelay -int 1000')
else: else:
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/osx/:' + os.environ['PATH'] os.chdir(os.path.dirname(os.path.abspath(__file__)))
elif sys.platform.startswith('win'): elif sys.platform.startswith('win'):
import multiprocessing.popen_spawn_win32 as forking
class _Popen(forking.Popen):
def __init__(self, *args, **kw):
if hasattr(sys, 'frozen'):
# noinspection PyUnresolvedReferences,PyProtectedMember
os.putenv('_MEIPASS2', sys._MEIPASS)
try:
super(_Popen, self).__init__(*args, **kw)
finally:
if hasattr(sys, 'frozen'):
if hasattr(os, 'unsetenv'):
os.unsetenv('_MEIPASS2')
else:
os.putenv('_MEIPASS2', '')
forking.Popen = _Popen
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
os.chdir(os.path.dirname(os.path.abspath(sys.executable))) os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
else: else:
@@ -60,13 +44,14 @@ elif sys.platform.startswith('win'):
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
try: try:
import kindlecomicconverter.sentry import kindlecomicconverter.sentry
except: except ImportError:
pass pass
from multiprocessing import freeze_support from multiprocessing import freeze_support, set_start_method
from kindlecomicconverter.startup import start from kindlecomicconverter.startup import start
if __name__ == "__main__": if __name__ == "__main__":
set_start_method('spawn')
freeze_support() freeze_support()
start() start()

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -22,8 +22,9 @@ import sys
from urllib.parse import unquote from urllib.parse import unquote
from urllib.request import urlopen, urlretrieve, Request from urllib.request import urlopen, urlretrieve, Request
from time import sleep from time import sleep
from shutil import move from shutil import move, rmtree
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
# noinspection PyUnresolvedReferences
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
from xml.dom.minidom import parse from xml.dom.minidom import parse
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
@@ -31,7 +32,8 @@ from psutil import Popen, Process
from copy import copy from copy import copy
from distutils.version import StrictVersion from distutils.version import StrictVersion
from raven import Client from raven import Client
from .shared import md5Checksum, HTMLStripper, sanitizeTrace from tempfile import gettempdir
from .shared import md5Checksum, HTMLStripper, sanitizeTrace, walkLevel
from . import __version__ from . import __version__
from . import comic2ebook from . import comic2ebook
from . import metadata from . import metadata
@@ -156,8 +158,8 @@ class VersionThread(QtCore.QThread):
'(<a href="https://github.com/ciromattia/kcc/releases/">' '(<a href="https://github.com/ciromattia/kcc/releases/">'
'Changelog</a>)', 'warning', False) 'Changelog</a>)', 'warning', False)
def setAnswer(self, dialogAnswer): def setAnswer(self, dialoganswer):
self.answer = dialogAnswer self.answer = dialoganswer
def getNewVersion(self): def getNewVersion(self):
while self.answer is None: while self.answer is None:
@@ -173,15 +175,16 @@ class VersionThread(QtCore.QThread):
move(path[0], path[0] + '.exe') move(path[0], path[0] + '.exe')
MW.hideProgressBar.emit() MW.hideProgressBar.emit()
MW.modeConvert.emit(1) MW.modeConvert.emit(1)
Popen(path[0] + '.exe /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) Popen(path[0] + '.exe /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, stdin=PIPE, close_fds=True,
shell=True)
MW.forceShutdown.emit() MW.forceShutdown.emit()
except Exception: except Exception:
MW.addMessage.emit('Failed to download the update!', 'warning', False) MW.addMessage.emit('Failed to download the update!', 'warning', False)
MW.hideProgressBar.emit() MW.hideProgressBar.emit()
MW.modeConvert.emit(1) MW.modeConvert.emit(1)
def getNewVersionTick(self, size, blockSize, totalSize): def getNewVersionTick(self, size, blocksize, totalsize):
progress = int((size / (totalSize // blockSize)) * 100) progress = int((size / (totalsize // blocksize)) * 100)
if size == 0: if size == 0:
MW.progressBarTick.emit('100') MW.progressBarTick.emit('100')
if progress > self.barProgress: if progress > self.barProgress:
@@ -238,6 +241,7 @@ class WorkerThread(QtCore.QThread):
MW.addTrayMessage.emit('Conversion interrupted.', 'Critical') MW.addTrayMessage.emit('Conversion interrupted.', 'Critical')
MW.modeConvert.emit(1) MW.modeConvert.emit(1)
# noinspection PyUnboundLocalVariable
def run(self): def run(self):
MW.modeConvert.emit(0) MW.modeConvert.emit(0)
@@ -477,20 +481,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if self.needClean: if self.needClean:
self.needClean = False self.needClean = False
GUI.jobList.clear() GUI.jobList.clear()
if self.UnRAR: if self.sevenzip:
if self.sevenza: fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)')
'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)')
else:
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
'Comic (*.cbz *.cbr *.zip *.rar *.pdf)')
else: else:
if self.sevenza: fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.pdf)')
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
'Comic (*.cbz *.cb7 *.zip *.7z *.pdf)')
else:
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
'Comic (*.cbz *.zip *.pdf)')
for fname in fnames[0]: for fname in fnames[0]:
if fname != '': if fname != '':
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
@@ -500,28 +495,30 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.jobList.scrollToBottom() GUI.jobList.scrollToBottom()
def selectFileMetaEditor(self): def selectFileMetaEditor(self):
if self.UnRAR: sname = ''
if self.sevenza: if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select directory', self.lastPath)
if dname != '':
sname = os.path.join(dname, 'ComicInfo.xml')
if sys.platform.startswith('win'):
sname = sname.replace('/', '\\')
self.lastPath = os.path.abspath(sname)
else:
if self.sevenzip:
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
'Comic (*.cbz *.cbr *.cb7)') 'Comic (*.cbz *.cbr *.cb7)')
else: else:
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, fname = ['']
'Comic (*.cbz *.cbr)') self.showDialog("Editor is disabled due to a lack of 7z.", 'error')
else: if fname[0] != '':
if self.sevenza: if sys.platform.startswith('win'):
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, sname = fname[0].replace('/', '\\')
'Comic (*.cbz *.cb7)') else:
else: sname = fname[0]
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, self.lastPath = os.path.abspath(os.path.join(sname, os.pardir))
'Comic (*.cbz)') if sname != '':
if fname[0] != '':
if sys.platform.startswith('win'):
fname = fname[0].replace('/', '\\')
else:
fname = fname[0]
self.lastPath = os.path.abspath(os.path.join(fname, os.pardir))
try: try:
self.editor.loadData(fname) self.editor.loadData(sname)
except Exception as err: except Exception as err:
_, _, traceback = sys.exc_info() _, _, traceback = sys.exc_info()
GUI.sentry.captureException() GUI.sentry.captureException()
@@ -618,9 +615,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def togglequalityBox(self, value): def togglequalityBox(self, value):
profile = GUI.profiles[str(GUI.deviceBox.currentText())] profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if value == 2: if value == 2:
if profile['Label'] in ['KV']: if profile['Label'] in ['KV', 'KO']:
self.addMessage('This option is intended for older Kindle models.', 'warning') self.addMessage('This option is intended for older Kindle models.', 'warning')
self.addMessage('It will not increase quality on a device with 300 ppi screen.', 'warning') self.addMessage('On this device, quality improvement will be negligible.', 'warning')
GUI.upscaleBox.setEnabled(False) GUI.upscaleBox.setEnabled(False)
GUI.upscaleBox.setChecked(True) GUI.upscaleBox.setChecked(True)
else: else:
@@ -657,10 +654,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">' self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
'List of supported Non-Kindle devices.</a>', 'info') 'List of supported Non-Kindle devices.</a>', 'info')
def changeFormat(self, outputFormat=None): def changeFormat(self, outputformat=None):
profile = GUI.profiles[str(GUI.deviceBox.currentText())] profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if outputFormat is not None: if outputformat is not None:
GUI.formatBox.setCurrentIndex(outputFormat) GUI.formatBox.setCurrentIndex(outputformat)
else: else:
GUI.formatBox.setCurrentIndex(profile['DefaultFormat']) GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
if not GUI.webtoonBox.isChecked(): if not GUI.webtoonBox.isChecked():
@@ -802,16 +799,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if self.needClean: if self.needClean:
self.needClean = False self.needClean = False
GUI.jobList.clear() GUI.jobList.clear()
if self.UnRAR: formats = ['.pdf']
if self.sevenza: if self.sevenzip:
formats = ['.cbz', '.cbr', '.cb7', '.zip', '.rar', '.7z', '.pdf'] formats.extend(['.cb7', '.7z', '.cbz', '.zip', '.cbr', '.rar'])
else:
formats = ['.cbz', '.cbr', '.zip', '.rar', '.pdf']
else:
if self.sevenza:
formats = ['.cbz', '.cb7', '.zip', '.7z', '.pdf']
else:
formats = ['.cbz', '.zip', '.pdf']
if os.path.isdir(message): if os.path.isdir(message):
GUI.jobList.addItem(message) GUI.jobList.addItem(message)
GUI.jobList.scrollToBottom() GUI.jobList.scrollToBottom()
@@ -821,7 +811,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.jobList.addItem(message) GUI.jobList.addItem(message)
GUI.jobList.scrollToBottom() GUI.jobList.scrollToBottom()
else: else:
self.addMessage('This file type is unsupported!', 'error') self.addMessage('Unsupported file type for ' + message, 'error')
def dragAndDrop(self, e): def dragAndDrop(self, e):
e.accept() e.accept()
@@ -848,7 +838,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
except Exception: except Exception:
pass pass
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
if kindleGenExitCode.wait() == 0: kindleGenExitCode.communicate()
if kindleGenExitCode.returncode == 0:
self.kindleGen = True self.kindleGen = True
versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
for line in versionCheck.stdout: for line in versionCheck.stdout:
@@ -871,10 +862,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
else: else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error') self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
def __init__(self, KCCAplication, KCCWindow): def __init__(self, kccapp, kccwindow):
global APP, MW, GUI global APP, MW, GUI
APP = KCCAplication APP = kccapp
MW = KCCWindow MW = kccwindow
GUI = self GUI = self
self.setupUi(MW) self.setupUi(MW)
self.editor = KCCGUI_MetaEditor() self.editor = KCCGUI_MetaEditor()
@@ -899,6 +890,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.targetDirectory = '' self.targetDirectory = ''
self.sentry = Client(release=__version__) self.sentry = Client(release=__version__)
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
# noinspection PyUnresolvedReferences
from psutil import BELOW_NORMAL_PRIORITY_CLASS from psutil import BELOW_NORMAL_PRIORITY_CLASS
self.p = Process(os.getpid()) self.p = Process(os.getpid())
self.p.nice(BELOW_NORMAL_PRIORITY_CLASS) self.p.nice(BELOW_NORMAL_PRIORITY_CLASS)
@@ -918,12 +910,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
MW.resize(500, 500) MW.resize(500, 500)
self.profiles = { self.profiles = {
"Kindle Oasis 2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KO'},
"Kindle Oasis": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Oasis": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle PW 3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 3/4": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'KPW'}, 'DefaultUpscale': False, 'Label': 'KPW'},
"Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
@@ -944,6 +938,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'DefaultUpscale': True, 'Label': 'KoAH2O'}, 'DefaultUpscale': True, 'Label': 'KoAH2O'},
"Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoAO'}, 'DefaultUpscale': True, 'Label': 'KoAO'},
"Kobo Forma": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoF'},
"Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1, "Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'OTHER'}, 'DefaultUpscale': False, 'Label': 'OTHER'},
"Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
@@ -956,12 +952,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'DefaultUpscale': False, 'Label': 'K34'}, 'DefaultUpscale': False, 'Label': 'K34'},
} }
profilesGUI = [ profilesGUI = [
"Kindle Oasis 2",
"Kindle Oasis", "Kindle Oasis",
"Kindle Voyage", "Kindle Voyage",
"Kindle PW 3", "Kindle PW 3/4",
"Kindle PW 1/2", "Kindle PW 1/2",
"Kindle", "Kindle",
"Separator", "Separator",
"Kobo Forma",
"Kobo Aura ONE", "Kobo Aura ONE",
"Kobo Aura H2O", "Kobo Aura H2O",
"Kobo Aura HD", "Kobo Aura HD",
@@ -994,22 +992,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.addMessage('Since you are a new user of <b>KCC</b> please see few ' self.addMessage('Since you are a new user of <b>KCC</b> please see few '
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.', '<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
'info') 'info')
rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) process = Popen('7z', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
rarExitCode = rarExitCode.wait() process.communicate()
if rarExitCode == 0 or rarExitCode == 7: if process.returncode == 0 or process.returncode == 7:
self.UnRAR = True self.sevenzip = True
else: else:
self.UnRAR = False self.sevenzip = False
self.addMessage('Cannot find <a href="http://www.rarlab.com/rar_add.htm">UnRAR</a>!' self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7z</a>!'
' Processing of CBR/RAR files will be disabled.', 'warning') ' Processing of archives will be disabled.', 'warning')
sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
sevenzaExitCode = sevenzaExitCode.wait()
if sevenzaExitCode == 0 or sevenzaExitCode == 7:
self.sevenza = True
else:
self.sevenza = False
self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7za</a>!'
' Processing of CB7/7Z files will be disabled.', 'warning')
self.detectKindleGen(True) self.detectKindleGen(True)
APP.messageFromOtherInstance.connect(self.handleMessage) APP.messageFromOtherInstance.connect(self.handleMessage)
@@ -1079,6 +1069,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.versionCheck.start() self.versionCheck.start()
self.tray.show() self.tray.show()
# Cleanup unfisnished conversion
for root, dirs, _ in walkLevel(gettempdir(), 0):
for tempdir in dirs:
if tempdir.startswith('KCC-'):
rmtree(os.path.join(root, tempdir), True)
if self.windowSize != '0x0': if self.windowSize != '0x0':
x, y = self.windowSize.split('x') x, y = self.windowSize.split('x')
MW.resize(int(x), int(y)) MW.resize(int(x), int(y))
@@ -1090,7 +1086,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
def loadData(self, file): def loadData(self, file):
self.parser = metadata.MetadataParser(file) self.parser = metadata.MetadataParser(file)
if self.parser.compressor == 'rar': if self.parser.format in ['RAR', 'RAR5']:
self.editorWidget.setEnabled(False) self.editorWidget.setEnabled(False)
self.okButton.setEnabled(False) self.okButton.setEnabled(False)
self.statusLabel.setText('CBR metadata are read-only.') self.statusLabel.setText('CBR metadata are read-only.')
@@ -1098,23 +1094,20 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
self.editorWidget.setEnabled(True) self.editorWidget.setEnabled(True)
self.okButton.setEnabled(True) self.okButton.setEnabled(True)
self.statusLabel.setText('Separate authors with a comma.') self.statusLabel.setText('Separate authors with a comma.')
for field in (self.seriesLine, self.volumeLine, self.numberLine, self.muidLine): for field in (self.seriesLine, self.volumeLine, self.numberLine):
if field.objectName() == 'muidLine': field.setText(self.parser.data[field.objectName().capitalize()[:-4]])
field.setText(self.parser.data['MUid'])
else:
field.setText(self.parser.data[field.objectName().capitalize()[:-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().capitalize()[:-4] + 's'])) field.setText(', '.join(self.parser.data[field.objectName().capitalize()[:-4] + 's']))
if self.seriesLine.text() == '': if self.seriesLine.text() == '':
self.seriesLine.setText(file.split('\\')[-1].split('/')[-1].split('.')[0]) if file.endswith('.xml'):
self.seriesLine.setText(file.split('\\')[-2])
else:
self.seriesLine.setText(file.split('\\')[-1].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):
if field.text().isnumeric() or self.cleanData(field.text()) == '': if field.text().isnumeric() or self.cleanData(field.text()) == '':
if field.objectName() == 'muidLine': self.parser.data[field.objectName().capitalize()[:-4]] = self.cleanData(field.text())
self.parser.data['MUid'] = self.cleanData(field.text())
else:
self.parser.data[field.objectName().capitalize()[:-4]] = self.cleanData(field.text())
else: else:
self.statusLabel.setText(field.objectName().capitalize()[:-4] + ' field must be a number.') self.statusLabel.setText(field.objectName().capitalize()[:-4] + ' field must be a number.')
break break

View File

@@ -258,6 +258,7 @@ class Ui_mainWindow(object):
self.colorBox.setText(_translate("mainWindow", "Color mode")) self.colorBox.setText(_translate("mainWindow", "Color mode"))
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto")) self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))
self.editorButton.setText(_translate("mainWindow", "Editor")) self.editorButton.setText(_translate("mainWindow", "Editor"))
self.editorButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to edit directory.</p></body></html>"))
self.wikiButton.setText(_translate("mainWindow", "Wiki")) self.wikiButton.setText(_translate("mainWindow", "Wiki"))
self.directoryButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>")) self.directoryButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>"))
self.directoryButton.setText(_translate("mainWindow", "Add directory")) self.directoryButton.setText(_translate("mainWindow", "Add directory"))

View File

@@ -66,13 +66,6 @@ class Ui_editorDialog(object):
self.coloristLine = QtWidgets.QLineEdit(self.editorWidget) self.coloristLine = QtWidgets.QLineEdit(self.editorWidget)
self.coloristLine.setObjectName("coloristLine") self.coloristLine.setObjectName("coloristLine")
self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1) self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1)
self.label_8 = QtWidgets.QLabel(self.editorWidget)
self.label_8.setOpenExternalLinks(True)
self.label_8.setObjectName("label_8")
self.gridLayout.addWidget(self.label_8, 7, 0, 1, 1)
self.muidLine = QtWidgets.QLineEdit(self.editorWidget)
self.muidLine.setObjectName("muidLine")
self.gridLayout.addWidget(self.muidLine, 7, 1, 1, 1)
self.verticalLayout.addWidget(self.editorWidget) self.verticalLayout.addWidget(self.editorWidget)
self.optionWidget = QtWidgets.QWidget(editorDialog) self.optionWidget = QtWidgets.QWidget(editorDialog)
self.optionWidget.setObjectName("optionWidget") self.optionWidget.setObjectName("optionWidget")
@@ -117,7 +110,6 @@ class Ui_editorDialog(object):
self.label_5.setText(_translate("editorDialog", "Penciller:")) self.label_5.setText(_translate("editorDialog", "Penciller:"))
self.label_6.setText(_translate("editorDialog", "Inker:")) self.label_6.setText(_translate("editorDialog", "Inker:"))
self.label_7.setText(_translate("editorDialog", "Colorist:")) self.label_7.setText(_translate("editorDialog", "Colorist:"))
self.label_8.setText(_translate("editorDialog", "<html><head/><body><p><a href=\"https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support\"><span style=\" text-decoration: underline; color:#0000ff;\">MUid:</span></a></p></body></html>"))
self.okButton.setText(_translate("editorDialog", "Save")) self.okButton.setText(_translate("editorDialog", "Save"))
self.cancelButton.setText(_translate("editorDialog", "Cancel")) self.cancelButton.setText(_translate("editorDialog", "Cancel"))

View File

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

View File

@@ -1,90 +0,0 @@
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
import os
from zipfile import is_zipfile, ZipFile
from subprocess import STDOUT, PIPE
from psutil import Popen
from shutil import move
from . import rarfile
from .shared import check7ZFile as is_7zfile
class CBxArchive:
def __init__(self, origFileName):
self.origFileName = origFileName
if is_zipfile(origFileName):
self.compressor = 'zip'
elif rarfile.is_rarfile(origFileName):
self.compressor = 'rar'
elif is_7zfile(origFileName):
self.compressor = '7z'
else:
self.compressor = None
def isCbxFile(self):
return self.compressor is not None
def extractCBZ(self, targetdir):
cbzFile = ZipFile(self.origFileName)
filelist = []
for f in cbzFile.namelist():
if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('humbs.db'):
pass
elif f.endswith('/'):
try:
os.makedirs(os.path.join(targetdir, f))
except Exception:
pass
else:
filelist.append(f)
cbzFile.extractall(targetdir, filelist)
def extractCBR(self, targetdir):
cbrFile = rarfile.RarFile(self.origFileName)
cbrFile.extractall(targetdir)
for root, _, filenames in os.walk(targetdir):
for filename in filenames:
if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'):
os.remove(os.path.join(root, filename))
def extractCB7(self, targetdir):
output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' +
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
extracted = False
for line in output.stdout:
if b"Everything is Ok" in line:
extracted = True
if not extracted:
raise OSError
def extract(self, targetdir):
if self.compressor == 'rar':
self.extractCBR(targetdir)
elif self.compressor == 'zip':
self.extractCBZ(targetdir)
elif self.compressor == '7z':
self.extractCB7(targetdir)
adir = os.listdir(targetdir)
if 'ComicInfo.xml' in adir:
adir.remove('ComicInfo.xml')
if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])):
for f in os.listdir(os.path.join(targetdir, adir[0])):
move(os.path.join(targetdir, adir[0], f), targetdir)
os.rmdir(os.path.join(targetdir, adir[0]))
return targetdir

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -22,9 +22,7 @@ import os
import sys import sys
from time import strftime, gmtime from time import strftime, gmtime
from copy import copy from copy import copy
from glob import glob from glob import glob, escape
from json import loads
from urllib.request import Request, urlopen
from re import sub from re import sub
from stat import S_IWRITE, S_IREAD, S_IEXEC from stat import S_IWRITE, S_IREAD, S_IEXEC
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
@@ -37,7 +35,7 @@ from slugify import slugify as slugifyExt
from PIL import Image from PIL import Image
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from psutil import Popen, virtual_memory, disk_usage from psutil import Popen, virtual_memory, disk_usage
from html import escape from html import escape as hescape
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
@@ -45,7 +43,7 @@ except ImportError:
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace
from . import comic2panel from . import comic2panel
from . import image from . import image
from . import cbxarchive from . import comicarchive
from . import pdfjpgextract from . import pdfjpgextract
from . import dualmetafix from . import dualmetafix
from . import metadata from . import metadata
@@ -61,7 +59,7 @@ def main(argv=None):
parser.print_help() parser.print_help()
return 0 return 0
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
sources = set([source for arg in args for source in glob(arg)]) sources = set([source for arg in args for source in glob(escape(arg))])
else: else:
sources = set(args) sources = set(args)
if len(sources) == 0: if len(sources) == 0:
@@ -88,7 +86,7 @@ def buildHTML(path, imgfile, imgfilepath):
if "BlackBackground" in options.imgMetadata[imgfilepath]: if "BlackBackground" in options.imgMetadata[imgfilepath]:
additionalStyle = 'background-color:#000000;' additionalStyle = 'background-color:#000000;'
else: else:
additionalStyle = 'background-color:#FFFFFF;' additionalStyle = ''
postfix = '' postfix = ''
backref = 1 backref = 1
head = path head = path
@@ -104,7 +102,7 @@ def buildHTML(path, imgfile, imgfilepath):
htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml') htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml')
imgsize = Image.open(os.path.join(head, "Images", postfix, imgfile)).size imgsize = Image.open(os.path.join(head, "Images", postfix, imgfile)).size
if options.hq: if options.hq:
imgsizeframe = deviceres imgsizeframe = (int(imgsize[0] // 1.5), int(imgsize[1] // 1.5))
else: else:
imgsizeframe = imgsize imgsizeframe = imgsize
f = open(htmlfile, "w", encoding='UTF-8') f = open(htmlfile, "w", encoding='UTF-8')
@@ -112,13 +110,13 @@ def buildHTML(path, imgfile, imgfilepath):
"<!DOCTYPE html>\n", "<!DOCTYPE html>\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n", "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
"<head>\n", "<head>\n",
"<title>", escape(filename[0]), "</title>\n", "<title>", hescape(filename[0]), "</title>\n",
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n", "<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"<meta name=\"viewport\" " "<meta name=\"viewport\" "
"content=\"width=" + str(imgsize[0]) + ", height=" + str(imgsize[1]) + "\"/>\n" "content=\"width=" + str(imgsize[0]) + ", height=" + str(imgsize[1]) + "\"/>\n"
"</head>\n", "</head>\n",
"<body style=\"" + additionalStyle + "\">\n", "<body style=\"" + additionalStyle + "\">\n",
"<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsize) + "%;\">\n", "<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsizeframe) + "%;\">\n",
"<img width=\"" + str(imgsizeframe[0]) + "\" height=\"" + str(imgsizeframe[1]) + "\" ", "<img width=\"" + str(imgsizeframe[0]) + "\" height=\"" + str(imgsizeframe[1]) + "\" ",
"src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n</div>\n"]) "src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n</div>\n"])
if options.iskindle and options.panelview: if options.iskindle and options.panelview:
@@ -198,7 +196,7 @@ def buildHTML(path, imgfile, imgfilepath):
return path, imgfile return path, imgfile
def buildNCX(dstdir, title, chapters, chapterNames): 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",
@@ -210,32 +208,32 @@ def buildNCX(dstdir, title, chapters, chapterNames):
"<meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n", "<meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n",
"<meta name=\"generated\" content=\"true\"/>\n", "<meta name=\"generated\" content=\"true\"/>\n",
"</head>\n", "</head>\n",
"<docTitle><text>", escape(title), "</text></docTitle>\n", "<docTitle><text>", hescape(title), "</text></docTitle>\n",
"<navMap>\n"]) "<navMap>\n"])
for chapter in chapters: for chapter in chapters:
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
filename = getImageFileName(os.path.join(folder, chapter[1])) filename = getImageFileName(os.path.join(folder, chapter[1]))
navID = folder.replace('/', '_').replace('\\', '_') navID = folder.replace('/', '_').replace('\\', '_')
if options.chapters: if options.chapters:
title = chapterNames[chapter[1]] title = chapternames[chapter[1]]
navID = filename[0].replace('/', '_').replace('\\', '_') navID = filename[0].replace('/', '_').replace('\\', '_')
elif os.path.basename(folder) != "Text": elif os.path.basename(folder) != "Text":
title = chapterNames[os.path.basename(folder)] title = chapternames[os.path.basename(folder)]
f.write("<navPoint id=\"" + navID + "\"><navLabel><text>" + f.write("<navPoint id=\"" + navID + "\"><navLabel><text>" +
escape(title) + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/") + hescape(title) + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/") +
".xhtml\"/></navPoint>\n") ".xhtml\"/></navPoint>\n")
f.write("</navMap>\n</ncx>") f.write("</navMap>\n</ncx>")
f.close() f.close()
def buildNAV(dstdir, title, chapters, chapterNames): def buildNAV(dstdir, title, chapters, chapternames):
navfile = os.path.join(dstdir, 'OEBPS', 'nav.xhtml') navfile = os.path.join(dstdir, 'OEBPS', 'nav.xhtml')
f = open(navfile, "w", encoding='UTF-8') f = open(navfile, "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 html>\n", "<!DOCTYPE html>\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n", "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n",
"<head>\n", "<head>\n",
"<title>" + escape(title) + "</title>\n", "<title>" + hescape(title) + "</title>\n",
"<meta charset=\"utf-8\"/>\n", "<meta charset=\"utf-8\"/>\n",
"</head>\n", "</head>\n",
"<body>\n", "<body>\n",
@@ -245,10 +243,10 @@ def buildNAV(dstdir, title, chapters, chapterNames):
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
filename = getImageFileName(os.path.join(folder, chapter[1])) filename = getImageFileName(os.path.join(folder, chapter[1]))
if options.chapters: if options.chapters:
title = chapterNames[chapter[1]] title = chapternames[chapter[1]]
elif os.path.basename(folder) != "Text": elif os.path.basename(folder) != "Text":
title = chapterNames[os.path.basename(folder)] title = chapternames[os.path.basename(folder)]
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".xhtml\">" + escape(title) + "</a></li>\n") f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".xhtml\">" + hescape(title) + "</a></li>\n")
f.writelines(["</ol>\n", f.writelines(["</ol>\n",
"</nav>\n", "</nav>\n",
"<nav epub:type=\"page-list\">\n", "<nav epub:type=\"page-list\">\n",
@@ -257,10 +255,10 @@ def buildNAV(dstdir, title, chapters, chapterNames):
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
filename = getImageFileName(os.path.join(folder, chapter[1])) filename = getImageFileName(os.path.join(folder, chapter[1]))
if options.chapters: if options.chapters:
title = chapterNames[chapter[1]] title = chapternames[chapter[1]]
elif os.path.basename(folder) != "Text": elif os.path.basename(folder) != "Text":
title = chapterNames[os.path.basename(folder)] title = chapternames[os.path.basename(folder)]
f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".xhtml\">" + escape(title) + "</a></li>\n") f.write("<li><a href=\"" + filename[0].replace("\\", "/") + ".xhtml\">" + hescape(title) + "</a></li>\n")
f.write("</ol>\n</nav>\n</body>\n</html>") f.write("</ol>\n</nav>\n</body>\n</html>")
f.close() f.close()
@@ -278,7 +276,7 @@ def buildOPF(dstdir, title, filelist, cover=None):
"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>", hescape(title), "</dc:title>\n",
"<dc:language>en-US</dc:language>\n", "<dc:language>en-US</dc:language>\n",
"<dc:identifier id=\"BookID\">urn: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:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n"])
@@ -287,20 +285,27 @@ def buildOPF(dstdir, title, filelist, cover=None):
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 property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\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=\"cover\" content=\"cover\"/>\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': if options.iskindle and options.profile != 'Custom':
f.writelines(["<meta name=\"original-resolution\" content=\"", f.writelines(["<meta name=\"fixed-layout\" content=\"true\"/>\n",
"<meta name=\"original-resolution\" content=\"",
str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n", str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
"<meta name=\"book-type\" content=\"comic\"/>\n", "<meta name=\"book-type\" content=\"comic\"/>\n",
"<meta name=\"RegionMagnification\" content=\"true\"/>\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=\"zero-gutter\" content=\"true\"/>\n",
"<meta name=\"zero-margin\" content=\"true\"/>\n", "<meta name=\"zero-margin\" content=\"true\"/>\n",
"<meta name=\"ke-border-color\" content=\"#ffffff\"/>\n", "<meta name=\"ke-border-color\" content=\"#FFFFFF\"/>\n",
"<meta name=\"ke-border-width\" content=\"0\"/>\n"]) "<meta name=\"ke-border-width\" content=\"0\"/>\n"])
if options.kfx:
f.writelines(["<meta name=\"orientation-lock\" content=\"none\"/>\n",
"<meta name=\"region-mag\" content=\"false\"/>\n"])
else:
f.writelines(["<meta name=\"orientation-lock\" content=\"portrait\"/>\n",
"<meta name=\"region-mag\" content=\"true\"/>\n"])
else:
f.writelines(["<meta property=\"rendition:orientation\">portrait</meta>\n",
"<meta property=\"rendition:spread\">portrait</meta>\n",
"<meta property=\"rendition:layout\">pre-paginated</meta>\n"])
f.writelines(["</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ", f.writelines(["</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ",
"media-type=\"application/x-dtbncx+xml\"/>\n", "media-type=\"application/x-dtbncx+xml\"/>\n",
"<item id=\"nav\" href=\"nav.xhtml\" ", "<item id=\"nav\" href=\"nav.xhtml\" ",
@@ -331,10 +336,43 @@ def buildOPF(dstdir, title, filelist, cover=None):
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")
if options.righttoleft: if options.righttoleft:
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
pageside = "right"
else: else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
for entry in reflist: pageside = "left"
f.write("<itemref idref=\"page_" + entry + "\"/>\n") if options.iskindle:
for entry in reflist:
if options.righttoleft:
if entry.endswith("-b"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-right\"/>\n")
pageside = "right"
elif entry.endswith("-c"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-left\"/>\n")
pageside = "right"
else:
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-" +
pageside + "\"/>\n")
if pageside == "right":
pageside = "left"
else:
pageside = "right"
else:
if entry.endswith("-b"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-left\"/>\n")
pageside = "left"
elif entry.endswith("-c"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-right\"/>\n")
pageside = "left"
else:
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-" +
pageside + "\"/>\n")
if pageside == "right":
pageside = "left"
else:
pageside = "right"
else:
for entry in reflist:
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
f.write("</spine>\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'))
@@ -348,7 +386,7 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.close() f.close()
def buildEPUB(path, chapterNames, tomeNumber): def buildEPUB(path, chapternames, tomenumber):
filelist = [] filelist = []
chapterlist = [] chapterlist = []
cover = None cover = None
@@ -361,71 +399,72 @@ def buildEPUB(path, chapterNames, tomeNumber):
"display: block;\n", "display: block;\n",
"margin: 0;\n", "margin: 0;\n",
"padding: 0;\n", "padding: 0;\n",
"}\n",
"#PV {\n",
"position: absolute;\n",
"width: 100%;\n",
"height: 100%;\n",
"top: 0;\n",
"left: 0;\n",
"}\n",
"#PV-T {\n",
"top: 0;\n",
"width: 100%;\n",
"height: 50%;\n",
"}\n",
"#PV-B {\n",
"bottom: 0;\n",
"width: 100%;\n",
"height: 50%;\n",
"}\n",
"#PV-L {\n",
"left: 0;\n",
"width: 49.5%;\n",
"height: 100%;\n",
"float: left;\n",
"}\n",
"#PV-R {\n",
"right: 0;\n",
"width: 49.5%;\n",
"height: 100%;\n",
"float: right;\n",
"}\n",
"#PV-TL {\n",
"top: 0;\n",
"left: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: left;\n",
"}\n",
"#PV-TR {\n",
"top: 0;\n",
"right: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: right;\n",
"}\n",
"#PV-BL {\n",
"bottom: 0;\n",
"left: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: left;\n",
"}\n",
"#PV-BR {\n",
"bottom: 0;\n",
"right: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: right;\n",
"}\n",
".PV-P {\n",
"width: 100%;\n",
"height: 100%;\n",
"top: 0;\n",
"position: absolute;\n",
"display: none;\n",
"}\n"]) "}\n"])
if options.iskindle and options.panelview:
f.writelines(["#PV {\n",
"position: absolute;\n",
"width: 100%;\n",
"height: 100%;\n",
"top: 0;\n",
"left: 0;\n",
"}\n",
"#PV-T {\n",
"top: 0;\n",
"width: 100%;\n",
"height: 50%;\n",
"}\n",
"#PV-B {\n",
"bottom: 0;\n",
"width: 100%;\n",
"height: 50%;\n",
"}\n",
"#PV-L {\n",
"left: 0;\n",
"width: 49.5%;\n",
"height: 100%;\n",
"float: left;\n",
"}\n",
"#PV-R {\n",
"right: 0;\n",
"width: 49.5%;\n",
"height: 100%;\n",
"float: right;\n",
"}\n",
"#PV-TL {\n",
"top: 0;\n",
"left: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: left;\n",
"}\n",
"#PV-TR {\n",
"top: 0;\n",
"right: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: right;\n",
"}\n",
"#PV-BL {\n",
"bottom: 0;\n",
"left: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: left;\n",
"}\n",
"#PV-BR {\n",
"bottom: 0;\n",
"right: 0;\n",
"width: 49.5%;\n",
"height: 50%;\n",
"float: right;\n",
"}\n",
".PV-P {\n",
"width: 100%;\n",
"height: 100%;\n",
"top: 0;\n",
"position: absolute;\n",
"display: none;\n",
"}\n"])
f.close() f.close()
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')): for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
chapter = False chapter = False
@@ -439,9 +478,9 @@ def buildEPUB(path, chapterNames, tomeNumber):
cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'), cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
'cover' + getImageFileName(filelist[-1][1])[1]) 'cover' + getImageFileName(filelist[-1][1])[1])
options.covers.append((image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options, options.covers.append((image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options,
tomeNumber), options.uuid)) tomenumber), options.uuid))
# 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:
chapterlist = [] chapterlist = []
globaldiff = 0 globaldiff = 0
for aChapter in options.chapters: for aChapter in options.chapters:
@@ -453,10 +492,10 @@ def buildEPUB(path, chapterNames, tomeNumber):
pageid -= 1 pageid -= 1
filename = filelist[pageid][1] filename = filelist[pageid][1]
chapterlist.append((filelist[pageid][0].replace('Images', 'Text'), filename)) chapterlist.append((filelist[pageid][0].replace('Images', 'Text'), filename))
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) buildNAV(path, options.title, chapterlist, chapternames)
buildOPF(path, options.title, filelist, cover) buildOPF(path, options.title, filelist, cover)
@@ -542,7 +581,7 @@ def getWorkFolder(afile):
copytree(afile, fullPath) copytree(afile, fullPath)
sanitizePermissions(fullPath) sanitizePermissions(fullPath)
return workdir return workdir
except: except Exception:
rmtree(workdir, True) rmtree(workdir, True)
raise UserWarning("Failed to prepare a workspace.") raise UserWarning("Failed to prepare a workspace.")
elif os.path.isfile(afile): elif os.path.isfile(afile):
@@ -556,16 +595,12 @@ def getWorkFolder(afile):
raise UserWarning("Failed to extract images from PDF file.") raise UserWarning("Failed to extract images from PDF file.")
else: else:
workdir = mkdtemp('', 'KCC-') workdir = mkdtemp('', 'KCC-')
cbx = cbxarchive.CBxArchive(afile) try:
if cbx.isCbxFile(): cbx = comicarchive.ComicArchive(afile)
try: path = cbx.extract(workdir)
path = cbx.extract(workdir) except OSError as e:
except:
rmtree(workdir, True)
raise UserWarning("Failed to extract archive.")
else:
rmtree(workdir, True) rmtree(workdir, True)
raise UserWarning("Failed to detect archive format.") raise UserWarning(e.strerror)
else: else:
raise UserWarning("Failed to open source file/directory.") raise UserWarning("Failed to open source file/directory.")
sanitizePermissions(path) sanitizePermissions(path)
@@ -575,7 +610,7 @@ def getWorkFolder(afile):
return newpath return newpath
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 'Ko' in options.profile and options.format == 'EPUB': if 'Ko' in options.profile and options.format == 'EPUB':
@@ -589,16 +624,16 @@ def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
filename = os.path.join(os.path.abspath(options.output), filename = os.path.join(os.path.abspath(options.output),
os.path.basename(os.path.splitext(srcpath)[0]) + ext) os.path.basename(os.path.splitext(srcpath)[0]) + ext)
elif os.path.isdir(srcpath): elif os.path.isdir(srcpath):
filename = srcpath + tomeNumber + ext filename = srcpath + tomenumber + ext
else: else:
if 'Ko' in options.profile and options.format == 'EPUB': if 'Ko' in options.profile and options.format == 'EPUB':
path = srcpath.split(os.path.sep) path = srcpath.split(os.path.sep)
path[-1] = ''.join(e for e in path[-1].split('.')[0] if e.isalnum()) + tomeNumber + ext path[-1] = ''.join(e for e in path[-1].split('.')[0] if e.isalnum()) + tomenumber + ext
if not path[-1].split('.')[0]: if not path[-1].split('.')[0]:
path[-1] = 'KCCPlaceholder' + tomeNumber + ext path[-1] = 'KCCPlaceholder' + tomenumber + ext
filename = os.path.sep.join(path) filename = os.path.sep.join(path)
else: else:
filename = os.path.splitext(srcpath)[0] + tomeNumber + ext 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]
@@ -608,19 +643,18 @@ def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
return filename return filename
def getComicInfo(path, originalPath): def getComicInfo(path, originalpath):
xmlPath = os.path.join(path, 'ComicInfo.xml') xmlPath = os.path.join(path, 'ComicInfo.xml')
options.authors = ['KCC'] options.authors = ['KCC']
options.remoteCovers = {}
options.chapters = [] options.chapters = []
options.summary = '' options.summary = ''
titleSuffix = '' titleSuffix = ''
if options.title == 'defaulttitle': if options.title == 'defaulttitle':
defaultTitle = True defaultTitle = True
if os.path.isdir(originalPath): if os.path.isdir(originalpath):
options.title = os.path.basename(originalPath) options.title = os.path.basename(originalpath)
else: else:
options.title = os.path.splitext(os.path.basename(originalPath))[0] options.title = os.path.splitext(os.path.basename(originalpath))[0]
else: else:
defaultTitle = False defaultTitle = False
if os.path.exists(xmlPath): if os.path.exists(xmlPath):
@@ -632,7 +666,7 @@ def getComicInfo(path, originalPath):
options.authors = [] options.authors = []
if defaultTitle: if defaultTitle:
if xml.data['Series']: if xml.data['Series']:
options.title = escape(xml.data['Series']) options.title = hescape(xml.data['Series'])
if xml.data['Volume']: if xml.data['Volume']:
titleSuffix += ' V' + xml.data['Volume'].zfill(2) titleSuffix += ' V' + xml.data['Volume'].zfill(2)
if xml.data['Number']: if xml.data['Number']:
@@ -640,35 +674,19 @@ 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(escape(person)) options.authors.append(hescape(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()
else: else:
options.authors = ['KCC'] options.authors = ['KCC']
if 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']: if xml.data['Summary']:
options.summary = escape(xml.data['Summary']) options.summary = hescape(xml.data['Summary'])
os.remove(xmlPath) os.remove(xmlPath)
def getCoversFromMCB(mangaID):
covers = {}
try:
jsonRaw = urlopen(Request('http://mcd.iosphe.re/api/v1/series/' + mangaID + '/',
headers={'User-Agent': 'KindleComicConverter/' + __version__}))
jsonData = loads(jsonRaw.read().decode('utf-8'))
for volume in jsonData['Covers']['a']:
if volume['Side'] == 'front':
covers[int(volume['Volume'])] = volume['Raw']
except Exception:
return {}
return covers
def getDirectorySize(start_path='.'): def getDirectorySize(start_path='.'):
total_size = 0 total_size = 0
for dirpath, _, filenames in os.walk(start_path): for dirpath, _, filenames in os.walk(start_path):
@@ -683,9 +701,9 @@ def getTopMargin(deviceres, size):
return str(round(y, 1)) return str(round(y, 1))
def getPanelViewResolution(imageSize, deviceRes): def getPanelViewResolution(imagesize, deviceres):
scale = float(deviceRes[0]) / float(imageSize[0]) scale = float(deviceres[0]) / float(imagesize[0])
return int(deviceRes[0]), int(scale * imageSize[1]) return int(deviceres[0]), int(scale * imagesize[1])
def getPanelViewSize(deviceres, size): def getPanelViewSize(deviceres, size):
@@ -699,7 +717,7 @@ def sanitizeTree(filetree):
for root, dirs, files in os.walk(filetree, False): for root, dirs, files in os.walk(filetree, False):
for name in files: for name in files:
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
slugified = slugify(splitname[0]) slugified = slugify(splitname[0], False)
while os.path.exists(os.path.join(root, slugified + splitname[1])) and splitname[0].upper()\ while os.path.exists(os.path.join(root, slugified + splitname[1])) and splitname[0].upper()\
!= slugified.upper(): != slugified.upper():
slugified += "A" slugified += "A"
@@ -709,7 +727,7 @@ def sanitizeTree(filetree):
os.replace(key, newKey) os.replace(key, newKey)
for name in dirs: for name in dirs:
tmpName = name tmpName = name
slugified = slugify(name) slugified = slugify(name, True)
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper(): while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
slugified += "A" slugified += "A"
chapterNames[slugified] = tmpName chapterNames[slugified] = tmpName
@@ -749,7 +767,8 @@ def splitDirectory(path):
level = -1 level = -1
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')): for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
for f in files: for f in files:
if f.endswith('.jpg') or f.endswith('.jpeg') or f.endswith('.png') or f.endswith('.gif'): if f.endswith('.jpg') or f.endswith('.jpeg') or f.endswith('.png') or f.endswith('.gif') or \
f.endswith('.webp'):
newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep) newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
if level != -1 and level != newLevel: if level != -1 and level != newLevel:
level = 0 level = 0
@@ -804,19 +823,19 @@ def splitProcess(path, mode):
return output return output
def detectCorruption(tmpPath, orgPath): def detectCorruption(tmppath, orgpath):
imageNumber = 0 imageNumber = 0
imageSmaller = 0 imageSmaller = 0
alreadyProcessed = False alreadyProcessed = False
for root, _, files in os.walk(tmpPath, False): for root, _, files in os.walk(tmppath, False):
for name in files: for name in files:
if getImageFileName(name) is not None: if getImageFileName(name) is not None:
if not alreadyProcessed and getImageFileName(name)[0].endswith('-kcc'): if not alreadyProcessed and getImageFileName(name)[0].endswith('-kcc'):
alreadyProcessed = True alreadyProcessed = True
path = os.path.join(root, name) path = os.path.join(root, name)
pathOrg = orgPath + path.split('OEBPS' + os.path.sep + 'Images')[1] pathOrg = orgpath + path.split('OEBPS' + os.path.sep + 'Images')[1]
if os.path.getsize(path) == 0: if os.path.getsize(path) == 0:
rmtree(os.path.join(tmpPath, '..', '..'), True) rmtree(os.path.join(tmppath, '..', '..'), True)
raise RuntimeError('Image file %s is corrupted.' % pathOrg) raise RuntimeError('Image file %s is corrupted.' % pathOrg)
try: try:
img = Image.open(path) img = Image.open(path)
@@ -827,11 +846,11 @@ def detectCorruption(tmpPath, orgPath):
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]: if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
imageSmaller += 1 imageSmaller += 1
except Exception as err: except Exception as err:
rmtree(os.path.join(tmpPath, '..', '..'), True) rmtree(os.path.join(tmppath, '..', '..'), True)
if 'decoder' in str(err) and 'not available' in str(err): if 'decoder' in str(err) and 'not available' in str(err):
raise RuntimeError('Pillow was compiled without JPG and/or PNG decoder.') raise RuntimeError('Pillow was compiled without JPG and/or PNG decoder.')
else: else:
raise RuntimeError('Image file %s is corrupted.' % pathOrg) raise RuntimeError('Image file %s is corrupted. Error: %s' % (pathOrg, str(err)))
else: else:
os.remove(os.path.join(root, name)) os.remove(os.path.join(root, name))
if alreadyProcessed: if alreadyProcessed:
@@ -856,25 +875,28 @@ def createNewTome():
return tomePath, tomePathRoot return tomePath, tomePathRoot
def slugify(value): def slugify(value, isdir):
value = slugifyExt(value) if isdir:
value = slugifyExt(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
else:
value = slugifyExt(value).strip('.')
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2)) value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
return value return value
def makeZIP(zipFilename, baseDir, isEPUB=False): def makeZIP(zipfilename, basedir, isepub=False):
zipFilename = os.path.abspath(zipFilename) + '.zip' zipfilename = os.path.abspath(zipfilename) + '.zip'
zipOutput = ZipFile(zipFilename, 'w', ZIP_DEFLATED) zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
if isEPUB: if isepub:
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED) zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
for dirpath, _, filenames in os.walk(baseDir): for dirpath, _, filenames in os.walk(basedir):
for name in filenames: for name in filenames:
path = os.path.normpath(os.path.join(dirpath, name)) path = os.path.normpath(os.path.join(dirpath, name))
aPath = os.path.normpath(os.path.join(dirpath.replace(baseDir, ''), name)) aPath = os.path.normpath(os.path.join(dirpath.replace(basedir, ''), name))
if os.path.isfile(path): if os.path.isfile(path):
zipOutput.write(path, aPath) zipOutput.write(path, aPath)
zipOutput.close() zipOutput.close()
return zipFilename return zipfilename
def makeParser(): def makeParser():
@@ -887,8 +909,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, K34, K578, KDX, KPW, KV, KoMT, KoG, KoGHD," help="Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KV, KO, KoMT, KoG,"
" KoA, KoAHD, KoAH2O, KoAO) [Default=KV]") " KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoF) [Default=KV]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (right-to-left reading and splitting)") help="Manga style (right-to-left reading and splitting)")
mainOptions.add_option("-q", "--hq", action="store_true", dest="hq", default=False, mainOptions.add_option("-q", "--hq", action="store_true", dest="hq", default=False,
@@ -903,7 +925,7 @@ def makeParser():
outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle", outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
help="Comic title [Default=filename or directory name]") help="Comic title [Default=filename or directory name]")
outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto", outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto",
help="Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]") help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX) [Default=Auto]")
outputOptions.add_option("-b", "--batchsplit", type="int", dest="batchsplit", default="0", outputOptions.add_option("-b", "--batchsplit", type="int", dest="batchsplit", default="0",
help="Split output into multiple files. 0: Don't split 1: Automatic mode " help="Split output into multiple files. 0: Don't split 1: Automatic mode "
"2: Consider every subdirectory as separate volume [Default=0]") "2: Consider every subdirectory as separate volume [Default=0]")
@@ -950,21 +972,22 @@ def checkOptions():
options.panelview = True options.panelview = True
options.iskindle = False options.iskindle = False
options.bordersColor = None options.bordersColor = None
options.kfx = False
if options.format == 'Auto': if options.format == 'Auto':
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KV']: if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KV', 'KO']:
options.format = 'MOBI' options.format = 'MOBI'
elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O', 'KoAO']: elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O', 'KoAO']:
options.format = 'EPUB' options.format = 'EPUB'
elif options.profile in ['KDX']: elif options.profile in ['KDX']:
options.format = 'CBZ' options.format = 'CBZ'
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KV']: if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KV', 'KO']:
options.iskindle = True 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:
options.bordersColor = 'black' options.bordersColor = 'black'
# Splitting MOBI is not optional # Splitting MOBI is not optional
if options.format == 'MOBI' and options.batchsplit != 2: if (options.format == 'MOBI' or options.format == 'KFX') and options.batchsplit != 2:
options.batchsplit = 1 options.batchsplit = 1
# Older Kindle models don't support Panel View. # Older Kindle models don't support Panel View.
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'K34' or options.profile == 'KDX': if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'K34' or options.profile == 'KDX':
@@ -986,6 +1009,11 @@ def checkOptions():
# 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
# KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin
if options.format == 'KFX':
options.format = 'EPUB'
options.kfx = True
options.panelview = False
# Override profile data # Override profile data
if options.customwidth != 0 or options.customheight != 0: if options.customwidth != 0 or options.customheight != 0:
X = image.ProfileData.Profiles[options.profile][1][0] X = image.ProfileData.Profiles[options.profile][1][0]
@@ -1003,21 +1031,17 @@ def checkOptions():
def checkTools(source): def checkTools(source):
source = source.upper() source = source.upper()
if source.endswith('.CBR') or source.endswith('.RAR'): if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) source.endswith('.ZIP') or source.endswith('.CBZ'):
rarExitCode = rarExitCode.wait() process = Popen('7z', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
if rarExitCode != 0 and rarExitCode != 7: process.communicate()
print('ERROR: UnRAR is missing!') if process.returncode != 0 and process.returncode != 7:
exit(1) print('ERROR: 7z is missing!')
elif source.endswith('.CB7') or source.endswith('.7Z'):
sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
sevenzaExitCode = sevenzaExitCode.wait()
if sevenzaExitCode != 0 and sevenzaExitCode != 7:
print('ERROR: 7za is missing!')
exit(1) exit(1)
if options.format == 'MOBI': if options.format == 'MOBI':
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
if kindleGenExitCode.wait() != 0: kindleGenExitCode.communicate()
if kindleGenExitCode.returncode != 0:
print('ERROR: KindleGen is missing!') print('ERROR: KindleGen is missing!')
exit(1) exit(1)
@@ -1036,13 +1060,13 @@ def checkPre(source):
try: try:
with TemporaryFile(prefix='KCC-', dir=src): with TemporaryFile(prefix='KCC-', dir=src):
pass pass
except: except Exception:
raise UserWarning("Target directory is not writable.") raise UserWarning("Target directory is not writable.")
def makeBook(source, qtGUI=None): def makeBook(source, qtgui=None):
global GUI global GUI
GUI = qtGUI GUI = qtgui
if GUI: if GUI:
GUI.progressBarTick.emit('1') GUI.progressBarTick.emit('1')
else: else:
@@ -1058,7 +1082,7 @@ def makeBook(source, qtGUI=None):
y = 1024 y = 1024
else: else:
y = image.ProfileData.Profiles[options.profile][1][1] y = image.ProfileData.Profiles[options.profile][1][1]
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtGUI) comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui)
print("Processing images...") print("Processing images...")
if GUI: if GUI:
GUI.progressBarTick.emit('Processing images') GUI.progressBarTick.emit('Processing images')
@@ -1189,9 +1213,9 @@ def makeMOBIWorker(item):
return [kindlegenErrorCode, kindlegenError, item] return [kindlegenErrorCode, kindlegenError, item]
def makeMOBI(work, qtGUI=None): def makeMOBI(work, qtgui=None):
global GUI, makeMOBIWorkerPool, makeMOBIWorkerOutput global GUI, makeMOBIWorkerPool, makeMOBIWorkerOutput
GUI = qtGUI GUI = qtgui
makeMOBIWorkerOutput = [] makeMOBIWorkerOutput = []
availableMemory = virtual_memory().total / 1000000000 availableMemory = virtual_memory().total / 1000000000
if availableMemory <= 2: if availableMemory <= 2:

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -93,6 +93,7 @@ def splitImageTick(output):
splitWorkerPool.terminate() splitWorkerPool.terminate()
# noinspection PyUnboundLocalVariable
def splitImage(work): def splitImage(work):
try: try:
path = work[0] path = work[0]
@@ -140,9 +141,7 @@ def splitImage(work):
if opt.debug: if opt.debug:
for panel in panelsProcessed: for panel in panelsProcessed:
# noinspection PyUnboundLocalVariable
draw.rectangle([(0, panel[0]), (widthImg, panel[1])], (0, 255, 0, 128), (0, 0, 255, 255)) draw.rectangle([(0, panel[0]), (widthImg, panel[1])], (0, 255, 0, 128), (0, 0, 255, 255))
# noinspection PyUnboundLocalVariable
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg) debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG') debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
@@ -185,7 +184,7 @@ def splitImage(work):
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def main(argv=None, qtGUI=None): def main(argv=None, qtgui=None):
global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False) parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False)
mainOptions = OptionGroup(parser, "MANDATORY") mainOptions = OptionGroup(parser, "MANDATORY")
@@ -203,8 +202,8 @@ def main(argv=None, qtGUI=None):
parser.add_option_group(mainOptions) parser.add_option_group(mainOptions)
parser.add_option_group(otherOptions) parser.add_option_group(otherOptions)
options, args = parser.parse_args(argv) options, args = parser.parse_args(argv)
if qtGUI: if qtgui:
GUI = qtGUI GUI = qtgui
else: else:
GUI = None GUI = None
if len(args) != 1: if len(args) != 1:

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
import os
from psutil import Popen
from shutil import move
from subprocess import STDOUT, PIPE
from xml.dom.minidom import parseString
from xml.parsers.expat import ExpatError
class ComicArchive:
def __init__(self, filepath):
self.filepath = filepath
self.type = None
if not os.path.isfile(self.filepath):
raise OSError('File not found.')
process = Popen('7z l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True)
for line in process.stdout:
if b'Type =' in line:
self.type = line.rstrip().decode().split(' = ')[1].upper()
break
process.communicate()
if process.returncode != 0:
raise OSError('Archive is corrupted or encrypted.')
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
raise OSError('Unsupported archive format.')
def extract(self, targetdir):
if not os.path.isdir(targetdir):
raise OSError('Target directory don\'t exist.')
process = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' +
self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
process.communicate()
if process.returncode != 0:
raise OSError('Failed to extract archive.')
tdir = os.listdir(targetdir)
if 'ComicInfo.xml' in tdir:
tdir.remove('ComicInfo.xml')
if len(tdir) == 1 and os.path.isdir(os.path.join(targetdir, tdir[0])):
for f in os.listdir(os.path.join(targetdir, tdir[0])):
move(os.path.join(targetdir, tdir[0], f), targetdir)
os.rmdir(os.path.join(targetdir, tdir[0]))
return targetdir
def addFile(self, sourcefile):
if self.type in ['RAR', 'RAR5']:
raise NotImplementedError
process = Popen('7z a -y "' + self.filepath + '" "' + sourcefile + '"',
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
process.communicate()
if process.returncode != 0:
raise OSError('Failed to add the file.')
def extractMetadata(self):
process = Popen('7z x -y -so "' + self.filepath + '" ComicInfo.xml',
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
xml = process.communicate()
if process.returncode != 0:
raise OSError('Failed to extract archive.')
try:
return parseString(xml[0])
except ExpatError:
return None

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks # Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks
# Changes for KCC Copyright (C) 2014-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Changes for KCC Copyright (C) 2014-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@ import shutil
class DualMetaFixException(Exception): class DualMetaFixException(Exception):
pass pass
# palm database offset constants # palm database offset constants
number_of_pdb_records = 76 number_of_pdb_records = 76
first_pdb_record = 78 first_pdb_record = 78

View File

@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Alex Yatskov # Copyright (C) 2010 Alex Yatskov
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com> # Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
# Copyright (c) 2016 Alberto Planas <aplanas@gmail.com> # Copyright (c) 2016 Alberto Planas <aplanas@gmail.com>
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@@ -18,12 +20,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os import os
from io import BytesIO
from urllib.request import Request, urlopen
from urllib.parse import quote
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
from .shared import md5Checksum from .shared import md5Checksum
from . import __version__
class ProfileData: class ProfileData:
@@ -84,7 +82,8 @@ class ProfileData:
'K578': ("Kindle", (600, 800), Palette16, 1.8), 'K578': ("Kindle", (600, 800), Palette16, 1.8),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8), 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8), 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8), 'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
'KO': ("Kindle Oasis 2", (1264, 1680), Palette16, 1.8),
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8), 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8), 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8), 'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
@@ -92,12 +91,14 @@ class ProfileData:
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8), 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8),
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8), 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8),
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8), 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
'OTHER': ("Other", (0, 0), Palette16, 1.8), 'OTHER': ("Other", (0, 0), Palette16, 1.8),
} }
class ComicPageParser: class ComicPageParser:
def __init__(self, source, options): def __init__(self, source, options):
Image.MAX_IMAGE_PIXELS = int(2048 * 2048 * 2048 // 4 // 3)
self.opt = options self.opt = options
self.source = source self.source = source
self.size = self.opt.profileData[1] self.size = self.opt.profileData[1]
@@ -272,17 +273,17 @@ class ComicPage:
method = Image.BICUBIC method = Image.BICUBIC
else: else:
method = Image.LANCZOS method = Image.LANCZOS
if self.opt.stretch: if self.opt.stretch or (self.opt.kfx and ('-KCC-B' in self.targetPath or '-KCC-C' in self.targetPath)):
self.image = self.image.resize(self.size, method) self.image = self.image.resize(self.size, method)
elif self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not self.opt.upscale: elif self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not self.opt.upscale:
if self.opt.format == 'CBZ': if self.opt.format == 'CBZ' or self.opt.kfx:
borderw = int((self.size[0] - self.image.size[0]) / 2) borderw = int((self.size[0] - self.image.size[0]) / 2)
borderh = int((self.size[1] - self.image.size[1]) / 2) borderh = int((self.size[1] - self.image.size[1]) / 2)
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill) self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]: if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
self.image = ImageOps.fit(self.image, self.size, method=Image.BICUBIC, centering=(0.5, 0.5)) self.image = ImageOps.fit(self.image, self.size, method=Image.BICUBIC, centering=(0.5, 0.5))
else: else:
if self.opt.format == 'CBZ': if self.opt.format == 'CBZ' or self.opt.kfx:
ratioDev = float(self.size[0]) / float(self.size[1]) ratioDev = float(self.size[0]) / float(self.size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0] diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
@@ -298,59 +299,51 @@ class ComicPage:
if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]: if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]:
self.image.thumbnail(self.size, Image.LANCZOS) self.image.thumbnail(self.size, Image.LANCZOS)
def getBoundingBox(self, tmpImg): def getBoundingBox(self, tmptmg):
min_margin = [int(0.005 * i + 0.5) for i in tmpImg.size] min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
max_margin = [int(0.1 * i + 0.5) for i in tmpImg.size] max_margin = [int(0.1 * i + 0.5) for i in tmptmg.size]
bbox = tmpImg.getbbox() bbox = tmptmg.getbbox()
bbox = ( bbox = (
max(0, min(max_margin[0], bbox[0] - min_margin[0])), max(0, min(max_margin[0], bbox[0] - min_margin[0])),
max(0, min(max_margin[1], bbox[1] - min_margin[1])), max(0, min(max_margin[1], bbox[1] - min_margin[1])),
min(tmpImg.size[0], min(tmptmg.size[0],
max(tmpImg.size[0] - max_margin[0], bbox[2] + min_margin[0])), max(tmptmg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
min(tmpImg.size[1], min(tmptmg.size[1],
max(tmpImg.size[1] - max_margin[1], bbox[3] + min_margin[1])), max(tmptmg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
) )
return bbox return bbox
def cropPageNumber(self, power): def cropPageNumber(self, power):
if self.fill != 'white': if self.fill != 'white':
tmpImg = self.image.convert(mode='L') tmptmg = self.image.convert(mode='L')
else: else:
tmpImg = ImageOps.invert(self.image.convert(mode='L')) tmptmg = ImageOps.invert(self.image.convert(mode='L'))
tmpImg = tmpImg.point(lambda x: x and 255) tmptmg = tmptmg.point(lambda x: x and 255)
tmpImg = tmpImg.filter(ImageFilter.MinFilter(size=3)) tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=5)) tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x) tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image self.image = self.image.crop(tmptmg.getbbox()) if tmptmg.getbbox() else self.image
def cropMargin(self, power): def cropMargin(self, power):
if self.fill != 'white': if self.fill != 'white':
tmpImg = self.image.convert(mode='L') tmptmg = self.image.convert(mode='L')
else: else:
tmpImg = ImageOps.invert(self.image.convert(mode='L')) tmptmg = ImageOps.invert(self.image.convert(mode='L'))
tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=3)) tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x) tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(self.getBoundingBox(tmpImg)) if tmpImg.getbbox() else self.image self.image = self.image.crop(self.getBoundingBox(tmptmg)) if tmptmg.getbbox() else self.image
class Cover: class Cover:
def __init__(self, source, target, opt, tomeNumber): def __init__(self, source, target, opt, tomeid):
self.options = opt self.options = opt
self.source = source self.source = source
self.target = target self.target = target
if tomeNumber == 0: if tomeid == 0:
self.tomeNumber = 1 self.tomeid = 1
else: else:
self.tomeNumber = tomeNumber self.tomeid = tomeid
if self.tomeNumber in self.options.remoteCovers: self.image = Image.open(source)
try:
source = urlopen(Request(quote(self.options.remoteCovers[self.tomeNumber]).replace('%3A', ':', 1),
headers={'User-Agent': 'KindleComicConverter/' + __version__})).read()
self.image = Image.open(BytesIO(source))
except Exception:
self.image = Image.open(source)
else:
self.image = Image.open(source)
self.process() self.process()
def process(self): def process(self):

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -18,14 +18,9 @@
import os import os
from xml.dom.minidom import parse, Document from xml.dom.minidom import parse, Document
from re import compile
from zipfile import is_zipfile, ZipFile, ZIP_DEFLATED
from subprocess import STDOUT, PIPE
from psutil import Popen
from tempfile import mkdtemp from tempfile import mkdtemp
from shutil import rmtree from shutil import rmtree
from .shared import removeFromZIP, check7ZFile as is_7zfile from . import comicarchive
from . import rarfile
class MetadataParser: class MetadataParser:
@@ -39,50 +34,20 @@ class MetadataParser:
'Inkers': [], 'Inkers': [],
'Colorists': [], 'Colorists': [],
'Summary': '', 'Summary': '',
'MUid': '',
'Bookmarks': []} 'Bookmarks': []}
self.rawdata = None self.rawdata = None
self.compressor = None self.format = None
if self.source.endswith('.xml'): if self.source.endswith('.xml') and os.path.exists(self.source):
self.rawdata = parse(self.source) self.rawdata = parse(self.source)
elif not self.source.endswith('.xml'):
try:
cbx = comicarchive.ComicArchive(self.source)
self.rawdata = cbx.extractMetadata()
self.format = cbx.type
except OSError as e:
raise UserWarning(e.strerror)
if self.rawdata:
self.parseXML() self.parseXML()
else:
if is_zipfile(self.source):
self.compressor = 'zip'
with ZipFile(self.source) as zip_file:
for member in zip_file.namelist():
if member != 'ComicInfo.xml':
continue
with zip_file.open(member) as xml_file:
self.rawdata = parse(xml_file)
elif rarfile.is_rarfile(self.source):
self.compressor = 'rar'
with rarfile.RarFile(self.source) as rar_file:
for member in rar_file.namelist():
if member != 'ComicInfo.xml':
continue
with rar_file.open(member) as xml_file:
self.rawdata = parse(xml_file)
elif is_7zfile(self.source):
self.compressor = '7z'
workdir = mkdtemp('', 'KCC-')
tmpXML = os.path.join(workdir, 'ComicInfo.xml')
output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"',
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
extracted = False
for line in output.stdout:
if b"Everything is Ok" in line or b"No files to process" in line:
extracted = True
if not extracted:
rmtree(workdir)
raise OSError('Failed to extract 7ZIP file.')
if os.path.isfile(tmpXML):
self.rawdata = parse(tmpXML)
rmtree(workdir)
else:
raise OSError('Failed to detect archive format.')
if self.rawdata:
self.parseXML()
def parseXML(self): def parseXML(self):
if len(self.rawdata.getElementsByTagName('Series')) != 0: if len(self.rawdata.getElementsByTagName('Series')) != 0:
@@ -99,11 +64,6 @@ class MetadataParser:
self.data[field + 's'].append(person) self.data[field + 's'].append(person)
self.data[field + 's'] = list(set(self.data[field + 's'])) self.data[field + 's'] = list(set(self.data[field + 's']))
self.data[field + 's'].sort() self.data[field + 's'].sort()
if len(self.rawdata.getElementsByTagName('ScanInformation')) != 0:
coverId = compile('(MCD\\()(\\d+)(\\))')\
.search(self.rawdata.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue)
if coverId:
self.data['MUid'] = coverId.group(2)
if len(self.rawdata.getElementsByTagName('Page')) != 0: if len(self.rawdata.getElementsByTagName('Page')) != 0:
for page in self.rawdata.getElementsByTagName('Page'): for page in self.rawdata.getElementsByTagName('Page'):
if 'Bookmark' in page.attributes and 'Image' in page.attributes: if 'Bookmark' in page.attributes and 'Image' in page.attributes:
@@ -116,8 +76,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'])], ['Summary', self.data['Summary']], ['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]): if self.rawdata.getElementsByTagName(row[0]):
node = self.rawdata.getElementsByTagName(row[0])[0] node = self.rawdata.getElementsByTagName(row[0])[0]
if row[1]: if row[1]:
@@ -138,8 +97,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'])], ['Summary', self.data['Summary']], ['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]):
['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])
root.appendChild(main) root.appendChild(main)
@@ -154,20 +112,9 @@ class MetadataParser:
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')
if is_zipfile(self.source): try:
removeFromZIP(self.source, 'ComicInfo.xml') cbx = comicarchive.ComicArchive(self.source)
with ZipFile(self.source, mode='a', compression=ZIP_DEFLATED) as zip_file: cbx.addFile(tmpXML)
zip_file.write(tmpXML, arcname=tmpXML.split(os.sep)[-1]) except OSError as e:
elif rarfile.is_rarfile(self.source): raise UserWarning(e.strerror)
raise NotImplementedError
elif is_7zfile(self.source):
output = Popen('7za a "' + self.source + '" "' + tmpXML + '"',
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
extracted = False
for line in output.stdout:
if b"Everything is Ok" in line:
extracted = True
if not extracted:
rmtree(workdir)
raise OSError('Failed to modify 7ZIP file.')
rmtree(workdir) rmtree(workdir)

View File

@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Based upon the code snippet by Ned Batchelder # Based upon the code snippet by Ned Batchelder
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html) # (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
@@ -25,17 +27,16 @@ from string import ascii_uppercase, digits
class PdfJpgExtract: class PdfJpgExtract:
def __init__(self, origFileName): def __init__(self, fname):
self.origFileName = origFileName self.fname = fname
self.filename = os.path.splitext(origFileName) self.filename = os.path.splitext(fname)
# noinspection PyUnusedLocal self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for _ 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
def extract(self): def extract(self):
pdf = open(self.origFileName, "rb").read() pdf = open(self.fname, "rb").read()
startmark = b"\xff\xd8" startmark = b"\xff\xd8"
startfix = 0 startfix = 0
endmark = b"\xff\xd9" endmark = b"\xff\xd9"

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -20,9 +22,6 @@ import os
from hashlib import md5 from hashlib import md5
from html.parser import HTMLParser from html.parser import HTMLParser
from distutils.version import StrictVersion from distutils.version import StrictVersion
from shutil import rmtree, copy
from tempfile import mkdtemp
from zipfile import ZipFile, ZIP_DEFLATED
from re import split from re import split
from traceback import format_tb from traceback import format_tb
@@ -48,7 +47,7 @@ class HTMLStripper(HTMLParser):
def getImageFileName(imgfile): def getImageFileName(imgfile):
name, ext = os.path.splitext(imgfile) name, ext = os.path.splitext(imgfile)
ext = ext.lower() ext = ext.lower()
if name.startswith('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg' and ext != '.gif'): if name.startswith('.') or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp']:
return None return None
return [name, ext] return [name, ext]
@@ -73,8 +72,8 @@ def walkLevel(some_dir, level=1):
del dirs[:] del dirs[:]
def md5Checksum(filePath): def md5Checksum(fpath):
with open(filePath, 'rb') as fh: with open(fpath, 'rb') as fh:
m = md5() m = md5()
while True: while True:
data = fh.read(8192) data = fh.read(8192)
@@ -84,38 +83,19 @@ def md5Checksum(filePath):
return m.hexdigest() return m.hexdigest()
def check7ZFile(filePath):
with open(filePath, 'rb') as fh:
header = fh.read(6)
return header == b"7z\xbc\xaf'\x1c"
def removeFromZIP(zipfname, *filenames):
tempdir = mkdtemp('', 'KCC-')
try:
tempname = os.path.join(tempdir, 'KCC.zip')
with ZipFile(zipfname, 'r') as zipread:
with ZipFile(tempname, 'w', compression=ZIP_DEFLATED) as zipwrite:
for item in zipread.infolist():
if item.filename not in filenames:
zipwrite.writestr(item, zipread.read(item.filename))
copy(tempname, zipfname)
finally:
rmtree(tempdir, True)
def sanitizeTrace(traceback): def sanitizeTrace(traceback):
return ''.join(format_tb(traceback))\ return ''.join(format_tb(traceback))\
.replace('C:/projects/kcc/', '') \ .replace('C:/projects/kcc/', '')\
.replace('c:/projects/kcc/', '') \ .replace('c:/projects/kcc/', '')\
.replace('C:/python36-x64/', '')\ .replace('C:/python37-x64/', '')\
.replace('c:/python36-x64/', '')\ .replace('c:/python37-x64/', '')\
.replace('C:\\projects\\kcc\\', '') \ .replace('C:\\projects\\kcc\\', '')\
.replace('c:\\projects\\kcc\\', '') \ .replace('c:\\projects\\kcc\\', '')\
.replace('C:\\python36-x64\\', '')\ .replace('C:\\python37-x64\\', '')\
.replace('c:\\python36-x64\\', '') .replace('c:\\python37-x64\\', '')
# noinspection PyUnresolvedReferences
def dependencyCheck(level): def dependencyCheck(level):
missing = [] missing = []
if level > 2: if level > 2:
@@ -143,11 +123,11 @@ def dependencyCheck(level):
except ImportError: except ImportError:
missing.append('python-slugify 1.2.1+') missing.append('python-slugify 1.2.1+')
try: try:
from PIL import PILLOW_VERSION as pillowVersion from PIL import __version__ as pillowVersion
if StrictVersion('4.0.0') > StrictVersion(pillowVersion): if StrictVersion('5.2.0') > StrictVersion(pillowVersion):
missing.append('Pillow 4.0.0+') missing.append('Pillow 5.2.0+')
except ImportError: except ImportError:
missing.append('Pillow 4.0.0+') missing.append('Pillow 5.2.0+')
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)

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -30,15 +30,15 @@ def start():
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1" os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1"
KCCAplication = KCC_gui.QApplicationMessaging(sys.argv) KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
if KCCAplication.isRunning(): if KCCAplication.isRunning():
if len(sys.argv) > 1: for i in range(1, len(sys.argv)):
KCCAplication.sendMessage(sys.argv[1]) KCCAplication.sendMessage(sys.argv[i])
else: else:
KCCAplication.sendMessage('ARISE') KCCAplication.sendMessage('ARISE')
else: else:
KCCWindow = KCC_gui.QMainWindowKCC() KCCWindow = KCC_gui.QMainWindowKCC()
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow) KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
if len(sys.argv) > 1: for i in range(1, len(sys.argv)):
KCCUI.handleMessage(sys.argv[1]) KCCUI.handleMessage(sys.argv[i])
sys.exit(KCCAplication.exec_()) sys.exit(KCCAplication.exec_())

View File

@@ -1,4 +0,0 @@
kindlecomicconverter: binary-without-manpage usr/bin/kcc
kindlecomicconverter: wrong-name-for-changelog-of-native-package usr/share/doc/kindlecomicconverter/changelog.Debian.gz
kindlecomicconverter: file-missing-in-md5sums usr/share/doc/kindlecomicconverter/changelog.Debian.gz
kindlecomicconverter: hardening-no-relro usr/bin/kcc

View File

@@ -1,11 +0,0 @@
[Desktop Entry]
Type=Application
Version=1.0
Name=Kindle Comic Converter
GenericName=Kindle Comic Converter
Comment=Comic and Manga converter for e-book readers
Icon=/usr/share/kindlecomicconverter/comic2ebook.png
Exec=/usr/bin/kcc %f
Terminal=false
Categories=Graphics;
MimeType=application/zip;application/x-rar;application/x-7z-compressed;

Binary file not shown.

BIN
other/osx/7z Executable file

Binary file not shown.

BIN
other/osx/7z.so Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -30,7 +30,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>MacOS/Kindle Comic Converter</string> <string>MacOS/Kindle Comic Converter</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>KindleComicConverter 5.4.1, written 2012-2017 by Ciro Mattia Gonano and Pawel Jastrzebski</string> <string>KindleComicConverter 5.5.1, written 2012-2019 by Ciro Mattia Gonano and Pawel Jastrzebski</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>comic2ebook.icns</string> <string>comic2ebook.icns</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@@ -42,11 +42,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.4.1</string> <string>5.5.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>5.4.1</string> <string>5.5.1</string>
<key>LSEnvironment</key> <key>LSEnvironment</key>
<dict> <dict>
<key>PATH</key> <key>PATH</key>
@@ -55,7 +55,7 @@
<key>LSHasLocalizedDisplayName</key> <key>LSHasLocalizedDisplayName</key>
<false/> <false/>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>10.10.0</string> <string>10.12.0</string>
<key>NSAppleScriptEnabled</key> <key>NSAppleScriptEnabled</key>
<false/> <false/>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>

BIN
other/osx/Rar.so Normal file

Binary file not shown.

Binary file not shown.

BIN
other/windows/7z.dll Normal file

Binary file not shown.

BIN
other/windows/7z.exe Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,53 +1,19 @@
****** ***** ****** UnRAR - free utility for RAR archives
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
****** ******* ****** License for use and distribution of
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
** ** ** ** ** ** FREEWARE version
~~~~~~~~~~~~~~~~
The UnRAR utility is freeware. This means:
1. All copyrights to RAR and the utility UnRAR are exclusively
owned by the author - Alexander Roshal.
2. The UnRAR utility may be freely distributed. It is allowed
to distribute UnRAR inside of other software packages.
3. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS".
NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT
YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS,
DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
OR MISUSING THIS SOFTWARE.
4. Neither RAR binary code, WinRAR binary code, UnRAR source or UnRAR
binary code may be used or reverse engineered to re-create the RAR
compression algorithm, which is proprietary, without written
permission of the author.
5. If you don't agree with terms of the license you must remove
UnRAR files from your storage devices and cease to use the
utility.
Thank you for your interest in RAR and UnRAR.
Alexander L. Roshal
7-Zip 7-Zip
~~~~~ ~~~~~
License for use and distribution License for use and distribution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7-Zip Copyright (C) 1999-2012 Igor Pavlov. 7-Zip Copyright (C) 1999-2018 Igor Pavlov.
Licenses for files are: The licenses for files are:
1) 7z.dll: GNU LGPL + unRAR restriction 1) 7z.dll:
2) All other files: GNU LGPL - The "GNU LGPL" as main license for most of the code
- The "GNU LGPL" with "unRAR license restriction" for some code
The GNU LGPL + unRAR restriction means that you must follow both - The "BSD 3-clause License" for some code
GNU LGPL rules and unRAR restriction rules. 2) All other files: the "GNU LGPL".
Redistributions in binary form must reproduce related license information from this file.
Note: Note:
You can use 7-Zip on any computer, including a computer in a commercial You can use 7-Zip on any computer, including a computer in a commercial
@@ -71,8 +37,41 @@
http://www.gnu.org/ http://www.gnu.org/
unRAR restriction
-----------------
BSD 3-clause License
--------------------
The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression.
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
that also uses the "BSD 3-clause License":
----
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----
unRAR license restriction
-------------------------
The decompression engine for RAR archives was developed using source The decompression engine for RAR archives was developed using source
code of unRAR program. code of unRAR program.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
PyQt5>=5.6.0 PyQt5>=5.6.0
Pillow>=4.0.0 Pillow>=5.2.0
psutil>=5.0.0 psutil>=5.0.0
python-slugify>=1.2.1 python-slugify>=1.2.1,<3.0.0
raven>=6.0.0 raven>=6.0.0

View File

@@ -1,4 +0,0 @@
@echo off
verpatch\lib\win\verpatch dist\KCC.exe %1 /va /pv %1 /s product "Kindle Comic Converter" /s description "Kindle Comic Converter" /s copyright "Copyright (C) 2012-2017 Ciro Mattia Gonano and Pawel Jastrzebski"
"C:\Program Files (x86)\Windows Kits\8.1\bin\x64\signtool.exe" sign /f "%APPVEYOR_BUILD_FOLDER%\other\windows\Cert.pfx" /p "%CERT_PASS%" /t http://time.certum.pl /d "Kindle Comic Converter" /du "http://kcc.iosphe.re/" dist/KCC.exe
iscc /SSignTool="""C:\Program Files (x86)\Windows Kits\8.1\bin\x64\signtool.exe"" sign /f ""%APPVEYOR_BUILD_FOLDER%\other\windows\Cert.pfx"" /p ""%CERT_PASS%"" /t http://time.certum.pl $p" kcc.iss >nul 2>&1

View File

@@ -1,11 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
pip/pyinstaller build script for KCC. pip/pyinstaller build script for KCC.
Install as Python package: Install as Python package:
python3 setup.py install python3 setup.py install
Create EXE/APP/DEB: Create EXE/APP:
python3 setup.py build_binary python3 setup.py build_binary
""" """
@@ -36,41 +37,23 @@ class BuildBinaryCommand(distutils.cmd.Command):
VERSION = __version__ VERSION = __version__
if sys.platform == 'darwin': if sys.platform == 'darwin':
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py') os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources') os.makedirs('dist/Kindle Comic Converter.app/Contents/Resources/Codecs')
shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/osx/7z', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/7z.so', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/Rar.so', 'dist/Kindle Comic Converter.app/Contents/Resources/Codecs')
shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents') shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents')
shutil.copy('LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/windows/Additional-LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/windows/Additional-LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/unrar', 0o777) os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7z', 0o777)
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7za', 0o777)
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg') os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
exit(0) exit(0)
elif sys.platform == 'win32': elif sys.platform == 'win32':
os.system('pyinstaller -y -F -i icons\comic2ebook.ico -n KCC -w --noupx kcc.py') os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC -w --noupx kcc.py')
if os.getenv('APPVEYOR'):
if len(VERSION) == 3:
VERSION = VERSION + '.0'
os.system('setup.bat ' + VERSION)
exit(0) exit(0)
else: else:
os.system('pyinstaller -y -F kcc.py')
os.system('mkdir -p dist/usr/bin dist/usr/share/applications dist/usr/share/doc/kindlecomicconverter '
'dist/usr/share/kindlecomicconverter dist/usr/share/lintian/overrides')
os.system('mv dist/kcc dist/usr/bin')
os.system('cp icons/comic2ebook.png dist/usr/share/kindlecomicconverter')
os.system('cp LICENSE.txt dist/usr/share/doc/kindlecomicconverter/copyright')
os.system('cp other/linux/kindlecomicconverter.desktop dist/usr/share/applications')
os.system('cp other/linux/kindlecomicconverter dist/usr/share/lintian/overrides')
os.chdir('dist')
os.system('fpm -f -s dir -t deb -n kindlecomicconverter -v ' + VERSION +
' -m "Paweł Jastrzębski <pawelj@iosphe.re>" --license "ISC" '
'--description "$(printf "Comic and Manga converter for e-book '
'readers.\nThis app allows you to transform your PNG, JPG, GIF, '
'CBZ, CBR and CB7 files\ninto EPUB or MOBI format e-books.")" '
'--url "https://kcc.iosphe.re/" --deb-priority "optional" --vendor "" '
'--category "graphics" -d "unrar | unrar-free" -d "p7zip-full" -d "libc6" usr')
exit(0) exit(0)
setuptools.setup( setuptools.setup(
cmdclass={ cmdclass={
'build_binary': BuildBinaryCommand, 'build_binary': BuildBinaryCommand,
@@ -95,7 +78,7 @@ setuptools.setup(
packages=['kindlecomicconverter'], packages=['kindlecomicconverter'],
install_requires=[ install_requires=[
'PyQt5>=5.6.0', 'PyQt5>=5.6.0',
'Pillow>=4.0.0', 'Pillow>=5.2.0',
'psutil>=5.0.0', 'psutil>=5.0.0',
'python-slugify>=1.2.1', 'python-slugify>=1.2.1',
'raven>=6.0.0', 'raven>=6.0.0',