mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 21:48:44 +00:00
Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
515b83637f | ||
|
|
c3dad087d3 | ||
|
|
67e913ed9e | ||
|
|
6ce0f76fe0 | ||
|
|
5f5157c1d4 | ||
|
|
4c13ef0f6c | ||
|
|
50dc7fbffe | ||
|
|
a060498ac7 | ||
|
|
35bba68a72 | ||
|
|
0b056a8fa8 | ||
|
|
a6f9e84251 | ||
|
|
259800e48b | ||
|
|
28e170f1d2 | ||
|
|
7120c76025 | ||
|
|
4891913b5c | ||
|
|
cd83b2899c | ||
|
|
535c2c220b | ||
|
|
409f077c3e | ||
|
|
3ecb2ba877 | ||
|
|
c07a9657ef | ||
|
|
7f719a22ad | ||
|
|
332d3d455e | ||
|
|
2070a977ae | ||
|
|
8f8d0d68a3 | ||
|
|
5a8deb4623 | ||
|
|
a7ea795df5 | ||
|
|
a2ffd259b8 | ||
|
|
93e6b51466 | ||
|
|
7904662f25 | ||
|
|
6792c2d366 | ||
|
|
ef4a91e44d | ||
|
|
a2a405e5f5 | ||
|
|
a63a46a741 | ||
|
|
2591b53a09 | ||
|
|
1c615ffc20 | ||
|
|
6eb05b3a8f | ||
|
|
968b083fb2 | ||
|
|
205907ef1e | ||
|
|
33ef8275c3 | ||
|
|
eec2099515 | ||
|
|
9027265b7c | ||
|
|
34e2af3389 | ||
|
|
5ecddaceab | ||
|
|
fe554c20aa | ||
|
|
6acf1a1802 | ||
|
|
ac81a6be4b | ||
|
|
829a5f25e7 | ||
|
|
a695a4c151 | ||
|
|
7c0b78350d | ||
|
|
aa978ab246 | ||
|
|
7524c50657 | ||
|
|
9d8663a925 | ||
|
|
08ed304f8e | ||
|
|
658d2f3281 | ||
|
|
f44bf5993e | ||
|
|
458c28281f | ||
|
|
968a1afa1d | ||
|
|
2c875673bd | ||
|
|
1a0be83d63 | ||
|
|
36a7dc49ec | ||
|
|
40d1ae63da | ||
|
|
3d3621c6ec | ||
|
|
d77f04a84e | ||
|
|
ec51d6fc51 | ||
|
|
b7861d9d9e | ||
|
|
0b4503af21 | ||
|
|
437ffb9b10 | ||
|
|
066d1401bd | ||
|
|
9babe68d2a | ||
|
|
67de77538c | ||
|
|
c3e950f2ec | ||
|
|
ac2934aba2 | ||
|
|
a5064a0c0a | ||
|
|
2d712e796d | ||
|
|
cc3da40fd7 | ||
|
|
a53c272bd0 | ||
|
|
6526b139fd | ||
|
|
283d6101cd | ||
|
|
02dab3c6ee | ||
|
|
1895aa127d | ||
|
|
c01ff83fce | ||
|
|
4b670f3754 | ||
|
|
23b1560fa2 | ||
|
|
62350608dc | ||
|
|
8048b91fa8 | ||
|
|
2e9b3389e4 | ||
|
|
40e1ab4cf3 | ||
|
|
d2c12c89e6 | ||
|
|
4647fd1f1d | ||
|
|
010ad3c88c | ||
|
|
4b0a94a8a0 | ||
|
|
807a2d1dff | ||
|
|
e1470cca15 | ||
|
|
02b9081e37 | ||
|
|
495db88a9e | ||
|
|
2bea546a9d | ||
|
|
ee042ef98d | ||
|
|
aea7c0fafb | ||
|
|
45c1afcad4 | ||
|
|
b8e314f6ca | ||
|
|
d76eea9f43 | ||
|
|
2e55f22355 | ||
|
|
30b8770e34 | ||
|
|
9ad161489f | ||
|
|
bdb459cfab | ||
|
|
2e85556543 | ||
|
|
93ebbbd0af | ||
|
|
dd5c907bad | ||
|
|
64fb4a9eca | ||
|
|
284c577894 | ||
|
|
c68c9892e4 | ||
|
|
aa00ea3aa2 | ||
|
|
88f005824c | ||
|
|
2a2bfae112 | ||
|
|
583eec787f | ||
|
|
9ce691aecb | ||
|
|
d1a07d7ffa | ||
|
|
b545f7ad48 | ||
|
|
9e01797d28 | ||
|
|
c68c5f25bf | ||
|
|
a04bf5262f | ||
|
|
b09b2527d9 | ||
|
|
94b372f47d |
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,16 +1,10 @@
|
||||
*.pyc
|
||||
*.cbz
|
||||
*.cbr
|
||||
.idea
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
dist
|
||||
Output
|
||||
test
|
||||
solaio
|
||||
kindlegen*
|
||||
*.spec
|
||||
Pipfile
|
||||
Pipfile.lock
|
||||
setup.bat
|
||||
setup.sh
|
||||
kcc/sentry.py
|
||||
kindlecomicconverter/sentry.py
|
||||
other/windows/kindlegen.exe
|
||||
dist/
|
||||
build/
|
||||
KindleComicConverter.egg-info/
|
||||
.idea/
|
||||
30
.travis.yml
Normal file
30
.travis.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
language: generic
|
||||
osx_image: xcode11.1
|
||||
|
||||
before_install:
|
||||
- pip3 install --upgrade pip setuptools wheel
|
||||
|
||||
install:
|
||||
- pip3 install -r requirements.txt
|
||||
- pip3 install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip
|
||||
- npm install -g appdmg
|
||||
|
||||
script: python3 setup.py build_binary
|
||||
|
||||
before_deploy:
|
||||
- shopt -s extglob
|
||||
- rm -r dist/!(*.deb|*.dmg)
|
||||
|
||||
deploy:
|
||||
provider: gcs
|
||||
access_key_id: GOOG1EC62457RKUYFR2TIZUWV4EFSV2EP5LVLPPFXUAKADWJFDYPFW63BQSLA
|
||||
secret_access_key:
|
||||
secure: sxYjeho7U3im0Ezf6cz6TjYDiLvf0kAM2ETQHYoFNbD1VVvhJJyymDCnPH80zpFKmhc1MWTB6ndwsrPfcyZDLR2meSdWGPjZfFPY3RcrfImndKi7ln+mYQDBQ7W1lGit4YcH3Ju7LHceaTbRA7fVTX8pWKOcbXL2oM+lQxTJHH32+crVma+ChhbjzTWsSLRoakt3Nhiveec5p/qSW7AFe4Zq+b3C85IgwjSJI/xVwzaWrs6p915h1zZi7KL7YCMIxfQFrvRPFR2KTbh/DoLCCrqfbD4qh0PVy1li51Ac3hd/u3foiNnTNchzgE3Nv/nbKmtFU6huuLNgzkQGuLA+yn7mKYzBwA3ZmFgoimdH9+yRCMkZ8B5VHpvfN1hgpJcyEl1T98Kv4cdtRYNB4w9iAMy1qSVxhjeI+2rjuWGoXro0lU6L4LIRCOruY3AuLCAKG8Qw5Ak9ksmIKBhZ9soxpoIwu/TYDUQkFj29IrUQucg9TEp7uAoxu8/7EHxB7hWnBRaBAAQbMuIRg7yysT3FT0Os6SB0t9+RBsVMSPuIti9JJZ2Lu0uRI1+Se+g7ItzYtJoPhBJAzAa+J9OONj0RNj2z8Vq2oIBhH4z6b6zTRMVroos3cdfYl5qIKs9SQ7rmeHoPRROcqpCznsUZ/ESa4f2MewFU/7AYcEnCesZV4xg=
|
||||
bucket: kcc-deploy
|
||||
local-dir: dist
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: AcidWeb/KCC
|
||||
372
CHANGELOG.md
Normal file
372
CHANGELOG.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# CHANGELOG
|
||||
#### 5.5.2:
|
||||
* Fixed KindleGen detection on macOS 10.15
|
||||
* Fixed multiple smaller issues
|
||||
|
||||
#### 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:
|
||||
* Minor bug fixes and tweaks
|
||||
* Implemented new binary build pipeline
|
||||
|
||||
#### 5.4:
|
||||
* Reimplemented high quality Panel View option
|
||||
* Improved webtoon splitter
|
||||
* Fixed page splitter
|
||||
|
||||
#### 5.3.1:
|
||||
* Small increase of output quality
|
||||
* Improved error reporting
|
||||
* Internal changes and tweaks
|
||||
|
||||
#### 5.3:
|
||||
* Vastly improved output compatibility for non-Kindle devices
|
||||
* Enabled old pinch zoom for Kindle devices
|
||||
* Re-enabled Panel View support for Kindle Keyboard
|
||||
* Partially re-enabled OS X file association mechanism
|
||||
* Fixed multiple smaller issues
|
||||
|
||||
#### 5.2.1:
|
||||
* Improved directory parsing
|
||||
* Tweaked margin detection algorithm
|
||||
* Improved error reporting
|
||||
|
||||
#### 5.2:
|
||||
* Added new Panel View options
|
||||
* Implemented new margin detection algorithm
|
||||
* Removed HQ Panel View mode
|
||||
* Fixed multiple smaller issues
|
||||
|
||||
#### 5.1.3:
|
||||
* Added Kobo Aura ONE profile
|
||||
* Fixed few small bugs
|
||||
|
||||
#### 5.1.2:
|
||||
* Fixed error reporting
|
||||
|
||||
#### 5.1.1:
|
||||
* Fixed multiple GUI bugs
|
||||
|
||||
#### 5.1:
|
||||
* GUI now can be resized and high DPI support was somewhat improved
|
||||
* Added profile for Kindle Oasis
|
||||
* Implemented new error reporting mechanism
|
||||
* CLI version now support additional cropping options
|
||||
* Fixed permission issues on Windows
|
||||
* Fixed multiple smaller issues
|
||||
|
||||
#### 5.0.1:
|
||||
* Fixed Panel View placement issues
|
||||
* Decreased application startup time
|
||||
* Fixed multiple smaller issues
|
||||
|
||||
#### 5.0:
|
||||
* Major overhaul of internal mechanisms and GUI
|
||||
* Added cover upload feature
|
||||
* Tweaked Webtoon parsing mode
|
||||
* Fixed multiple smaller issues
|
||||
* Migrated build enviroment to PyInstaller
|
||||
|
||||
#### 4.6.5:
|
||||
* Fixed multiple Windows and OS X issues
|
||||
* Allowed Linux release to use older PyQT5 version
|
||||
|
||||
#### 4.6.4:
|
||||
* Fixed multiple Windows specific problems
|
||||
* Improved error handling
|
||||
* Improved color detection algorithm
|
||||
* New, slimmer OS X release
|
||||
|
||||
#### 4.6.3:
|
||||
* Implemented remote bug reporting
|
||||
* Minor bug fixes and GUI tweaks
|
||||
|
||||
#### 4.6.2:
|
||||
* Fixed critical MOBI header bug
|
||||
* Fixed metadata encoding error
|
||||
|
||||
#### 4.6.1:
|
||||
* Fixed KEPUB TOC generator
|
||||
* Added warning about too small input files
|
||||
* ComicRack Summary metadata field is now parsed
|
||||
* Small tweaks of KEPUB output
|
||||
|
||||
#### 4.6:
|
||||
* KEPUB is now default output for all Kobo profiles
|
||||
* EPUB output now produce fully valid EPUB 3.0.1
|
||||
* Added profile for Kindle Paperwhite 3
|
||||
* Dropped official support of all Kindle Fire models and Kindle for Android
|
||||
* Other minor tweaks
|
||||
|
||||
#### 4.5.1:
|
||||
* Added Kobo Glo HD profile
|
||||
* Fixed RAR/CBR parsing anomalies
|
||||
* Minor bug fixes and tweaks
|
||||
|
||||
#### 4.5:
|
||||
* Added simple ComicRack metadata editor
|
||||
* Re-enabled Manga Cover Database support
|
||||
* ComicRack bookmarks are now parsed
|
||||
* Fixed glitches in Kindle Voyage profile
|
||||
* Fixed problems with directory locks on Windows
|
||||
* Fixed sorting anomalies
|
||||
* Improved conversion speed
|
||||
|
||||
#### 4.4.1:
|
||||
* Fixed problems with OSX GUI
|
||||
* Added one missing DLL to Windows installer
|
||||
|
||||
#### 4.4:
|
||||
* Improved speed and quality of conversion
|
||||
* Added RAR5 support
|
||||
* Dropped BMP and TIFF support
|
||||
* Fixed some WebToon mode bugs
|
||||
* Fixed CBR parsing on OSX
|
||||
|
||||
#### 4.3.1:
|
||||
* Fixed Kindle Voyage profile
|
||||
* Fixed some bugs in OS X release
|
||||
* CLI version now support multiple input files at once
|
||||
* Disabled MCB support
|
||||
* Other minor tweaks
|
||||
|
||||
#### 4.3:
|
||||
* Added profiles for Kindle Voyage and Kobo Aura H2O
|
||||
* Added missing features to CLI version
|
||||
* Other minor bug fixes
|
||||
|
||||
#### 4.2.1:
|
||||
* Improved margin color detection
|
||||
* Fixed random crashes of MOBI processing step
|
||||
* Fixed resizing problems in high quality mode
|
||||
* Fixed some MCD support bugs
|
||||
* Default output format for Kindle DX is now CBZ
|
||||
|
||||
#### 4.2:
|
||||
* Added [Manga Cover Database](http://manga.joentjuh.nl/) support
|
||||
* Officially dropped Windows XP support
|
||||
* Fixed _Other_ profile
|
||||
* Fixed problems with page order on stock KOBO CBZ reader
|
||||
* Many other small bug fixes and tweaks
|
||||
|
||||
#### 4.1:
|
||||
* Thanks to code contributed by Kevin Hendricks speed of MOBI creation was greatly increased
|
||||
* Improved performance on Windows
|
||||
* Improved MOBI splitting and changed maximal size of output file
|
||||
* Fixed _No optimization_ mode
|
||||
* Multiple small tweaks nad minor bug fixes
|
||||
|
||||
#### 4.0.2:
|
||||
* Fixed some Windows and OSX specific bugs
|
||||
* Fixed problem with marigns when using HQ mode
|
||||
|
||||
#### 4.0.1:
|
||||
* Fixed file lock problems that plagued some Windows users
|
||||
* Fixed content server failing to start on Windows
|
||||
* Improved performance of WebToon splitter
|
||||
* Tweaked margin color detection
|
||||
|
||||
#### 4.0:
|
||||
* KCC now use Python 3.3 and Qt 5.2
|
||||
* Full UTF-8 awareness
|
||||
* CBZ output now support Manga mode
|
||||
* Improved Panel View support and margin color detection
|
||||
* Added drag&drop support
|
||||
* Output directory can be now selected
|
||||
* Windows release now have auto-updater
|
||||
* Names of chapters on Kindle should be now more user friendly
|
||||
* Fixed OSX file association support
|
||||
* Many extensive internal changes and tweaks
|
||||
|
||||
#### 3.7.2:
|
||||
* Fixed problems with HQ mode
|
||||
|
||||
#### 3.7.1:
|
||||
* Hotfixed Kobo profiles
|
||||
|
||||
#### 3.7:
|
||||
* Added profiles for KOBO devices
|
||||
* Improved Panel View support
|
||||
* Improved WebToon splitter
|
||||
* Improved margin color autodetection
|
||||
* Tweaked EPUB output
|
||||
* Fixed stretching option
|
||||
* GUI tweaks and minor bugfixes
|
||||
|
||||
#### 3.6.2:
|
||||
* Fixed previous PNG output fix
|
||||
* Fixed Panel View anomalies
|
||||
|
||||
#### 3.6.1:
|
||||
* Fixed PNG output
|
||||
|
||||
#### 3.6:
|
||||
* Increased quality of Panel View zoom
|
||||
* Creation of multipart MOBI output is now faster on machines with 4GB+ RAM
|
||||
* Automatic gamma correction now distinguishes color and grayscale images
|
||||
* Added ComicRack metadata parser
|
||||
* Implemented new method to detect border color in non-webtoon comics
|
||||
* Upscaling is now enabled by default for Kindle Fire HD/HDX
|
||||
* Windows nad Linux releases now have tray icon
|
||||
* Fixed Kindle Fire HDX 7" output
|
||||
* Increased target resolution for Kindle DX/DXG CBZ output
|
||||
|
||||
#### 3.5:
|
||||
* Added simple content server - Converted files can be now delivered wireless
|
||||
* Added proper Windows installer
|
||||
* Improved multiprocessing speed
|
||||
* GUI tweaks and minor bug fixes
|
||||
|
||||
#### 3.4:
|
||||
* Improved PNG output
|
||||
* Increased quality of upscaling
|
||||
* Added support of file association - KCC can now open CBZ, CBR, CB7, ZIP, RAR, 7Z and PDF files directly
|
||||
* Paths that contain UTF-8 characters are now supported
|
||||
* Migrated to new version of Pillow library
|
||||
* Merged DX and DXG profiles
|
||||
* Many other minor bug fixes and GUI tweaks
|
||||
|
||||
#### 3.3:
|
||||
* Margins are now automatically omitted in Panel View mode
|
||||
* Margin color fill is now autodetected
|
||||
* Created MOBI files are not longer marked as _Personal_ on newer Kindle models
|
||||
* Layout of panels in Panel View mode is now automatically adjusted to content
|
||||
* Fixed Kindle 2/DX/DXG profiles - no more blank pages
|
||||
* All Kindle Fire profiles now support hiqh quality Panel View
|
||||
* Added support of 7z/CB7 files
|
||||
* Added Kindle Fire HDX profile
|
||||
* Support for Virtual Panel View was removed
|
||||
* Profiles for Kindle Keyboard, Touch and Non-Touch are now merged
|
||||
* Windows release is now bundled with UnRAR and 7za
|
||||
* Small GUI tweaks
|
||||
|
||||
#### 3.2:
|
||||
* Too big EPUB files are now splitted before conversion to MOBI
|
||||
* Added experimental parser of manga webtoons
|
||||
* Improved error handling
|
||||
|
||||
#### 3.2.1:
|
||||
* Hotfixed crash occurring on OS with Russian locale
|
||||
|
||||
#### 3.1:
|
||||
* Added profile: Kindle for Android
|
||||
* Add file/directory dialogs now support multiselect
|
||||
* Many small fixes and tweaks
|
||||
|
||||
#### 3.0:
|
||||
* New QT GUI
|
||||
* Merge with AWKCC
|
||||
* Added ultra quality mode
|
||||
* Added support for custom width/height
|
||||
* Added option to disable color conversion
|
||||
|
||||
#### 2.10:
|
||||
* Multiprocessing support
|
||||
* Kindle Fire support (color EPUB/MOBI)
|
||||
* Panel View support for horizontal content
|
||||
* Fixed panel order for horizontal pages when --rotate is enabled
|
||||
* Disabled cropping and page number cutting for blank pages
|
||||
* Fixed some slugify issues with specific file naming conventions (#50, #51)
|
||||
|
||||
#### 2.9
|
||||
* Added support for generating a plain CBZ (skipping all the EPUB/MOBI generation) (#45)
|
||||
* Prevent output file overwriting the source one: if a duplicate name is detected, append _kcc to the name
|
||||
* Rarfile library updated to 2.6
|
||||
* Added GIF, TIFF and BMP to supported formats (#42)
|
||||
* Filenames slugifications (#28, #31, #9, #8)
|
||||
|
||||
#### 2.8
|
||||
* Updated rarfile library
|
||||
* Panel View support + HQ support (#36) - new option: --nopanelviewhq
|
||||
* Split profiles for K4NT and K4T
|
||||
* Rewrite of Landscape Mode support (huge readability improvement for KPW)
|
||||
* Upscale use now BILINEAR method
|
||||
* Added generic CSS file
|
||||
* Optimized archive extraction for zip/rar files (#40)
|
||||
|
||||
#### 2.7
|
||||
* Lots of GUI improvements (#27, #13)
|
||||
* Added gamma support within --gamma option (defaults to profile-specified gamma) (#26, #27)
|
||||
* Added --nodithering option to prevent dithering optimizations (#27)
|
||||
* EPUB margins support (#30)
|
||||
* Fixed no file added if file has no spaces on Windows (#25)
|
||||
* Gracefully exit if unrar missing (#15)
|
||||
* Do not call kindlegen if source EPUB is bigger than 320MB (#17)
|
||||
* Get filetype from magic number (#14)
|
||||
* PDF conversion works again
|
||||
|
||||
#### 2.6
|
||||
* Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
|
||||
* Added --output option to customize EPUB output dir/file (#22)
|
||||
* Add rendition:layout and rendition:orientation EPUB meta tags (supported by new kindlegen 2.8)
|
||||
* Fixed natural sorting for files (#18)
|
||||
|
||||
#### 2.5
|
||||
* Added --black-borders option to set added borders black when page's ratio is not the device's one (#11).
|
||||
* Fixes EPUB containing zipped itself (#10)
|
||||
|
||||
#### 2.4
|
||||
* Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
|
||||
* Fixed "add folders" from GUI.
|
||||
|
||||
#### 2.3
|
||||
* Fixed win32 EPUB generation, folder handling, filenames with spaces and subfolders
|
||||
|
||||
#### 2.2:
|
||||
* Added (valid!) EPUB 2.0 output
|
||||
* Rename .zip files to .cbz to avoid overwriting
|
||||
|
||||
#### 2.1
|
||||
* Added basic error reporting
|
||||
|
||||
#### 2.0
|
||||
* GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support.
|
||||
|
||||
#### 1.5
|
||||
* Added subfolder support for multiple chapters.
|
||||
|
||||
#### 1.4.1
|
||||
* Fixed a serious bug on resizing when img ratio was bigger than device one
|
||||
|
||||
#### 1.4
|
||||
* Added some options for controlling image optimization
|
||||
* Further optimization (ImageOps, page numbering cut, autocontrast)
|
||||
|
||||
#### 1.3
|
||||
* Fixed an issue in OPF generation for device resolution
|
||||
* Reworked options system (call with -h option to get the inline help)
|
||||
|
||||
#### 1.2
|
||||
* Comic optimizations! Split pages not target-oriented (landscape with portrait target or portrait with landscape target), add palette and other image optimizations from Mangle. WARNING: PIL is required for all image mangling!
|
||||
|
||||
#### 1.1.1
|
||||
* Added support for CBZ/CBR files in Kindle Comic Converter
|
||||
|
||||
#### 1.1
|
||||
* Added support for CBZ/CBR files in comic2ebook.py
|
||||
|
||||
#### 1.0
|
||||
* Initial version
|
||||
@@ -1,7 +1,7 @@
|
||||
ISC LICENSE
|
||||
|
||||
Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
Copyright (c) 2013-2016 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
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
|
||||
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
@@ -0,0 +1 @@
|
||||
exclude kindlecomicconverter/sentry.py
|
||||
425
README.md
425
README.md
@@ -1,4 +1,6 @@
|
||||
# KCC
|
||||
# KCC
|
||||
|
||||
[](https://github.com/ciromattia/kcc/releases) [](https://pypi.python.org/pypi/KindleComicConverter) [](https://aur.archlinux.org/packages/kcc/)
|
||||
|
||||
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
|
||||
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
|
||||
@@ -6,7 +8,7 @@ actually a comic/manga to EPUB converter that every e-reader owner can happily u
|
||||
It can also optionally optimize images by applying a number of transformations.
|
||||
|
||||
### A word of warning
|
||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||
_KC2_ in no way is a replacement for **KCC** so you can be quite confident we'll going to carry on developing our little monster ;-)
|
||||
|
||||
@@ -17,55 +19,59 @@ If you can fix an open issue, fork & make a pull request.
|
||||
|
||||
If you find **KCC** valuable you can consider donating to the authors:
|
||||
- Ciro Mattia Gonano:
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||
- [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||
- [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
||||
- Paweł Jastrzębski:
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||
- Bitcoin: 1W15wwqsfd7wbaZ6wvSJf1LW1bz6q5L8b
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||
- [](https://jastrzeb.ski/donate/)
|
||||
|
||||
## BINARY RELEASES
|
||||
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/)
|
||||
- **Linux (Glibc 2.19+):** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/)
|
||||
- **OS X (10.9+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/)
|
||||
- **[Windows](http://kcc.iosphe.re/Windows/) (64-bit only)**
|
||||
- **[macOS](http://kcc.iosphe.re/OSX/) (10.14+)**
|
||||
- **Linux:** Currently unavailable.
|
||||
|
||||
## PYPI
|
||||
**KCC** is also available on PyPI.
|
||||
```
|
||||
pip install KindleComicConverter
|
||||
```
|
||||
|
||||
## DEPENDENCIES
|
||||
Following software is required to run Linux version of **KCC** and/or bare sources:
|
||||
- Python 3.3+
|
||||
- [PyQt](https://pypi.python.org/pypi/PyQt5) 5.6.0+
|
||||
- [Pillow](https://pypi.python.org/pypi/Pillow/) 3.2.0+
|
||||
- [psutil](https://pypi.python.org/pypi/psutil) 4.1.0+
|
||||
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.0+
|
||||
- [raven](https://pypi.python.org/pypi/raven) 5.13.0+
|
||||
- [scandir](https://pypi.python.org/pypi/scandir) 1.2.0+ _(needed only when using Python 3.3 or 3.4)_
|
||||
- [PyQt5](https://pypi.python.org/pypi/PyQt5) 5.6.0+
|
||||
- [Pillow](https://pypi.python.org/pypi/Pillow/) 4.0.0+
|
||||
- [psutil](https://pypi.python.org/pypi/psutil) 5.0.0+
|
||||
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.1+, <3.0.0
|
||||
- [raven](https://pypi.python.org/pypi/raven) 6.0.0+
|
||||
|
||||
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 pip3 install --upgrade pillow python-slugify psutil scandir raven pyqt5
|
||||
sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full
|
||||
sudo pip3 install --upgrade pillow python-slugify psutil pyqt5 raven
|
||||
```
|
||||
|
||||
### Optional dependencies
|
||||
- [KindleGen](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211) v2.9+ in a directory reachable by your _PATH_ or in _KCC_ directory *(For MOBI generation)*
|
||||
- [UnRAR](http://www.rarlab.com/download.htm) *(For CBR/RAR support)*
|
||||
- [7za](http://www.7-zip.org/download.html) *(For 7z/CB7 support)*
|
||||
- [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)*
|
||||
|
||||
## INPUT FORMATS
|
||||
**KCC** can understand and convert, at the moment, the following input types:
|
||||
- Folders containing: PNG, JPG or GIF files
|
||||
- CBZ, ZIP
|
||||
- CBR, RAR *(With `unrar` executable)*
|
||||
- CB7, 7Z *(With `7za` executable)*
|
||||
- Folders containing: PNG, JPG, GIF or WebP files
|
||||
- CBZ, ZIP *(With `7z` executable)*
|
||||
- CBR, RAR *(With `7z` executable)*
|
||||
- CB7, 7Z *(With `7z` executable)*
|
||||
- PDF *(Only extracting JPG images)*
|
||||
|
||||
## USAGE
|
||||
|
||||
Should be pretty self-explanatory. All options have detailed informations in tooltips.
|
||||
After completed conversion you should find ready file alongside the original input file (same directory).
|
||||
Should be pretty self-explanatory. All options have detailed information in tooltips.
|
||||
After completed conversion, you should find ready file alongside the original input file (same directory).
|
||||
|
||||
Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details.
|
||||
|
||||
CLI version of **KCC** is intended for power users. It is not idiot-proof like GUI :-)
|
||||
CLI version of **KCC** is intended for power users. It allows using options that might not be compatible and decrease the quality of output.
|
||||
|
||||
### Standalone `kcc-c2e.py` usage:
|
||||
|
||||
@@ -75,9 +81,12 @@ Usage: kcc-c2e [options] comic_file|comic_folder
|
||||
Options:
|
||||
MAIN:
|
||||
-p PROFILE, --profile=PROFILE
|
||||
Device profile (Available options: K1, K2, K3, K45, KDX,
|
||||
KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O) [Default=KV]
|
||||
Device profile (Available options: K1, K2, K34, K578,
|
||||
KDX, KPW, KV, KO, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
|
||||
KoAO, KoF) [Default=KV]
|
||||
-m, --manga-style Manga style (right-to-left reading and splitting)
|
||||
-q, --hq Try to increase the quality of magnification
|
||||
-2, --two-panel Display two not four panels in Panel View mode
|
||||
-w, --webtoon Webtoon processing mode
|
||||
|
||||
OUTPUT SETTINGS:
|
||||
@@ -86,29 +95,31 @@ Options:
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [Default=filename or directory name]
|
||||
-f FORMAT, --format=FORMAT
|
||||
Output format (Available options: Auto, MOBI, EPUB, CBZ)
|
||||
[Default=Auto]
|
||||
-b, --batchsplit Split output into multiple files
|
||||
Output format (Available options: Auto, MOBI, EPUB,
|
||||
CBZ, KFX) [Default=Auto]
|
||||
-b BATCHSPLIT, --batchsplit=BATCHSPLIT
|
||||
Split output into multiple files. 0: Don't split 1:
|
||||
Automatic mode 2: Consider every subdirectory as
|
||||
separate volume [Default=0]
|
||||
|
||||
PROCESSING:
|
||||
-u, --upscale Resize images smaller than device's resolution
|
||||
-s, --stretch Stretch images to device's resolution
|
||||
-r SPLITTER, --splitter=SPLITTER
|
||||
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
|
||||
Double page parsing mode. 0: Split 1: Rotate 2: Both
|
||||
[Default=0]
|
||||
-g GAMMA, --gamma=GAMMA
|
||||
Apply gamma correction to linearize the image [Default=Auto]
|
||||
--hq Enable high quality Panel View
|
||||
Apply gamma correction to linearize the image
|
||||
[Default=Auto]
|
||||
-c CROPPING, --cropping=CROPPING
|
||||
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
|
||||
page numbers [Default=2]
|
||||
--cp=CROPPINGP, --croppingpower=CROPPINGP
|
||||
Set cropping power [Default=1.0]
|
||||
--blackborders Disable autodetection and force black borders
|
||||
--whiteborders Disable autodetection and force white borders
|
||||
--forcecolor Don't convert images to grayscale
|
||||
--forcepng Create PNG files instead JPEG
|
||||
--cropping=CROPPING
|
||||
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
|
||||
page numbers [Default=2]
|
||||
--croppingpower=CROPPINGP
|
||||
Set margin cropping threshold [Default=0.1]
|
||||
--croppingpowerpage=CROPPINGPN
|
||||
Set page number cropping threshold [Default=5.0]
|
||||
|
||||
CUSTOM PROFILE:
|
||||
--customwidth=CUSTOMWIDTH
|
||||
@@ -133,7 +144,7 @@ Options:
|
||||
-m, --merge Combine every directory into a single image before splitting
|
||||
|
||||
OTHER:
|
||||
-d, --debug Create debug file for every splitted image
|
||||
-d, --debug Create debug file for every split image
|
||||
-h, --help Show this help message and exit
|
||||
```
|
||||
|
||||
@@ -145,340 +156,28 @@ This script born as a cross-platform alternative to `KindleComicParser` by **Dc5
|
||||
The app relies and includes the following scripts:
|
||||
|
||||
- `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License.
|
||||
- `rarfile.py` script © 2005-2014 **Marko Kreen** <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.
|
||||
- Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License.
|
||||
|
||||
## SAMPLE FILES CREATED BY KCC
|
||||
* [Kindle Paperwhite 3 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||
* [Kindle Oasis 2 / 3](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](http://kcc.iosphe.re/Samples/Ubunchu!-K45.mobi)
|
||||
* [Kindle Keyboard](http://kcc.iosphe.re/Samples/Ubunchu!-K3.mobi)
|
||||
* [Kindle DX/DXG](http://kcc.iosphe.re/Samples/Ubunchu!-KDX.cbz)
|
||||
* [Kobo Mini/Touch](http://kcc.iosphe.re/Samples/Ubunchu-KoMT.kepub.epub)
|
||||
* [Kobo Glo](http://kcc.iosphe.re/Samples/Ubunchu-KoG.kepub.epub)
|
||||
* [Kobo Glo HD](http://kcc.iosphe.re/Samples/Ubunchu-KoGHD.kepub.epub)
|
||||
* [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K578.mobi)
|
||||
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub)
|
||||
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub)
|
||||
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub)
|
||||
|
||||
## CHANGELOG
|
||||
####5.1.2:
|
||||
* Fixed error reporting
|
||||
|
||||
####5.1.1:
|
||||
* Fixed multiple GUI bugs
|
||||
|
||||
####5.1:
|
||||
* GUI now can be resized and high DPI support was somewhat improved
|
||||
* Added profile for Kindle Oasis
|
||||
* Implemented new error reporting mechanism
|
||||
* CLI version now support additional cropping options
|
||||
* Fixed permission issues on Windows
|
||||
* Fixed multiple smaller issues
|
||||
|
||||
####5.0.1:
|
||||
* Fixed Panel View placement issues
|
||||
* Decreased application startup time
|
||||
* Fixed multiple smaller issues
|
||||
|
||||
####5.0:
|
||||
* Major overhaul of internal mechanisms and GUI
|
||||
* Added cover upload feature
|
||||
* Tweaked Webtoon parsing mode
|
||||
* Fixed multiple smaller issues
|
||||
* Migrated build enviroment to PyInstaller
|
||||
|
||||
####4.6.5:
|
||||
* Fixed multiple Windows and OS X issues
|
||||
* Allowed Linux release to use older PyQT5 version
|
||||
|
||||
####4.6.4:
|
||||
* Fixed multiple Windows specific problems
|
||||
* Improved error handling
|
||||
* Improved color detection algorithm
|
||||
* New, slimmer OS X release
|
||||
|
||||
####4.6.3:
|
||||
* Implemented remote bug reporting
|
||||
* Minor bug fixes and GUI tweaks
|
||||
|
||||
####4.6.2:
|
||||
* Fixed critical MOBI header bug
|
||||
* Fixed metadata encoding error
|
||||
|
||||
####4.6.1:
|
||||
* Fixed KEPUB TOC generator
|
||||
* Added warning about too small input files
|
||||
* ComicRack Summary metadata field is now parsed
|
||||
* Small tweaks of KEPUB output
|
||||
|
||||
####4.6:
|
||||
* KEPUB is now default output for all Kobo profiles
|
||||
* EPUB output now produce fully valid EPUB 3.0.1
|
||||
* Added profile for Kindle Paperwhite 3
|
||||
* Dropped official support of all Kindle Fire models and Kindle for Android
|
||||
* Other minor tweaks
|
||||
|
||||
####4.5.1:
|
||||
* Added Kobo Glo HD profile
|
||||
* Fixed RAR/CBR parsing anomalies
|
||||
* Minor bug fixes and tweaks
|
||||
|
||||
####4.5:
|
||||
* Added simple ComicRack metadata editor
|
||||
* Re-enabled Manga Cover Database support
|
||||
* ComicRack bookmarks are now parsed
|
||||
* Fixed glitches in Kindle Voyage profile
|
||||
* Fixed problems with directory locks on Windows
|
||||
* Fixed sorting anomalies
|
||||
* Improved conversion speed
|
||||
|
||||
####4.4.1:
|
||||
* Fixed problems with OSX GUI
|
||||
* Added one missing DLL to Windows installer
|
||||
|
||||
####4.4:
|
||||
* Improved speed and quality of conversion
|
||||
* Added RAR5 support
|
||||
* Dropped BMP and TIFF support
|
||||
* Fixed some WebToon mode bugs
|
||||
* Fixed CBR parsing on OSX
|
||||
|
||||
####4.3.1:
|
||||
* Fixed Kindle Voyage profile
|
||||
* Fixed some bugs in OS X release
|
||||
* CLI version now support multiple input files at once
|
||||
* Disabled MCB support
|
||||
* Other minor tweaks
|
||||
|
||||
####4.3:
|
||||
* Added profiles for Kindle Voyage and Kobo Aura H2O
|
||||
* Added missing features to CLI version
|
||||
* Other minor bug fixes
|
||||
|
||||
####4.2.1:
|
||||
* Improved margin color detection
|
||||
* Fixed random crashes of MOBI processing step
|
||||
* Fixed resizing problems in high quality mode
|
||||
* Fixed some MCD support bugs
|
||||
* Default output format for Kindle DX is now CBZ
|
||||
|
||||
####4.2:
|
||||
* Added [Manga Cover Database](http://manga.joentjuh.nl/) support
|
||||
* Officially dropped Windows XP support
|
||||
* Fixed _Other_ profile
|
||||
* Fixed problems with page order on stock KOBO CBZ reader
|
||||
* Many other small bug fixes and tweaks
|
||||
|
||||
####4.1:
|
||||
* Thanks to code contributed by Kevin Hendricks speed of MOBI creation was greatly increased
|
||||
* Improved performance on Windows
|
||||
* Improved MOBI splitting and changed maximal size of output file
|
||||
* Fixed _No optimization_ mode
|
||||
* Multiple small tweaks nad minor bug fixes
|
||||
|
||||
####4.0.2:
|
||||
* Fixed some Windows and OSX specific bugs
|
||||
* Fixed problem with marigns when using HQ mode
|
||||
|
||||
####4.0.1:
|
||||
* Fixed file lock problems that plagued some Windows users
|
||||
* Fixed content server failing to start on Windows
|
||||
* Improved performance of WebToon splitter
|
||||
* Tweaked margin color detection
|
||||
|
||||
####4.0:
|
||||
* KCC now use Python 3.3 and Qt 5.2
|
||||
* Full UTF-8 awareness
|
||||
* CBZ output now support Manga mode
|
||||
* Improved Panel View support and margin color detection
|
||||
* Added drag&drop support
|
||||
* Output directory can be now selected
|
||||
* Windows release now have auto-updater
|
||||
* Names of chapters on Kindle should be now more user friendly
|
||||
* Fixed OSX file association support
|
||||
* Many extensive internal changes and tweaks
|
||||
|
||||
####3.7.2:
|
||||
* Fixed problems with HQ mode
|
||||
|
||||
####3.7.1:
|
||||
* Hotfixed Kobo profiles
|
||||
|
||||
####3.7:
|
||||
* Added profiles for KOBO devices
|
||||
* Improved Panel View support
|
||||
* Improved WebToon splitter
|
||||
* Improved margin color autodetection
|
||||
* Tweaked EPUB output
|
||||
* Fixed stretching option
|
||||
* GUI tweaks and minor bugfixes
|
||||
|
||||
####3.6.2:
|
||||
* Fixed previous PNG output fix
|
||||
* Fixed Panel View anomalies
|
||||
|
||||
####3.6.1:
|
||||
* Fixed PNG output
|
||||
|
||||
####3.6:
|
||||
* Increased quality of Panel View zoom
|
||||
* Creation of multipart MOBI output is now faster on machines with 4GB+ RAM
|
||||
* Automatic gamma correction now distinguishes color and grayscale images
|
||||
* Added ComicRack metadata parser
|
||||
* Implemented new method to detect border color in non-webtoon comics
|
||||
* Upscaling is now enabled by default for Kindle Fire HD/HDX
|
||||
* Windows nad Linux releases now have tray icon
|
||||
* Fixed Kindle Fire HDX 7" output
|
||||
* Increased target resolution for Kindle DX/DXG CBZ output
|
||||
|
||||
####3.5:
|
||||
* Added simple content server - Converted files can be now delivered wireless
|
||||
* Added proper Windows installer
|
||||
* Improved multiprocessing speed
|
||||
* GUI tweaks and minor bug fixes
|
||||
|
||||
####3.4:
|
||||
* Improved PNG output
|
||||
* Increased quality of upscaling
|
||||
* Added support of file association - KCC can now open CBZ, CBR, CB7, ZIP, RAR, 7Z and PDF files directly
|
||||
* Paths that contain UTF-8 characters are now supported
|
||||
* Migrated to new version of Pillow library
|
||||
* Merged DX and DXG profiles
|
||||
* Many other minor bug fixes and GUI tweaks
|
||||
|
||||
####3.3:
|
||||
* Margins are now automatically omitted in Panel View mode
|
||||
* Margin color fill is now autodetected
|
||||
* Created MOBI files are not longer marked as _Personal_ on newer Kindle models
|
||||
* Layout of panels in Panel View mode is now automatically adjusted to content
|
||||
* Fixed Kindle 2/DX/DXG profiles - no more blank pages
|
||||
* All Kindle Fire profiles now support hiqh quality Panel View
|
||||
* Added support of 7z/CB7 files
|
||||
* Added Kindle Fire HDX profile
|
||||
* Support for Virtual Panel View was removed
|
||||
* Profiles for Kindle Keyboard, Touch and Non-Touch are now merged
|
||||
* Windows release is now bundled with UnRAR and 7za
|
||||
* Small GUI tweaks
|
||||
|
||||
####3.2:
|
||||
* Too big EPUB files are now splitted before conversion to MOBI
|
||||
* Added experimental parser of manga webtoons
|
||||
* Improved error handling
|
||||
|
||||
####3.2.1:
|
||||
* Hotfixed crash occurring on OS with Russian locale
|
||||
|
||||
####3.1:
|
||||
* Added profile: Kindle for Android
|
||||
* Add file/directory dialogs now support multiselect
|
||||
* Many small fixes and tweaks
|
||||
|
||||
####3.0:
|
||||
* New QT GUI
|
||||
* Merge with AWKCC
|
||||
* Added ultra quality mode
|
||||
* Added support for custom width/height
|
||||
* Added option to disable color conversion
|
||||
|
||||
####2.10:
|
||||
* Multiprocessing support
|
||||
* Kindle Fire support (color EPUB/MOBI)
|
||||
* Panel View support for horizontal content
|
||||
* Fixed panel order for horizontal pages when --rotate is enabled
|
||||
* Disabled cropping and page number cutting for blank pages
|
||||
* Fixed some slugify issues with specific file naming conventions (#50, #51)
|
||||
|
||||
####2.9
|
||||
* Added support for generating a plain CBZ (skipping all the EPUB/MOBI generation) (#45)
|
||||
* Prevent output file overwriting the source one: if a duplicate name is detected, append _kcc to the name
|
||||
* Rarfile library updated to 2.6
|
||||
* Added GIF, TIFF and BMP to supported formats (#42)
|
||||
* Filenames slugifications (#28, #31, #9, #8)
|
||||
|
||||
####2.8
|
||||
* Updated rarfile library
|
||||
* Panel View support + HQ support (#36) - new option: --nopanelviewhq
|
||||
* Split profiles for K4NT and K4T
|
||||
* Rewrite of Landscape Mode support (huge readability improvement for KPW)
|
||||
* Upscale use now BILINEAR method
|
||||
* Added generic CSS file
|
||||
* Optimized archive extraction for zip/rar files (#40)
|
||||
|
||||
####2.7
|
||||
* Lots of GUI improvements (#27, #13)
|
||||
* Added gamma support within --gamma option (defaults to profile-specified gamma) (#26, #27)
|
||||
* Added --nodithering option to prevent dithering optimizations (#27)
|
||||
* EPUB margins support (#30)
|
||||
* Fixed no file added if file has no spaces on Windows (#25)
|
||||
* Gracefully exit if unrar missing (#15)
|
||||
* Do not call kindlegen if source EPUB is bigger than 320MB (#17)
|
||||
* Get filetype from magic number (#14)
|
||||
* PDF conversion works again
|
||||
|
||||
####2.6
|
||||
* Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
|
||||
* Added --output option to customize EPUB output dir/file (#22)
|
||||
* Add rendition:layout and rendition:orientation EPUB meta tags (supported by new kindlegen 2.8)
|
||||
* Fixed natural sorting for files (#18)
|
||||
|
||||
####2.5
|
||||
* Added --black-borders option to set added borders black when page's ratio is not the device's one (#11).
|
||||
* Fixes EPUB containing zipped itself (#10)
|
||||
|
||||
####2.4
|
||||
* Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
|
||||
* Fixed "add folders" from GUI.
|
||||
|
||||
####2.3
|
||||
* Fixed win32 EPUB generation, folder handling, filenames with spaces and subfolders
|
||||
|
||||
####2.2:
|
||||
* Added (valid!) EPUB 2.0 output
|
||||
* Rename .zip files to .cbz to avoid overwriting
|
||||
|
||||
####2.1
|
||||
* Added basic error reporting
|
||||
|
||||
####2.0
|
||||
* GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support.
|
||||
|
||||
####1.5
|
||||
* Added subfolder support for multiple chapters.
|
||||
|
||||
####1.4.1
|
||||
* Fixed a serious bug on resizing when img ratio was bigger than device one
|
||||
|
||||
####1.4
|
||||
* Added some options for controlling image optimization
|
||||
* Further optimization (ImageOps, page numbering cut, autocontrast)
|
||||
|
||||
####1.3
|
||||
* Fixed an issue in OPF generation for device resolution
|
||||
* Reworked options system (call with -h option to get the inline help)
|
||||
|
||||
####1.2
|
||||
* Comic optimizations! Split pages not target-oriented (landscape with portrait target or portrait with landscape target), add palette and other image optimizations from Mangle. WARNING: PIL is required for all image mangling!
|
||||
|
||||
####1.1.1
|
||||
* Added support for CBZ/CBR files in Kindle Comic Converter
|
||||
|
||||
####1.1
|
||||
* Added support for CBZ/CBR files in comic2ebook.py
|
||||
|
||||
####1.0
|
||||
* Initial version
|
||||
* [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub)
|
||||
* [Kobo Forma](http://kcc.iosphe.re/Samples/Ubunchu-KoF.kepub.epub)
|
||||
|
||||
## PRIVACY
|
||||
**KCC** is initiating internet connections in three cases:
|
||||
* During startup - Version check
|
||||
* When MCD metadata are used - Cover download
|
||||
* When error occurs - Automatic reporting
|
||||
**KCC** is initiating internet connections in two cases:
|
||||
* During startup - Version check.
|
||||
* When error occurs - Automatic reporting on Windows and macOS.
|
||||
|
||||
## KNOWN ISSUES
|
||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||
|
||||
## COPYRIGHT
|
||||
Copyright (c) 2012-2016 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.
|
||||
|
||||
14
appveyor.yml
Normal file
14
appveyor.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
environment:
|
||||
PYTHON: "C:\\Python37-x64"
|
||||
|
||||
install:
|
||||
- set PATH="%PYTHON%\\Scripts";%PATH%
|
||||
- "%PYTHON%\\python.exe -m pip install --upgrade pip setuptools wheel"
|
||||
- "%PYTHON%\\python.exe -m pip install -r requirements.txt"
|
||||
- "%PYTHON%\\python.exe -m pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip"
|
||||
|
||||
build_script:
|
||||
- "%PYTHON%\\python.exe setup.py build_binary"
|
||||
|
||||
artifacts:
|
||||
- path: dist\KCC*
|
||||
18
docker/Build
18
docker/Build
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
pip3 install --upgrade pip setuptools wheel
|
||||
pip3 install pillow python-slugify psutil pyinstaller==3.1.1 raven pyqt5 certifi
|
||||
gem install fpm
|
||||
|
||||
cd /app
|
||||
pyinstaller -F -s kcc.py
|
||||
mkdir -p dist/usr/bin dist/usr/share/applications dist/usr/share/doc/kindlecomicconverter dist/usr/share/kindlecomicconverter dist/usr/share/lintian/overrides
|
||||
mv dist/kcc dist/usr/bin
|
||||
cp icons/comic2ebook.png dist/usr/share/kindlecomicconverter
|
||||
cp LICENSE.txt dist/usr/share/doc/kindlecomicconverter/copyright
|
||||
cp other/linux/kindlecomicconverter.desktop dist/usr/share/applications
|
||||
cp other/linux/kindlecomicconverter dist/usr/share/lintian/overrides
|
||||
|
||||
cd /app/dist
|
||||
fpm -f -s dir -t deb -n kindlecomicconverter -v $KCCVER -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
|
||||
@@ -1,18 +0,0 @@
|
||||
# acidweb/kcc
|
||||
FROM debian:jessie
|
||||
MAINTAINER Paweł Jastrzębski <pawelj@iosphe.re>
|
||||
|
||||
ADD ./Build /Build
|
||||
|
||||
RUN printf "deb http://httpredir.debian.org/debian stretch main" > /etc/apt/sources.list.d/stretch.list
|
||||
RUN printf "Package: *\nPin: release a=testing\nPin-Priority: 400\n" > /etc/apt/preferences.d/stretch.pref
|
||||
RUN printf "deb http://httpredir.debian.org/debian sid main" > /etc/apt/sources.list.d/sid.list
|
||||
RUN printf "Package: *\nPin: release a=testing\nPin-Priority: 300\n" > /etc/apt/preferences.d/sid.pref
|
||||
RUN apt-get update && apt-get -y dist-upgrade
|
||||
RUN apt-get -y install build-essential curl ruby ruby-dev libpng-dev libjpeg-dev
|
||||
RUN apt-get -y -t testing install python3 python3-dev
|
||||
RUN apt-get -y -t unstable install python3-pyqt5
|
||||
RUN curl https://bootstrap.pypa.io/get-pip.py | python3
|
||||
RUN apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
CMD /Build
|
||||
32
gui/KCC.ui
32
gui/KCC.ui
@@ -47,7 +47,7 @@
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QListWidget" name="jobList">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}</string>
|
||||
<string notr="true">QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}</string>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
@@ -87,7 +87,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of target device.</p></body></html></string>
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Custom height:</string>
|
||||
@@ -97,7 +97,7 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="widthBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of target device.</p></body></html></string>
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2160</number>
|
||||
@@ -113,7 +113,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of target device.</p></body></html></string>
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Custom width:</string>
|
||||
@@ -123,7 +123,7 @@
|
||||
<item row="0" column="3">
|
||||
<widget class="QSpinBox" name="heightBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of target device.</p></body></html></string>
|
||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>3840</number>
|
||||
@@ -174,10 +174,13 @@
|
||||
<item row="0" column="2">
|
||||
<widget class="QCheckBox" name="qualityBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>High quality Panel View.<br/>Require source files with bigger resolution than target device.<br/><span style=" font-weight:600;">Highly impact size of output file!</span></p></body></html></string>
|
||||
<string><html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>HQ zoom</string>
|
||||
<string>Panel View 4/2/HQ</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -217,7 +220,7 @@
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="borderBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>W/B margins</string>
|
||||
@@ -228,12 +231,12 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="noDitheringBox">
|
||||
<widget class="QCheckBox" name="outputSplit">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Create PNG files instead JPEG.<br/>Quality increase is not noticeable on most of devices.<br/>Output files <span style=" font-weight:600;">might</span> be smaller.<br/><span style=" font-weight:600;">MOBI conversion will be much slower.</span></p></body></html></string>
|
||||
<string><html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PNG output</string>
|
||||
<string>Output split</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -317,6 +320,9 @@
|
||||
<property name="text">
|
||||
<string>Editor</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html></string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="KCC.qrc">
|
||||
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
|
||||
@@ -451,7 +457,7 @@
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory.</p></body></html></string>
|
||||
<string><html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Convert</string>
|
||||
@@ -510,7 +516,7 @@
|
||||
<tabstop>upscaleBox</tabstop>
|
||||
<tabstop>gammaBox</tabstop>
|
||||
<tabstop>borderBox</tabstop>
|
||||
<tabstop>noDitheringBox</tabstop>
|
||||
<tabstop>outputSplit</tabstop>
|
||||
<tabstop>colorBox</tabstop>
|
||||
<tabstop>editorButton</tabstop>
|
||||
<tabstop>wikiButton</tabstop>
|
||||
|
||||
@@ -112,19 +112,6 @@
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="coloristLine"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="muidLine"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
14
kcc-c2e.py
14
kcc-c2e.py
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -23,14 +23,10 @@ if sys.version_info[0] != 3:
|
||||
print('ERROR: This is Python 3 script!')
|
||||
exit(1)
|
||||
|
||||
from kcc.shared import dependencyCheck
|
||||
dependencyCheck(2)
|
||||
|
||||
from multiprocessing import freeze_support
|
||||
from kcc import __version__
|
||||
from kcc.comic2ebook import main
|
||||
from multiprocessing import freeze_support, set_start_method
|
||||
from kindlecomicconverter.startup import startC2E
|
||||
|
||||
if __name__ == "__main__":
|
||||
set_start_method('spawn')
|
||||
freeze_support()
|
||||
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
startC2E()
|
||||
|
||||
14
kcc-c2p.py
14
kcc-c2p.py
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -23,14 +23,10 @@ if sys.version_info[0] != 3:
|
||||
print('ERROR: This is Python 3 script!')
|
||||
exit(1)
|
||||
|
||||
from kcc.shared import dependencyCheck
|
||||
dependencyCheck(1)
|
||||
|
||||
from multiprocessing import freeze_support
|
||||
from kcc import __version__
|
||||
from kcc.comic2panel import main
|
||||
from multiprocessing import freeze_support, set_start_method
|
||||
from kindlecomicconverter.startup import startC2P
|
||||
|
||||
if __name__ == "__main__":
|
||||
set_start_method('spawn')
|
||||
freeze_support()
|
||||
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
startC2P()
|
||||
|
||||
10
kcc.iss
10
kcc.iss
@@ -1,5 +1,5 @@
|
||||
#define MyAppName "Kindle Comic Converter"
|
||||
#define MyAppVersion "5.1.2"
|
||||
#define MyAppVersion "5.5.2"
|
||||
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
|
||||
#define MyAppURL "http://kcc.iosphe.re/"
|
||||
#define MyAppExeName "KCC.exe"
|
||||
@@ -12,7 +12,7 @@ AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
AppCopyright=Copyright (C) 2012-2016 Ciro Mattia Gonano and Paweł Jastrzębski
|
||||
AppCopyright=Copyright (C) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski
|
||||
ArchitecturesAllowed=x64
|
||||
DefaultDirName={pf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
@@ -47,9 +47,8 @@ Name: "CB7association"; Description: "CB7"; GroupDescription: "File associations
|
||||
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak
|
||||
Source: "other\windows\Additional-LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "other\windows\UnRAR.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "other\windows\7za.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "other\windows\vc_redist.x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall
|
||||
Source: "other\windows\7z.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "other\windows\7z.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
@@ -57,7 +56,6 @@ Name: "{group}\Readme"; Filename: "https://github.com/ciromattia/kcc#kcc"
|
||||
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[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
|
||||
|
||||
[Messages]
|
||||
|
||||
60
kcc.py
60
kcc.py
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -28,59 +28,29 @@ import os
|
||||
if sys.platform.startswith('darwin'):
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.environ['PATH'] = os.path.dirname(os.path.abspath(sys.executable)) + \
|
||||
'/../Resources:/usr/local/bin:/usr/bin:/bin'
|
||||
os.system('defaults write com.kindlecomicconverter.KindleComicConverter ApplePersistenceIgnoreState YES')
|
||||
os.system('defaults write com.kindlecomicconverter.KindleComicConverter NSInitialToolTipDelay -int 1000')
|
||||
'/../Resources:/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/' \
|
||||
'MacOS:/usr/local/bin:/usr/bin:/bin'
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)) + '/../Resources')
|
||||
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'):
|
||||
import multiprocessing.popen_spawn_win32 as forking
|
||||
|
||||
class _Popen(forking.Popen):
|
||||
def __init__(self, *args, **kw):
|
||||
if hasattr(sys, 'frozen'):
|
||||
# noinspection 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):
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
else:
|
||||
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/windows/;' + os.environ['PATH']
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
# Load additional Sentry configuration
|
||||
if getattr(sys, 'frozen', False):
|
||||
try:
|
||||
import kcc.sentry
|
||||
except:
|
||||
pass
|
||||
# if getattr(sys, 'frozen', False):
|
||||
# try:
|
||||
# import kindlecomicconverter.sentry
|
||||
# except ImportError:
|
||||
# pass
|
||||
|
||||
from kcc.shared import dependencyCheck
|
||||
dependencyCheck(3)
|
||||
|
||||
from multiprocessing import freeze_support
|
||||
from kcc import KCC_gui
|
||||
from multiprocessing import freeze_support, set_start_method
|
||||
from kindlecomicconverter.startup import start
|
||||
|
||||
if __name__ == "__main__":
|
||||
set_start_method('spawn')
|
||||
freeze_support()
|
||||
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1"
|
||||
KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
|
||||
if KCCAplication.isRunning():
|
||||
if len(sys.argv) > 1:
|
||||
KCCAplication.sendMessage(sys.argv[1])
|
||||
else:
|
||||
KCCAplication.sendMessage('ARISE')
|
||||
else:
|
||||
KCCWindow = KCC_gui.QMainWindowKCC()
|
||||
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
|
||||
if len(sys.argv) > 1:
|
||||
KCCUI.handleMessage(sys.argv[1])
|
||||
sys.exit(KCCAplication.exec_())
|
||||
start()
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 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 sys
|
||||
import os
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
from subprocess import STDOUT, PIPE
|
||||
from psutil import Popen
|
||||
from shutil import move, copy
|
||||
try:
|
||||
from scandir import walk
|
||||
except ImportError:
|
||||
walk = os.walk
|
||||
from . import rarfile
|
||||
from .shared import check7ZFile as is_7zfile, saferReplace
|
||||
|
||||
|
||||
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 # skip MacOS special files
|
||||
elif f.endswith('/'):
|
||||
try:
|
||||
os.makedirs(os.path.join(targetdir, f))
|
||||
except Exception:
|
||||
pass # the dir exists so we are going to extract the images only.
|
||||
else:
|
||||
filelist.append(f)
|
||||
cbzFile.extractall(targetdir, filelist)
|
||||
|
||||
def extractCBR(self, targetdir):
|
||||
cbrFile = rarfile.RarFile(self.origFileName)
|
||||
cbrFile.extractall(targetdir)
|
||||
for root, dirnames, filenames in 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):
|
||||
# Workaround for some wide UTF-8 + Popen abnormalities
|
||||
if sys.platform.startswith('darwin'):
|
||||
copy(self.origFileName, os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP'))
|
||||
self.origFileName = os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP')
|
||||
output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' +
|
||||
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||
extracted = False
|
||||
for line in output.stdout:
|
||||
if b"Everything is Ok" in line:
|
||||
extracted = True
|
||||
if sys.platform.startswith('darwin'):
|
||||
os.remove(self.origFileName)
|
||||
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])):
|
||||
# If directory names contain UTF-8 chars shutil.move can't clean up the mess alone
|
||||
if os.path.isdir(os.path.join(targetdir, f)):
|
||||
saferReplace(os.path.join(targetdir, adir[0], f), os.path.join(targetdir, adir[0], f + '-A'))
|
||||
f += '-A'
|
||||
move(os.path.join(targetdir, adir[0], f), targetdir)
|
||||
os.rmdir(os.path.join(targetdir, adir[0]))
|
||||
return targetdir
|
||||
462
kcc/image.py
462
kcc/image.py
@@ -1,462 +0,0 @@
|
||||
# Copyright (C) 2010 Alex Yatskov
|
||||
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import quote
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops
|
||||
from .shared import md5Checksum
|
||||
from . import __version__
|
||||
|
||||
|
||||
class ProfileData:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
Palette4 = [
|
||||
0x00, 0x00, 0x00,
|
||||
0x55, 0x55, 0x55,
|
||||
0xaa, 0xaa, 0xaa,
|
||||
0xff, 0xff, 0xff
|
||||
]
|
||||
|
||||
Palette15 = [
|
||||
0x00, 0x00, 0x00,
|
||||
0x11, 0x11, 0x11,
|
||||
0x22, 0x22, 0x22,
|
||||
0x33, 0x33, 0x33,
|
||||
0x44, 0x44, 0x44,
|
||||
0x55, 0x55, 0x55,
|
||||
0x66, 0x66, 0x66,
|
||||
0x77, 0x77, 0x77,
|
||||
0x88, 0x88, 0x88,
|
||||
0x99, 0x99, 0x99,
|
||||
0xaa, 0xaa, 0xaa,
|
||||
0xbb, 0xbb, 0xbb,
|
||||
0xcc, 0xcc, 0xcc,
|
||||
0xdd, 0xdd, 0xdd,
|
||||
0xff, 0xff, 0xff,
|
||||
]
|
||||
|
||||
Palette16 = [
|
||||
0x00, 0x00, 0x00,
|
||||
0x11, 0x11, 0x11,
|
||||
0x22, 0x22, 0x22,
|
||||
0x33, 0x33, 0x33,
|
||||
0x44, 0x44, 0x44,
|
||||
0x55, 0x55, 0x55,
|
||||
0x66, 0x66, 0x66,
|
||||
0x77, 0x77, 0x77,
|
||||
0x88, 0x88, 0x88,
|
||||
0x99, 0x99, 0x99,
|
||||
0xaa, 0xaa, 0xaa,
|
||||
0xbb, 0xbb, 0xbb,
|
||||
0xcc, 0xcc, 0xcc,
|
||||
0xdd, 0xdd, 0xdd,
|
||||
0xee, 0xee, 0xee,
|
||||
0xff, 0xff, 0xff,
|
||||
]
|
||||
|
||||
PalleteNull = [
|
||||
]
|
||||
|
||||
Profiles = {
|
||||
'K1': ("Kindle 1", (600, 670), Palette4, 1.8, (900, 1005)),
|
||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)),
|
||||
'K3': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'K45': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)),
|
||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8, (1608, 2172)),
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8, (1152, 1536)),
|
||||
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8, (1608, 2172)),
|
||||
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)),
|
||||
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8, (1620, 2145)),
|
||||
'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)),
|
||||
}
|
||||
|
||||
|
||||
class ComicPageParser:
|
||||
def __init__(self, source, options):
|
||||
self.opt = options
|
||||
self.source = source
|
||||
self.size = self.opt.profileData[1]
|
||||
self.payload = []
|
||||
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
|
||||
self.color = self.colorCheck()
|
||||
self.fill = self.fillCheck()
|
||||
self.splitCheck()
|
||||
if self.opt.hqmode:
|
||||
self.sizeCheck()
|
||||
|
||||
def getImageHistogram(self, image):
|
||||
histogram = image.histogram()
|
||||
if histogram[0] == 0:
|
||||
return -1
|
||||
elif histogram[255] == 0:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def splitCheck(self):
|
||||
width, height = self.image.size
|
||||
dstwidth, dstheight = self.size
|
||||
# Only split if origin is not oriented the same as target
|
||||
if (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
||||
if self.opt.splitter != 1:
|
||||
if width > height:
|
||||
# Source is landscape, so split by the width
|
||||
leftbox = (0, 0, int(width / 2), height)
|
||||
rightbox = (int(width / 2), 0, width, height)
|
||||
else:
|
||||
# Source is portrait and target is landscape, so split by the height
|
||||
leftbox = (0, 0, width, int(height / 2))
|
||||
rightbox = (0, int(height / 2), width, height)
|
||||
if self.opt.righttoleft:
|
||||
pageone = self.image.crop(rightbox)
|
||||
pagetwo = self.image.crop(leftbox)
|
||||
else:
|
||||
pageone = self.image.crop(leftbox)
|
||||
pagetwo = self.image.crop(rightbox)
|
||||
self.payload.append(['S1', self.source, pageone, self.color, self.fill])
|
||||
self.payload.append(['S2', self.source, pagetwo, self.color, self.fill])
|
||||
if self.opt.splitter > 0:
|
||||
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True),
|
||||
self.color, self.fill])
|
||||
else:
|
||||
self.payload.append(['N', self.source, self.image, self.color, self.fill])
|
||||
|
||||
def colorCheck(self):
|
||||
if self.opt.webtoon:
|
||||
return True
|
||||
else:
|
||||
img = self.image.copy()
|
||||
bands = img.getbands()
|
||||
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
|
||||
thumb = img.resize((40, 40))
|
||||
SSE, bias = 0, [0, 0, 0]
|
||||
bias = ImageStat.Stat(thumb).mean[:3]
|
||||
bias = [b - sum(bias) / 3 for b in bias]
|
||||
for pixel in thumb.getdata():
|
||||
mu = sum(pixel) / 3
|
||||
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
|
||||
MSE = float(SSE) / (40 * 40)
|
||||
if MSE > 22:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def fillCheck(self):
|
||||
if self.opt.bordersColor:
|
||||
return self.opt.bordersColor
|
||||
else:
|
||||
bw = self.image.convert('L').point(lambda x: 0 if x < 128 else 255, '1')
|
||||
imageBoxA = bw.getbbox()
|
||||
imageBoxB = ImageChops.invert(bw).getbbox()
|
||||
if imageBoxA is None or imageBoxB is None:
|
||||
surfaceB, surfaceW = 0, 0
|
||||
diff = 0
|
||||
else:
|
||||
surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1])
|
||||
surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1])
|
||||
diff = ((max(surfaceB, surfaceW) - min(surfaceB, surfaceW)) / min(surfaceB, surfaceW)) * 100
|
||||
if diff > 0.5:
|
||||
if surfaceW < surfaceB:
|
||||
return 'white'
|
||||
elif surfaceW > surfaceB:
|
||||
return 'black'
|
||||
else:
|
||||
fill = 0
|
||||
startY = 0
|
||||
while startY < bw.size[1]:
|
||||
if startY + 5 > bw.size[1]:
|
||||
startY = bw.size[1] - 5
|
||||
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY + 5)))
|
||||
startY += 5
|
||||
startX = 0
|
||||
while startX < bw.size[0]:
|
||||
if startX + 5 > bw.size[0]:
|
||||
startX = bw.size[0] - 5
|
||||
fill += self.getImageHistogram(bw.crop((startX, 0, startX + 5, bw.size[1])))
|
||||
startX += 5
|
||||
if fill > 0:
|
||||
return 'black'
|
||||
else:
|
||||
return 'white'
|
||||
|
||||
def sizeCheck(self):
|
||||
additionalPayload = []
|
||||
width, height = self.image.size
|
||||
dstwidth, dstheight = self.size
|
||||
for work in self.payload:
|
||||
if width > dstwidth and height > dstheight:
|
||||
additionalPayload.append([work[0] + '+', work[1], work[2].copy(), work[3], work[4]])
|
||||
self.payload = self.payload + additionalPayload
|
||||
|
||||
|
||||
class ComicPage:
|
||||
def __init__(self, mode, path, image, color, fill, options):
|
||||
self.opt = options
|
||||
_, self.size, self.palette, self.gamma, self.panelviewsize = self.opt.profileData
|
||||
self.image = image
|
||||
self.color = color
|
||||
self.fill = fill
|
||||
self.rotated = False
|
||||
self.orgPath = os.path.join(path[0], path[1])
|
||||
if '+' in mode:
|
||||
self.hqMode = True
|
||||
else:
|
||||
self.hqMode = False
|
||||
if 'N' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
|
||||
elif 'R' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
|
||||
self.rotated = True
|
||||
elif 'S1' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-B'
|
||||
elif 'S2' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-C'
|
||||
|
||||
def saveToDir(self):
|
||||
try:
|
||||
flags = []
|
||||
if not self.opt.forcecolor and not self.opt.forcepng:
|
||||
self.image = self.image.convert('L')
|
||||
if self.rotated:
|
||||
flags.append('Rotated')
|
||||
if self.fill != 'white':
|
||||
flags.append('BlackFill')
|
||||
if self.hqMode:
|
||||
self.targetPath += '-HQ'
|
||||
if self.opt.forcepng:
|
||||
self.targetPath += '.png'
|
||||
self.image.save(self.targetPath, 'PNG', optimize=1)
|
||||
else:
|
||||
self.targetPath += '.jpg'
|
||||
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=80)
|
||||
return [md5Checksum(self.targetPath), flags, self.orgPath]
|
||||
except IOError:
|
||||
raise RuntimeError('Cannot save image.')
|
||||
|
||||
def autocontrastImage(self):
|
||||
gamma = self.opt.gamma
|
||||
if gamma < 0.1:
|
||||
gamma = self.gamma
|
||||
if self.gamma != 1.0 and self.color:
|
||||
gamma = 1.0
|
||||
if gamma == 1.0:
|
||||
self.image = ImageOps.autocontrast(self.image)
|
||||
else:
|
||||
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: 255 * (a / 255.) ** gamma))
|
||||
|
||||
def quantizeImage(self):
|
||||
colors = len(self.palette) // 3
|
||||
if colors < 256:
|
||||
self.palette += self.palette[:3] * (256 - colors)
|
||||
palImg = Image.new('P', (1, 1))
|
||||
palImg.putpalette(self.palette)
|
||||
self.image = self.image.convert('L')
|
||||
self.image = self.image.convert('RGB')
|
||||
# Quantize is deprecated but new function call it internally anyway...
|
||||
self.image = self.image.quantize(palette=palImg)
|
||||
|
||||
def resizeImage(self):
|
||||
if self.hqMode:
|
||||
size = (self.panelviewsize[0], self.panelviewsize[1])
|
||||
if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
|
||||
self.image.thumbnail(size, Image.LANCZOS)
|
||||
else:
|
||||
size = (self.size[0], self.size[1])
|
||||
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
|
||||
method = Image.BICUBIC
|
||||
else:
|
||||
method = Image.LANCZOS
|
||||
if self.opt.stretch:
|
||||
self.image = self.image.resize(size, method)
|
||||
elif self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale:
|
||||
if self.opt.format == 'CBZ':
|
||||
borderw = int((size[0] - self.image.size[0]) / 2)
|
||||
borderh = int((size[1] - self.image.size[1]) / 2)
|
||||
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
|
||||
if self.image.size[0] != size[0] or self.image.size[1] != size[1]:
|
||||
self.image = ImageOps.fit(self.image, size, method=Image.BICUBIC, centering=(0.5, 0.5))
|
||||
else:
|
||||
if self.opt.format == 'CBZ':
|
||||
ratioDev = float(size[0]) / float(size[1])
|
||||
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
|
||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
||||
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
|
||||
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
||||
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
||||
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
|
||||
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
|
||||
else:
|
||||
hpercent = size[1] / float(self.image.size[1])
|
||||
wsize = int((float(self.image.size[0]) * float(hpercent)))
|
||||
self.image = self.image.resize((wsize, size[1]), method)
|
||||
if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
|
||||
self.image.thumbnail(size, Image.LANCZOS)
|
||||
|
||||
def cutPageNumber(self, fixedThreshold):
|
||||
if ImageChops.invert(self.image).getbbox() is not None:
|
||||
widthImg, heightImg = self.image.size
|
||||
delta = 2
|
||||
diff = delta
|
||||
if ImageStat.Stat(self.image).var[0] < 2 * fixedThreshold:
|
||||
return self.image
|
||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\
|
||||
and diff < heightImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
pageNumberCut1 = diff
|
||||
if diff < delta:
|
||||
diff = delta
|
||||
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0]
|
||||
diff += delta
|
||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] - oldStat > 0\
|
||||
and diff < heightImg // 4:
|
||||
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0]
|
||||
diff += delta
|
||||
diff -= delta
|
||||
pageNumberCut2 = diff
|
||||
diff += delta
|
||||
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg,
|
||||
heightImg - pageNumberCut2))).var[0]
|
||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg - pageNumberCut2))).var[0]\
|
||||
< fixedThreshold + oldStat and diff < heightImg // 4:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
pageNumberCut3 = diff
|
||||
delta = 5
|
||||
diff = delta
|
||||
while ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut2, diff, heightImg))).var[0]\
|
||||
< fixedThreshold and diff < widthImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
pageNumberX1 = diff
|
||||
diff = delta
|
||||
while ImageStat.Stat(self.image.crop((widthImg - diff, heightImg - pageNumberCut2,
|
||||
widthImg, heightImg))).var[0] < fixedThreshold and diff < widthImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
pageNumberX2 = widthImg - diff
|
||||
if pageNumberCut3 - pageNumberCut1 > 2 * delta\
|
||||
and float(pageNumberX2 - pageNumberX1) / float(pageNumberCut2 - pageNumberCut1) <= 9.0\
|
||||
and ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut3, widthImg, heightImg))).var[0]\
|
||||
/ ImageStat.Stat(self.image).var[0] < 0.1\
|
||||
and pageNumberCut3 < heightImg / 4 - delta:
|
||||
diff = pageNumberCut3
|
||||
else:
|
||||
diff = pageNumberCut1
|
||||
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
|
||||
|
||||
def cropWhiteSpace(self, fixedThreshold):
|
||||
if ImageChops.invert(self.image).getbbox() is not None:
|
||||
widthImg, heightImg = self.image.size
|
||||
delta = 10
|
||||
diff = delta
|
||||
# top
|
||||
while ImageStat.Stat(self.image.crop((0, 0, widthImg, diff))).var[0] < fixedThreshold and diff < heightImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
self.image = self.image.crop((0, diff, widthImg, heightImg))
|
||||
widthImg, heightImg = self.image.size
|
||||
diff = delta
|
||||
# left
|
||||
while ImageStat.Stat(self.image.crop((0, 0, diff, heightImg))).var[0] < fixedThreshold and diff < widthImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
self.image = self.image.crop((diff, 0, widthImg, heightImg))
|
||||
widthImg, heightImg = self.image.size
|
||||
diff = delta
|
||||
# down
|
||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\
|
||||
and diff < heightImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
|
||||
widthImg, heightImg = self.image.size
|
||||
diff = delta
|
||||
# right
|
||||
while ImageStat.Stat(self.image.crop((widthImg - diff, 0, widthImg, heightImg))).var[0] < fixedThreshold\
|
||||
and diff < widthImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
self.image = self.image.crop((0, 0, widthImg - diff, heightImg))
|
||||
|
||||
|
||||
class Cover:
|
||||
def __init__(self, source, target, opt, tomeNumber):
|
||||
self.options = opt
|
||||
self.source = source
|
||||
self.target = target
|
||||
if tomeNumber == 0:
|
||||
self.tomeNumber = 1
|
||||
else:
|
||||
self.tomeNumber = tomeNumber
|
||||
if self.tomeNumber in self.options.remoteCovers:
|
||||
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))
|
||||
self.processExternal()
|
||||
except Exception:
|
||||
self.image = Image.open(source)
|
||||
self.processInternal()
|
||||
else:
|
||||
self.image = Image.open(source)
|
||||
self.processInternal()
|
||||
|
||||
def processInternal(self):
|
||||
self.image = self.image.convert('RGB')
|
||||
self.image = self.trim()
|
||||
self.save()
|
||||
|
||||
def processExternal(self):
|
||||
self.image = self.image.convert('RGB')
|
||||
self.image.thumbnail(self.options.profileData[1], Image.LANCZOS)
|
||||
self.save()
|
||||
|
||||
def trim(self):
|
||||
bg = Image.new(self.image.mode, self.image.size, self.image.getpixel((0, 0)))
|
||||
diff = ImageChops.difference(self.image, bg)
|
||||
diff = ImageChops.add(diff, diff, 2.0, -100)
|
||||
bbox = diff.getbbox()
|
||||
if bbox:
|
||||
return self.image.crop(bbox)
|
||||
else:
|
||||
return self.image
|
||||
|
||||
def save(self):
|
||||
try:
|
||||
self.image.save(self.target, "JPEG", optimize=1, quality=80)
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to process downloaded cover.')
|
||||
|
||||
def saveToKindle(self, kindle, asin):
|
||||
self.image = self.image.resize((300, 470), Image.ANTIALIAS).convert('L')
|
||||
try:
|
||||
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
|
||||
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG')
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to upload cover.')
|
||||
1990
kcc/rarfile.py
1990
kcc/rarfile.py
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -21,19 +21,19 @@ import os
|
||||
import sys
|
||||
from urllib.parse import unquote
|
||||
from urllib.request import urlopen, urlretrieve, Request
|
||||
from time import sleep, time
|
||||
from datetime import datetime
|
||||
from shutil import move
|
||||
from time import sleep
|
||||
from shutil import move, rmtree
|
||||
from subprocess import STDOUT, PIPE
|
||||
# noinspection PyUnresolvedReferences
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
|
||||
from xml.dom.minidom import parse, Document
|
||||
from xml.dom.minidom import parse
|
||||
from xml.sax.saxutils import escape
|
||||
from psutil import Popen, Process
|
||||
from copy import copy
|
||||
from distutils.version import StrictVersion
|
||||
from xml.sax.saxutils import escape
|
||||
from platform import platform
|
||||
from raven import Client
|
||||
from .shared import md5Checksum, HTMLStripper, sanitizeTrace
|
||||
from tempfile import gettempdir
|
||||
from .shared import md5Checksum, HTMLStripper, sanitizeTrace, walkLevel
|
||||
from . import __version__
|
||||
from . import comic2ebook
|
||||
from . import metadata
|
||||
@@ -154,12 +154,12 @@ class VersionThread(QtCore.QThread):
|
||||
self.getNewVersion()
|
||||
else:
|
||||
MW.addMessage.emit('<a href="https://kcc.iosphe.re/">'
|
||||
'<b>New version is available!</b></a> '
|
||||
'<b>The new version is available!</b></a> '
|
||||
'(<a href="https://github.com/ciromattia/kcc/releases/">'
|
||||
'Changelog</a>)', 'warning', False)
|
||||
|
||||
def setAnswer(self, dialogAnswer):
|
||||
self.answer = dialogAnswer
|
||||
def setAnswer(self, dialoganswer):
|
||||
self.answer = dialoganswer
|
||||
|
||||
def getNewVersion(self):
|
||||
while self.answer is None:
|
||||
@@ -178,12 +178,12 @@ class VersionThread(QtCore.QThread):
|
||||
Popen(path[0] + '.exe /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||
MW.forceShutdown.emit()
|
||||
except Exception:
|
||||
MW.addMessage.emit('Failed to download update!', 'warning', False)
|
||||
MW.addMessage.emit('Failed to download the update!', 'warning', False)
|
||||
MW.hideProgressBar.emit()
|
||||
MW.modeConvert.emit(1)
|
||||
|
||||
def getNewVersionTick(self, size, blockSize, totalSize):
|
||||
progress = int((size / (totalSize // blockSize)) * 100)
|
||||
def getNewVersionTick(self, size, blocksize, totalsize):
|
||||
progress = int((size / (totalsize // blocksize)) * 100)
|
||||
if size == 0:
|
||||
MW.progressBarTick.emit('100')
|
||||
if progress > self.barProgress:
|
||||
@@ -240,6 +240,7 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.addTrayMessage.emit('Conversion interrupted.', 'Critical')
|
||||
MW.modeConvert.emit(1)
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
def run(self):
|
||||
MW.modeConvert.emit(0)
|
||||
|
||||
@@ -256,8 +257,10 @@ class WorkerThread(QtCore.QThread):
|
||||
options.splitter = 2
|
||||
elif GUI.rotateBox.checkState() == 2:
|
||||
options.splitter = 1
|
||||
if GUI.qualityBox.isChecked():
|
||||
options.hqmode = True
|
||||
if GUI.qualityBox.checkState() == 1:
|
||||
options.autoscale = True
|
||||
elif GUI.qualityBox.checkState() == 2:
|
||||
options.hq = True
|
||||
if GUI.webtoonBox.isChecked():
|
||||
options.webtoon = True
|
||||
if GUI.upscaleBox.checkState() == 1:
|
||||
@@ -270,8 +273,8 @@ class WorkerThread(QtCore.QThread):
|
||||
options.white_borders = True
|
||||
elif GUI.borderBox.checkState() == 2:
|
||||
options.black_borders = True
|
||||
if GUI.noDitheringBox.isChecked():
|
||||
options.forcepng = True
|
||||
if GUI.outputSplit.isChecked():
|
||||
options.batchsplit = 2
|
||||
if GUI.colorBox.isChecked():
|
||||
options.forcecolor = True
|
||||
if GUI.currentMode > 2:
|
||||
@@ -319,18 +322,24 @@ class WorkerThread(QtCore.QThread):
|
||||
GUI.progress.content = ''
|
||||
self.errors = True
|
||||
_, _, traceback = sys.exc_info()
|
||||
if len(err.args) == 1:
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||
else:
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
|
||||
GUI.sentry.extra_context({'realTraceback': err.args[1]})
|
||||
if ' is corrupted.' not in str(err):
|
||||
GUI.sentry.captureException()
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||
MW.addMessage.emit('Error during conversion! Please consult '
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> '
|
||||
'for more details.', 'error', False)
|
||||
MW.addTrayMessage.emit('Error during conversion!', 'Critical')
|
||||
if not self.conversionAlive:
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
os.remove(item)
|
||||
if 'outputPath' in locals():
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
os.remove(item)
|
||||
self.clean()
|
||||
return
|
||||
if not self.errors:
|
||||
@@ -392,7 +401,7 @@ class WorkerThread(QtCore.QThread):
|
||||
for item in outputPath:
|
||||
comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle(
|
||||
k, comic2ebook.options.covers[outputPath.index(item)][1])
|
||||
MW.addMessage.emit('Kindle detected. Uploading covers...', 'info', False)
|
||||
MW.addMessage.emit('Kindle detected. Uploading covers... <b>Done!</b>', 'info', False)
|
||||
else:
|
||||
GUI.progress.content = ''
|
||||
for item in outputPath:
|
||||
@@ -471,20 +480,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.jobList.clear()
|
||||
if self.UnRAR:
|
||||
if self.sevenza:
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)')
|
||||
else:
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cbr *.zip *.rar *.pdf)')
|
||||
if self.sevenzip:
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)')
|
||||
else:
|
||||
if self.sevenza:
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cb7 *.zip *.7z *.pdf)')
|
||||
else:
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.zip *.pdf)')
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.pdf)')
|
||||
for fname in fnames[0]:
|
||||
if fname != '':
|
||||
if sys.platform.startswith('win'):
|
||||
@@ -494,28 +494,30 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.jobList.scrollToBottom()
|
||||
|
||||
def selectFileMetaEditor(self):
|
||||
if self.UnRAR:
|
||||
if self.sevenza:
|
||||
sname = ''
|
||||
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,
|
||||
'Comic (*.cbz *.cbr *.cb7)')
|
||||
else:
|
||||
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cbr)')
|
||||
else:
|
||||
if self.sevenza:
|
||||
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cb7)')
|
||||
else:
|
||||
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz)')
|
||||
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))
|
||||
fname = ['']
|
||||
self.showDialog("Editor is disabled due to a lack of 7z.", 'error')
|
||||
if fname[0] != '':
|
||||
if sys.platform.startswith('win'):
|
||||
sname = fname[0].replace('/', '\\')
|
||||
else:
|
||||
sname = fname[0]
|
||||
self.lastPath = os.path.abspath(os.path.join(sname, os.pardir))
|
||||
if sname != '':
|
||||
try:
|
||||
self.editor.loadData(fname)
|
||||
self.editor.loadData(sname)
|
||||
except Exception as err:
|
||||
_, _, traceback = sys.exc_info()
|
||||
GUI.sentry.captureException()
|
||||
@@ -527,8 +529,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
def clearJobs(self):
|
||||
GUI.jobList.clear()
|
||||
|
||||
# noinspection PyCallByClass,PyTypeChecker,PyArgumentList
|
||||
def openWiki(self):
|
||||
# noinspection PyCallByClass
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki'))
|
||||
|
||||
def modeChange(self, mode):
|
||||
@@ -602,11 +604,25 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.upscaleBox.setEnabled(False)
|
||||
GUI.upscaleBox.setChecked(True)
|
||||
else:
|
||||
GUI.qualityBox.setEnabled(True)
|
||||
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
|
||||
if profile['PVOptions']:
|
||||
GUI.qualityBox.setEnabled(True)
|
||||
GUI.mangaBox.setEnabled(True)
|
||||
GUI.rotateBox.setEnabled(True)
|
||||
GUI.upscaleBox.setEnabled(True)
|
||||
|
||||
def togglequalityBox(self, value):
|
||||
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
|
||||
if value == 2:
|
||||
if profile['Label'] in ['KV', 'KO']:
|
||||
self.addMessage('This option is intended for older Kindle models.', 'warning')
|
||||
self.addMessage('On this device, quality improvement will be negligible.', 'warning')
|
||||
GUI.upscaleBox.setEnabled(False)
|
||||
GUI.upscaleBox.setChecked(True)
|
||||
else:
|
||||
GUI.upscaleBox.setEnabled(True)
|
||||
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
|
||||
|
||||
def changeGamma(self, value):
|
||||
valueRaw = int(5 * round(float(value) / 5))
|
||||
value = '%.2f' % (float(valueRaw) / 100)
|
||||
@@ -628,21 +644,28 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.changeFormat()
|
||||
GUI.gammaSlider.setValue(0)
|
||||
self.changeGamma(0)
|
||||
GUI.qualityBox.setEnabled(profile['Quality'])
|
||||
if not GUI.webtoonBox.isChecked():
|
||||
GUI.qualityBox.setEnabled(profile['PVOptions'])
|
||||
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
|
||||
if not profile['Quality']:
|
||||
if not profile['PVOptions']:
|
||||
GUI.qualityBox.setChecked(False)
|
||||
if str(GUI.deviceBox.currentText()) == 'Other':
|
||||
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
|
||||
'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())]
|
||||
if outputFormat is not None:
|
||||
GUI.formatBox.setCurrentIndex(outputFormat)
|
||||
if outputformat is not None:
|
||||
GUI.formatBox.setCurrentIndex(outputformat)
|
||||
else:
|
||||
GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
|
||||
GUI.qualityBox.setEnabled(profile['Quality'])
|
||||
if not GUI.webtoonBox.isChecked():
|
||||
GUI.qualityBox.setEnabled(profile['PVOptions'])
|
||||
if str(GUI.formatBox.currentText()) == 'MOBI/AZW3':
|
||||
GUI.outputSplit.setEnabled(True)
|
||||
else:
|
||||
GUI.outputSplit.setEnabled(False)
|
||||
GUI.outputSplit.setChecked(False)
|
||||
|
||||
def stripTags(self, html):
|
||||
s = HTMLStripper()
|
||||
@@ -661,7 +684,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
# We still fill original text field with transparent content to trigger creation of horizontal scrollbar
|
||||
item.setForeground(QtGui.QColor('transparent'))
|
||||
label = QtWidgets.QLabel(message)
|
||||
label.setStyleSheet('background-image:url('');background-color:rgba(0,0,0,0);')
|
||||
label.setStyleSheet('background-image:url('');background-color:rgba(0,0,0,0);color:rgb(0,0,0);')
|
||||
label.setOpenExternalLinks(True)
|
||||
GUI.jobList.addItem(item)
|
||||
GUI.jobList.setItemWidget(item, label)
|
||||
@@ -693,11 +716,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
def convertStart(self):
|
||||
if self.conversionAlive:
|
||||
GUI.convertButton.setEnabled(False)
|
||||
self.addMessage('Process will be interrupted. Please wait.', 'warning')
|
||||
self.addMessage('The process will be interrupted. Please wait.', 'warning')
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
else:
|
||||
# noinspection PyArgumentList
|
||||
if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
|
||||
dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
|
||||
if dname != '':
|
||||
@@ -740,7 +762,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
def saveSettings(self, event):
|
||||
if self.conversionAlive:
|
||||
GUI.convertButton.setEnabled(False)
|
||||
self.addMessage('Process will be interrupted. Please wait.', 'warning')
|
||||
self.addMessage('The process will be interrupted. Please wait.', 'warning')
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
event.ignore()
|
||||
@@ -759,7 +781,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'upscaleBox': GUI.upscaleBox.checkState(),
|
||||
'borderBox': GUI.borderBox.checkState(),
|
||||
'webtoonBox': GUI.webtoonBox.checkState(),
|
||||
'noDitheringBox': GUI.noDitheringBox.checkState(),
|
||||
'outputSplit': GUI.outputSplit.checkState(),
|
||||
'colorBox': GUI.colorBox.checkState(),
|
||||
'widthBox': GUI.widthBox.value(),
|
||||
'heightBox': GUI.heightBox.value(),
|
||||
@@ -776,16 +798,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.jobList.clear()
|
||||
if self.UnRAR:
|
||||
if self.sevenza:
|
||||
formats = ['.cbz', '.cbr', '.cb7', '.zip', '.rar', '.7z', '.pdf']
|
||||
else:
|
||||
formats = ['.cbz', '.cbr', '.zip', '.rar', '.pdf']
|
||||
else:
|
||||
if self.sevenza:
|
||||
formats = ['.cbz', '.cb7', '.zip', '.7z', '.pdf']
|
||||
else:
|
||||
formats = ['.cbz', '.zip', '.pdf']
|
||||
formats = ['.pdf']
|
||||
if self.sevenzip:
|
||||
formats.extend(['.cb7', '.7z', '.cbz', '.zip', '.cbr', '.rar'])
|
||||
if os.path.isdir(message):
|
||||
GUI.jobList.addItem(message)
|
||||
GUI.jobList.scrollToBottom()
|
||||
@@ -795,7 +810,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.jobList.addItem(message)
|
||||
GUI.jobList.scrollToBottom()
|
||||
else:
|
||||
self.addMessage('This file type is unsupported!', 'error')
|
||||
self.addMessage('Unsupported file type for ' + message, 'error')
|
||||
|
||||
def dragAndDrop(self, e):
|
||||
e.accept()
|
||||
@@ -822,7 +837,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
except Exception:
|
||||
pass
|
||||
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
|
||||
versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||
for line in versionCheck.stdout:
|
||||
@@ -841,15 +857,15 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if sys.platform.startswith('win'):
|
||||
self.addMessage('Download it and place EXE in KCC directory.', 'error')
|
||||
elif sys.platform.startswith('darwin'):
|
||||
self.addMessage('Install it using <a href="http://brew.sh/">Brew</a>.', 'error')
|
||||
self.addMessage('Install it using <a href="http://brew.sh/">Brew</a>: <i>brew cask install kindle-c'
|
||||
'omic-creator</i>', 'error')
|
||||
else:
|
||||
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
|
||||
|
||||
# noinspection PyArgumentList
|
||||
def __init__(self, KCCAplication, KCCWindow):
|
||||
def __init__(self, kccapp, kccwindow):
|
||||
global APP, MW, GUI
|
||||
APP = KCCAplication
|
||||
MW = KCCWindow
|
||||
APP = kccapp
|
||||
MW = kccwindow
|
||||
GUI = self
|
||||
self.setupUi(MW)
|
||||
self.editor = KCCGUI_MetaEditor()
|
||||
@@ -874,6 +890,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.targetDirectory = ''
|
||||
self.sentry = Client(release=__version__)
|
||||
if sys.platform.startswith('win'):
|
||||
# noinspection PyUnresolvedReferences
|
||||
from psutil import BELOW_NORMAL_PRIORITY_CLASS
|
||||
self.p = Process(os.getpid())
|
||||
self.p.nice(BELOW_NORMAL_PRIORITY_CLASS)
|
||||
@@ -893,59 +910,72 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
MW.resize(500, 500)
|
||||
|
||||
self.profiles = {
|
||||
"Kindle Oasis": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
"Kindle Oasis 2/3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KO'},
|
||||
"Kindle Oasis": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KV'},
|
||||
"Kindle Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
"Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KV'},
|
||||
"Kindle PW 3": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KV'},
|
||||
"Kindle PW 1/2": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
"Kindle PW 3/4": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KV'},
|
||||
"Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KPW'},
|
||||
"Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K45'},
|
||||
"Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
"Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K578'},
|
||||
"Kindle DX/DXG": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
'DefaultUpscale': False, 'Label': 'KDX'},
|
||||
"Kobo Mini/Touch": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
"Kobo Mini/Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoMT'},
|
||||
"Kobo Glo": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
"Kobo Glo": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoG'},
|
||||
"Kobo Glo HD": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
"Kobo Glo HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoGHD'},
|
||||
"Kobo Aura": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
"Kobo Aura": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'KoA'},
|
||||
"Kobo Aura HD": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
"Kobo Aura HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': True, 'Label': 'KoAHD'},
|
||||
"Kobo Aura H2O": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
"Kobo Aura H2O": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': True, 'Label': 'KoAH2O'},
|
||||
"Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1,
|
||||
"Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': True, 'Label': 'KoAO'},
|
||||
"Kobo Forma": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': True, 'Label': 'KoF'},
|
||||
"Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'OTHER'},
|
||||
"Kindle 1": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
"Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K1'},
|
||||
"Kindle 2": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
"Kindle 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K2'},
|
||||
"Kindle 3": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K3'},
|
||||
"Kindle Keyboard": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K34'},
|
||||
"Kindle Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K34'},
|
||||
}
|
||||
profilesGUI = [
|
||||
"Kindle Oasis 2/3",
|
||||
"Kindle Oasis",
|
||||
"Kindle Voyage",
|
||||
"Kindle PW 3",
|
||||
"Kindle PW 3/4",
|
||||
"Kindle PW 1/2",
|
||||
"Kindle",
|
||||
"Separator",
|
||||
"Kobo Forma",
|
||||
"Kobo Aura ONE",
|
||||
"Kobo Aura H2O",
|
||||
"Kobo Aura HD",
|
||||
"Kobo Aura",
|
||||
"Kobo Glo HD",
|
||||
"Kobo Glo",
|
||||
"Kobo Mini/Touch",
|
||||
"Separator",
|
||||
"Other",
|
||||
"Separator",
|
||||
"Kindle Touch",
|
||||
"Kindle Keyboard",
|
||||
"Kindle DX/DXG",
|
||||
"Kindle 3",
|
||||
"Kindle 2",
|
||||
"Kindle 1",
|
||||
"Separator",
|
||||
"Kobo Glo HD",
|
||||
"Kobo Glo",
|
||||
"Kobo Mini/Touch",
|
||||
]
|
||||
|
||||
statusBarLabel = QtWidgets.QLabel('<b><a href="https://kcc.iosphe.re/">HOMEPAGE</a> - <a href="https://github.'
|
||||
@@ -957,27 +987,19 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
|
||||
|
||||
self.addMessage('<b>Welcome!</b>', 'info')
|
||||
self.addMessage('<b>Remember:</b> All options have additional informations in tooltips.', 'info')
|
||||
self.addMessage('<b>Remember:</b> All options have additional information in tooltips.', 'info')
|
||||
if self.startNumber < 5:
|
||||
self.addMessage('Since you are 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>.',
|
||||
'info')
|
||||
rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||
rarExitCode = rarExitCode.wait()
|
||||
if rarExitCode == 0 or rarExitCode == 7:
|
||||
self.UnRAR = True
|
||||
process = Popen('7z', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||
process.communicate()
|
||||
if process.returncode == 0 or process.returncode == 7:
|
||||
self.sevenzip = True
|
||||
else:
|
||||
self.UnRAR = False
|
||||
self.addMessage('Cannot find <a href="http://www.rarlab.com/rar_add.htm">UnRAR</a>!'
|
||||
' Processing of CBR/RAR files will be disabled.', 'warning')
|
||||
sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||
sevenzaExitCode = sevenzaExitCode.wait()
|
||||
if sevenzaExitCode == 0 or sevenzaExitCode == 7:
|
||||
self.sevenza = True
|
||||
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.sevenzip = False
|
||||
self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7z</a>!'
|
||||
' Processing of archives will be disabled.', 'warning')
|
||||
self.detectKindleGen(True)
|
||||
|
||||
APP.messageFromOtherInstance.connect(self.handleMessage)
|
||||
@@ -990,6 +1012,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
||||
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
|
||||
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
|
||||
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
|
||||
GUI.deviceBox.activated.connect(self.changeDevice)
|
||||
GUI.formatBox.activated.connect(self.changeFormat)
|
||||
MW.progressBarTick.connect(self.updateProgressbar)
|
||||
@@ -1046,6 +1069,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.versionCheck.start()
|
||||
self.tray.show()
|
||||
|
||||
# Cleanup unfisnished conversion
|
||||
for root, dirs, _ in walkLevel(gettempdir(), 0):
|
||||
for tempdir in dirs:
|
||||
if tempdir.startswith('KCC-'):
|
||||
rmtree(os.path.join(root, tempdir), True)
|
||||
|
||||
if self.windowSize != '0x0':
|
||||
x, y = self.windowSize.split('x')
|
||||
MW.resize(int(x), int(y))
|
||||
@@ -1057,7 +1086,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
|
||||
def loadData(self, file):
|
||||
self.parser = metadata.MetadataParser(file)
|
||||
if self.parser.compressor == 'rar':
|
||||
if self.parser.format in ['RAR', 'RAR5']:
|
||||
self.editorWidget.setEnabled(False)
|
||||
self.okButton.setEnabled(False)
|
||||
self.statusLabel.setText('CBR metadata are read-only.')
|
||||
@@ -1065,23 +1094,20 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
|
||||
self.editorWidget.setEnabled(True)
|
||||
self.okButton.setEnabled(True)
|
||||
self.statusLabel.setText('Separate authors with a comma.')
|
||||
for field in (self.seriesLine, self.volumeLine, self.numberLine, self.muidLine):
|
||||
if field.objectName() == 'muidLine':
|
||||
field.setText(self.parser.data['MUid'])
|
||||
else:
|
||||
field.setText(self.parser.data[field.objectName().capitalize()[:-4]])
|
||||
for field in (self.seriesLine, self.volumeLine, self.numberLine):
|
||||
field.setText(self.parser.data[field.objectName().capitalize()[:-4]])
|
||||
for field in (self.writerLine, self.pencillerLine, self.inkerLine, self.coloristLine):
|
||||
field.setText(', '.join(self.parser.data[field.objectName().capitalize()[:-4] + 's']))
|
||||
if self.seriesLine.text() == '':
|
||||
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):
|
||||
for field in (self.volumeLine, self.numberLine, self.muidLine):
|
||||
for field in (self.volumeLine, self.numberLine):
|
||||
if field.text().isnumeric() or self.cleanData(field.text()) == '':
|
||||
if field.objectName() == 'muidLine':
|
||||
self.parser.data['MUid'] = self.cleanData(field.text())
|
||||
else:
|
||||
self.parser.data[field.objectName().capitalize()[:-4]] = self.cleanData(field.text())
|
||||
self.parser.data[field.objectName().capitalize()[:-4]] = self.cleanData(field.text())
|
||||
else:
|
||||
self.statusLabel.setText(field.objectName().capitalize()[:-4] + ' field must be a number.')
|
||||
break
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'gui\KCC.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.6
|
||||
# Created by: PyQt5 UI code generator 5.8.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -31,7 +31,7 @@ class Ui_mainWindow(object):
|
||||
self.progressBar.setObjectName("progressBar")
|
||||
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
|
||||
self.jobList = QtWidgets.QListWidget(self.centralWidget)
|
||||
self.jobList.setStyleSheet("QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}")
|
||||
self.jobList.setStyleSheet("QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}")
|
||||
self.jobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.jobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.jobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
@@ -81,6 +81,7 @@ class Ui_mainWindow(object):
|
||||
self.rotateBox.setObjectName("rotateBox")
|
||||
self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1)
|
||||
self.qualityBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||
self.qualityBox.setTristate(True)
|
||||
self.qualityBox.setObjectName("qualityBox")
|
||||
self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1)
|
||||
self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||
@@ -97,9 +98,9 @@ class Ui_mainWindow(object):
|
||||
self.borderBox.setTristate(True)
|
||||
self.borderBox.setObjectName("borderBox")
|
||||
self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1)
|
||||
self.noDitheringBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||
self.noDitheringBox.setObjectName("noDitheringBox")
|
||||
self.gridLayout_2.addWidget(self.noDitheringBox, 2, 1, 1, 1)
|
||||
self.outputSplit = QtWidgets.QCheckBox(self.optionWidget)
|
||||
self.outputSplit.setObjectName("outputSplit")
|
||||
self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
|
||||
self.colorBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||
self.colorBox.setObjectName("colorBox")
|
||||
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
|
||||
@@ -219,8 +220,8 @@ class Ui_mainWindow(object):
|
||||
mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox)
|
||||
mainWindow.setTabOrder(self.upscaleBox, self.gammaBox)
|
||||
mainWindow.setTabOrder(self.gammaBox, self.borderBox)
|
||||
mainWindow.setTabOrder(self.borderBox, self.noDitheringBox)
|
||||
mainWindow.setTabOrder(self.noDitheringBox, self.colorBox)
|
||||
mainWindow.setTabOrder(self.borderBox, self.outputSplit)
|
||||
mainWindow.setTabOrder(self.outputSplit, self.colorBox)
|
||||
mainWindow.setTabOrder(self.colorBox, self.editorButton)
|
||||
mainWindow.setTabOrder(self.editorButton, self.wikiButton)
|
||||
mainWindow.setTabOrder(self.wikiButton, self.jobList)
|
||||
@@ -231,32 +232,33 @@ class Ui_mainWindow(object):
|
||||
def retranslateUi(self, mainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
mainWindow.setWindowTitle(_translate("mainWindow", "Kindle Comic Converter"))
|
||||
self.hLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
|
||||
self.hLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||
self.hLabel.setText(_translate("mainWindow", "Custom height:"))
|
||||
self.widthBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
|
||||
self.wLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
|
||||
self.widthBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||
self.wLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||
self.wLabel.setText(_translate("mainWindow", "Custom width:"))
|
||||
self.heightBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
|
||||
self.heightBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||
self.mangaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
|
||||
self.mangaBox.setText(_translate("mainWindow", "Manga mode"))
|
||||
self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>"))
|
||||
self.rotateBox.setText(_translate("mainWindow", "Spread splitter"))
|
||||
self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>High quality Panel View.<br/>Require source files with bigger resolution than target device.<br/><span style=\" font-weight:600;\">Highly impact size of output file!</span></p></body></html>"))
|
||||
self.qualityBox.setText(_translate("mainWindow", "HQ zoom"))
|
||||
self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>"))
|
||||
self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2/HQ"))
|
||||
self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>"))
|
||||
self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode"))
|
||||
self.upscaleBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>"))
|
||||
self.upscaleBox.setText(_translate("mainWindow", "Stretch/Upscale"))
|
||||
self.gammaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable automatic gamma correction.</p></body></html>"))
|
||||
self.gammaBox.setText(_translate("mainWindow", "Custom gamma"))
|
||||
self.borderBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>"))
|
||||
self.borderBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>"))
|
||||
self.borderBox.setText(_translate("mainWindow", "W/B margins"))
|
||||
self.noDitheringBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Create PNG files instead JPEG.<br/>Quality increase is not noticeable on most of devices.<br/>Output files <span style=\" font-weight:600;\">might</span> be smaller.<br/><span style=\" font-weight:600;\">MOBI conversion will be much slower.</span></p></body></html>"))
|
||||
self.noDitheringBox.setText(_translate("mainWindow", "PNG output"))
|
||||
self.outputSplit.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>"))
|
||||
self.outputSplit.setText(_translate("mainWindow", "Output split"))
|
||||
self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>"))
|
||||
self.colorBox.setText(_translate("mainWindow", "Color mode"))
|
||||
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))
|
||||
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.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"))
|
||||
@@ -66,13 +66,6 @@ class Ui_editorDialog(object):
|
||||
self.coloristLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||
self.coloristLine.setObjectName("coloristLine")
|
||||
self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1)
|
||||
self.label_8 = QtWidgets.QLabel(self.editorWidget)
|
||||
self.label_8.setOpenExternalLinks(True)
|
||||
self.label_8.setObjectName("label_8")
|
||||
self.gridLayout.addWidget(self.label_8, 7, 0, 1, 1)
|
||||
self.muidLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||
self.muidLine.setObjectName("muidLine")
|
||||
self.gridLayout.addWidget(self.muidLine, 7, 1, 1, 1)
|
||||
self.verticalLayout.addWidget(self.editorWidget)
|
||||
self.optionWidget = QtWidgets.QWidget(editorDialog)
|
||||
self.optionWidget.setObjectName("optionWidget")
|
||||
@@ -117,7 +110,6 @@ class Ui_editorDialog(object):
|
||||
self.label_5.setText(_translate("editorDialog", "Penciller:"))
|
||||
self.label_6.setText(_translate("editorDialog", "Inker:"))
|
||||
self.label_7.setText(_translate("editorDialog", "Colorist:"))
|
||||
self.label_8.setText(_translate("editorDialog", "<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.cancelButton.setText(_translate("editorDialog", "Cancel"))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '5.1.2'
|
||||
__version__ = '5.5.2'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2016, 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'
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -23,16 +23,12 @@ import sys
|
||||
from shutil import rmtree, copytree, move
|
||||
from optparse import OptionParser, OptionGroup
|
||||
from multiprocessing import Pool
|
||||
from PIL import Image, ImageStat, ImageOps
|
||||
from .shared import getImageFileName, walkLevel, walkSort
|
||||
from PIL import Image, ImageChops, ImageOps, ImageDraw
|
||||
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||
try:
|
||||
from PyQt5 import QtCore
|
||||
except ImportError:
|
||||
QtCore = None
|
||||
try:
|
||||
from scandir import walk
|
||||
except ImportError:
|
||||
walk = os.walk
|
||||
|
||||
|
||||
def mergeDirectoryTick(output):
|
||||
@@ -52,7 +48,7 @@ def mergeDirectory(work):
|
||||
imagesValid = []
|
||||
sizes = []
|
||||
targetHeight = 0
|
||||
for root, dirs, files in walkLevel(directory, 0):
|
||||
for root, _, files in walkLevel(directory, 0):
|
||||
for name in files:
|
||||
if getImageFileName(name) is not None:
|
||||
i = Image.open(os.path.join(root, name))
|
||||
@@ -71,8 +67,7 @@ def mergeDirectory(work):
|
||||
result = Image.new('RGB', (targetWidth, targetHeight))
|
||||
y = 0
|
||||
for i in imagesValid:
|
||||
img = Image.open(i)
|
||||
img = img.convert('RGB')
|
||||
img = Image.open(i).convert('RGB')
|
||||
if img.size[0] < targetWidth:
|
||||
img = ImageOps.fit(img, (targetWidth, img.size[1]), method=Image.BICUBIC, centering=(0.5, 0.5))
|
||||
result.paste(img, (0, y))
|
||||
@@ -81,33 +76,11 @@ def mergeDirectory(work):
|
||||
savePath = os.path.split(imagesValid[0])
|
||||
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
|
||||
except Exception:
|
||||
return str(sys.exc_info()[1])
|
||||
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
|
||||
|
||||
|
||||
def sanitizePanelSize(panel, opt):
|
||||
newPanels = []
|
||||
if panel[2] > 6 * opt.height:
|
||||
diff = int(panel[2] / 8)
|
||||
newPanels.append([panel[0], panel[1] - diff * 7, diff])
|
||||
newPanels.append([panel[1] - diff * 7, panel[1] - diff * 6, diff])
|
||||
newPanels.append([panel[1] - diff * 6, panel[1] - diff * 5, diff])
|
||||
newPanels.append([panel[1] - diff * 5, panel[1] - diff * 4, diff])
|
||||
newPanels.append([panel[1] - diff * 4, panel[1] - diff * 3, diff])
|
||||
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
|
||||
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
|
||||
newPanels.append([panel[1] - diff, panel[1], diff])
|
||||
elif panel[2] > 3 * opt.height:
|
||||
diff = int(panel[2] / 4)
|
||||
newPanels.append([panel[0], panel[1] - diff * 3, diff])
|
||||
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
|
||||
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
|
||||
newPanels.append([panel[1] - diff, panel[1], diff])
|
||||
elif panel[2] > 1.5 * opt.height:
|
||||
newPanels.append([panel[0], panel[1] - int(panel[2] / 2), int(panel[2] / 2)])
|
||||
newPanels.append([panel[1] - int(panel[2] / 2), panel[1], int(panel[2] / 2)])
|
||||
else:
|
||||
newPanels = [panel]
|
||||
return newPanels
|
||||
def detectSolid(img):
|
||||
return not ImageChops.invert(img).getbbox() or not img.getbbox()
|
||||
|
||||
|
||||
def splitImageTick(output):
|
||||
@@ -120,61 +93,64 @@ def splitImageTick(output):
|
||||
splitWorkerPool.terminate()
|
||||
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
def splitImage(work):
|
||||
try:
|
||||
path = work[0]
|
||||
name = work[1]
|
||||
opt = work[2]
|
||||
# Hardcoded options
|
||||
threshold = 1.0
|
||||
delta = 15
|
||||
fileExpanded = os.path.splitext(name)
|
||||
filePath = os.path.join(path, name)
|
||||
image = Image.open(filePath)
|
||||
image = image.convert('RGB')
|
||||
widthImg, heightImg = image.size
|
||||
imgOrg = Image.open(filePath).convert('RGB')
|
||||
imgProcess = Image.open(filePath).convert('1')
|
||||
widthImg, heightImg = imgOrg.size
|
||||
if heightImg > opt.height:
|
||||
if opt.debug:
|
||||
from PIL import ImageDraw
|
||||
debugImage = Image.open(filePath)
|
||||
draw = ImageDraw.Draw(debugImage)
|
||||
drawImg = Image.open(filePath).convert(mode='RGBA')
|
||||
draw = ImageDraw.Draw(drawImg)
|
||||
|
||||
# Find panels
|
||||
y1 = 0
|
||||
y2 = 15
|
||||
yWork = 0
|
||||
panelDetected = False
|
||||
panels = []
|
||||
while y2 < heightImg:
|
||||
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] < threshold and y2 < heightImg:
|
||||
y2 += delta
|
||||
y2 -= delta
|
||||
y1Temp = y2
|
||||
y1 = y2 + delta
|
||||
y2 = y1 + delta
|
||||
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] >= threshold and y2 < heightImg:
|
||||
y1 += delta
|
||||
y2 += delta
|
||||
if y1 + delta >= heightImg:
|
||||
y1 = heightImg - 1
|
||||
y2Temp = y1
|
||||
if opt.debug:
|
||||
draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0))
|
||||
draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0))
|
||||
panelHeight = y2Temp - y1Temp
|
||||
if panelHeight > delta:
|
||||
# Panels that can't be cut nicely will be forcefully splitted
|
||||
panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], opt)
|
||||
for panel in panelsCleaned:
|
||||
panels.append(panel)
|
||||
while yWork < heightImg:
|
||||
tmpImg = imgProcess.crop([0, yWork, widthImg, yWork + 4])
|
||||
solid = detectSolid(tmpImg)
|
||||
if not solid and not panelDetected:
|
||||
panelDetected = True
|
||||
panelY1 = yWork - 2
|
||||
if solid and panelDetected:
|
||||
panelDetected = False
|
||||
panelY2 = yWork + 6
|
||||
panels.append((panelY1, panelY2, panelY2 - panelY1))
|
||||
yWork += 5
|
||||
|
||||
# Split too big panels
|
||||
panelsProcessed = []
|
||||
for panel in panels:
|
||||
if panel[2] <= opt.height * 1.5:
|
||||
panelsProcessed.append(panel)
|
||||
elif panel[2] < opt.height * 2:
|
||||
diff = panel[2] - opt.height
|
||||
panelsProcessed.append((panel[0], panel[1] - diff, opt.height))
|
||||
panelsProcessed.append((panel[1] - opt.height, panel[1], opt.height))
|
||||
else:
|
||||
parts = round(panel[2] / opt.height)
|
||||
diff = panel[2] // parts
|
||||
for x in range(0, parts):
|
||||
panelsProcessed.append((panel[0] + (x * diff), panel[1] - ((parts - x - 1) * diff), diff))
|
||||
|
||||
if opt.debug:
|
||||
# noinspection PyUnboundLocalVariable
|
||||
debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG')
|
||||
for panel in panelsProcessed:
|
||||
draw.rectangle([(0, panel[0]), (widthImg, panel[1])], (0, 255, 0, 128), (0, 0, 255, 255))
|
||||
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
|
||||
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
|
||||
|
||||
# Create virtual pages
|
||||
pages = []
|
||||
currentPage = []
|
||||
pageLeft = opt.height
|
||||
panelNumber = 0
|
||||
for panel in panels:
|
||||
for panel in panelsProcessed:
|
||||
if pageLeft - panel[2] > 0:
|
||||
pageLeft -= panel[2]
|
||||
currentPage.append(panelNumber)
|
||||
@@ -194,21 +170,21 @@ def splitImage(work):
|
||||
pageHeight = 0
|
||||
targetHeight = 0
|
||||
for panel in page:
|
||||
pageHeight += panels[panel][2]
|
||||
if pageHeight > delta:
|
||||
pageHeight += panelsProcessed[panel][2]
|
||||
if pageHeight > 15:
|
||||
newPage = Image.new('RGB', (widthImg, pageHeight))
|
||||
for panel in page:
|
||||
panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]])
|
||||
panelImg = imgOrg.crop([0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]])
|
||||
newPage.paste(panelImg, (0, targetHeight))
|
||||
targetHeight += panels[panel][2]
|
||||
newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG')
|
||||
targetHeight += panelsProcessed[panel][2]
|
||||
newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
|
||||
pageNumber += 1
|
||||
os.remove(filePath)
|
||||
except Exception:
|
||||
return str(sys.exc_info()[1])
|
||||
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
|
||||
parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False)
|
||||
mainOptions = OptionGroup(parser, "MANDATORY")
|
||||
@@ -220,14 +196,14 @@ def main(argv=None, qtGUI=None):
|
||||
mainOptions.add_option("-m", "--merge", action="store_true", dest="merge", default=False,
|
||||
help="Combine every directory into a single image before splitting")
|
||||
otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False,
|
||||
help="Create debug file for every splitted image")
|
||||
help="Create debug file for every split image")
|
||||
otherOptions.add_option("-h", "--help", action="help",
|
||||
help="Show this help message and exit")
|
||||
parser.add_option_group(mainOptions)
|
||||
parser.add_option_group(otherOptions)
|
||||
options, args = parser.parse_args(argv)
|
||||
if qtGUI:
|
||||
GUI = qtGUI
|
||||
if qtgui:
|
||||
GUI = qtgui
|
||||
else:
|
||||
GUI = None
|
||||
if len(args) != 1:
|
||||
@@ -242,15 +218,15 @@ def main(argv=None, qtGUI=None):
|
||||
work = []
|
||||
pagenumber = 1
|
||||
splitWorkerOutput = []
|
||||
splitWorkerPool = Pool()
|
||||
splitWorkerPool = Pool(maxtasksperchild=10)
|
||||
if options.merge:
|
||||
print("Merging images...")
|
||||
directoryNumer = 1
|
||||
mergeWork = []
|
||||
mergeWorkerOutput = []
|
||||
mergeWorkerPool = Pool()
|
||||
mergeWorkerPool = Pool(maxtasksperchild=10)
|
||||
mergeWork.append([options.targetDir])
|
||||
for root, dirs, files in walk(options.targetDir, False):
|
||||
for root, dirs, files in os.walk(options.targetDir, False):
|
||||
dirs, files = walkSort(dirs, files)
|
||||
for directory in dirs:
|
||||
directoryNumer += 1
|
||||
@@ -267,9 +243,10 @@ def main(argv=None, qtGUI=None):
|
||||
raise UserWarning("Conversion interrupted.")
|
||||
if len(mergeWorkerOutput) > 0:
|
||||
rmtree(options.targetDir, True)
|
||||
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0])
|
||||
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
||||
mergeWorkerOutput[0][1])
|
||||
print("Splitting images...")
|
||||
for root, dirs, files in walk(options.targetDir, False):
|
||||
for root, _, files in os.walk(options.targetDir, False):
|
||||
for name in files:
|
||||
if getImageFileName(name) is not None:
|
||||
pagenumber += 1
|
||||
@@ -290,7 +267,8 @@ def main(argv=None, qtGUI=None):
|
||||
raise UserWarning("Conversion interrupted.")
|
||||
if len(splitWorkerOutput) > 0:
|
||||
rmtree(options.targetDir, True)
|
||||
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0])
|
||||
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
||||
splitWorkerOutput[0][1])
|
||||
if options.inPlace:
|
||||
rmtree(options.sourceDir)
|
||||
move(options.targetDir, options.sourceDir)
|
||||
81
kindlecomicconverter/comicarchive.py
Normal file
81
kindlecomicconverter/comicarchive.py
Normal 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
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks
|
||||
# Changes for KCC Copyright (C) 2014-2016 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -24,6 +24,7 @@ import shutil
|
||||
class DualMetaFixException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# palm database offset constants
|
||||
number_of_pdb_records = 76
|
||||
first_pdb_record = 78
|
||||
@@ -142,14 +143,12 @@ class DualMobiMetaFix:
|
||||
self.datain_rec0 = readsection(self.datain, 0)
|
||||
|
||||
# in the first mobi header
|
||||
# add 501 to "EBOK", add 113 as asin, add 504 as asin
|
||||
# add 501 to "EBOK", add 113 as asin
|
||||
rec0 = self.datain_rec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = del_exth(rec0, 504)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
rec0 = add_exth(rec0, 504, asin)
|
||||
replacesection(self.datain, 0, rec0)
|
||||
|
||||
ver = getint(self.datain_rec0, mobi_version)
|
||||
@@ -171,14 +170,12 @@ class DualMobiMetaFix:
|
||||
self.datain_kfrec0 = readsection(self.datain, datain_kf8)
|
||||
|
||||
# in the second header
|
||||
# add 501 to "EBOK", add 113 as asin, add 504 as asin
|
||||
# add 501 to "EBOK", add 113 as asin
|
||||
rec0 = self.datain_kfrec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = del_exth(rec0, 504)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
rec0 = add_exth(rec0, 504, asin)
|
||||
replacesection(self.datain, datain_kf8, rec0)
|
||||
|
||||
self.datain.flush()
|
||||
369
kindlecomicconverter/image.py
Executable file
369
kindlecomicconverter/image.py
Executable file
@@ -0,0 +1,369 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2010 Alex Yatskov
|
||||
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
|
||||
# Copyright (c) 2016 Alberto Planas <aplanas@gmail.com>
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
||||
from .shared import md5Checksum
|
||||
|
||||
|
||||
class ProfileData:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
Palette4 = [
|
||||
0x00, 0x00, 0x00,
|
||||
0x55, 0x55, 0x55,
|
||||
0xaa, 0xaa, 0xaa,
|
||||
0xff, 0xff, 0xff
|
||||
]
|
||||
|
||||
Palette15 = [
|
||||
0x00, 0x00, 0x00,
|
||||
0x11, 0x11, 0x11,
|
||||
0x22, 0x22, 0x22,
|
||||
0x33, 0x33, 0x33,
|
||||
0x44, 0x44, 0x44,
|
||||
0x55, 0x55, 0x55,
|
||||
0x66, 0x66, 0x66,
|
||||
0x77, 0x77, 0x77,
|
||||
0x88, 0x88, 0x88,
|
||||
0x99, 0x99, 0x99,
|
||||
0xaa, 0xaa, 0xaa,
|
||||
0xbb, 0xbb, 0xbb,
|
||||
0xcc, 0xcc, 0xcc,
|
||||
0xdd, 0xdd, 0xdd,
|
||||
0xff, 0xff, 0xff,
|
||||
]
|
||||
|
||||
Palette16 = [
|
||||
0x00, 0x00, 0x00,
|
||||
0x11, 0x11, 0x11,
|
||||
0x22, 0x22, 0x22,
|
||||
0x33, 0x33, 0x33,
|
||||
0x44, 0x44, 0x44,
|
||||
0x55, 0x55, 0x55,
|
||||
0x66, 0x66, 0x66,
|
||||
0x77, 0x77, 0x77,
|
||||
0x88, 0x88, 0x88,
|
||||
0x99, 0x99, 0x99,
|
||||
0xaa, 0xaa, 0xaa,
|
||||
0xbb, 0xbb, 0xbb,
|
||||
0xcc, 0xcc, 0xcc,
|
||||
0xdd, 0xdd, 0xdd,
|
||||
0xee, 0xee, 0xee,
|
||||
0xff, 0xff, 0xff,
|
||||
]
|
||||
|
||||
PalleteNull = [
|
||||
]
|
||||
|
||||
Profiles = {
|
||||
'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
|
||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
||||
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
||||
'K578': ("Kindle", (600, 800), Palette16, 1.8),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
|
||||
'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
|
||||
'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.8),
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
|
||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
|
||||
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
|
||||
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8),
|
||||
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8),
|
||||
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8),
|
||||
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
|
||||
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
||||
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
||||
}
|
||||
|
||||
|
||||
class ComicPageParser:
|
||||
def __init__(self, source, options):
|
||||
Image.MAX_IMAGE_PIXELS = int(2048 * 2048 * 2048 // 4 // 3)
|
||||
self.opt = options
|
||||
self.source = source
|
||||
self.size = self.opt.profileData[1]
|
||||
self.payload = []
|
||||
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
|
||||
self.color = self.colorCheck()
|
||||
self.fill = self.fillCheck()
|
||||
self.splitCheck()
|
||||
|
||||
def getImageHistogram(self, image):
|
||||
histogram = image.histogram()
|
||||
if histogram[0] == 0:
|
||||
return -1
|
||||
elif histogram[255] == 0:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def splitCheck(self):
|
||||
width, height = self.image.size
|
||||
dstwidth, dstheight = self.size
|
||||
if (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
|
||||
and not self.opt.webtoon and self.opt.splitter == 1:
|
||||
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True), self.color, self.fill])
|
||||
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
||||
if self.opt.splitter != 1:
|
||||
if width > height:
|
||||
leftbox = (0, 0, int(width / 2), height)
|
||||
rightbox = (int(width / 2), 0, width, height)
|
||||
else:
|
||||
leftbox = (0, 0, width, int(height / 2))
|
||||
rightbox = (0, int(height / 2), width, height)
|
||||
if self.opt.righttoleft:
|
||||
pageone = self.image.crop(rightbox)
|
||||
pagetwo = self.image.crop(leftbox)
|
||||
else:
|
||||
pageone = self.image.crop(leftbox)
|
||||
pagetwo = self.image.crop(rightbox)
|
||||
self.payload.append(['S1', self.source, pageone, self.color, self.fill])
|
||||
self.payload.append(['S2', self.source, pagetwo, self.color, self.fill])
|
||||
if self.opt.splitter > 0:
|
||||
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True),
|
||||
self.color, self.fill])
|
||||
else:
|
||||
self.payload.append(['N', self.source, self.image, self.color, self.fill])
|
||||
|
||||
def colorCheck(self):
|
||||
if self.opt.webtoon:
|
||||
return True
|
||||
else:
|
||||
img = self.image.copy()
|
||||
bands = img.getbands()
|
||||
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
|
||||
thumb = img.resize((40, 40))
|
||||
SSE, bias = 0, [0, 0, 0]
|
||||
bias = ImageStat.Stat(thumb).mean[:3]
|
||||
bias = [b - sum(bias) / 3 for b in bias]
|
||||
for pixel in thumb.getdata():
|
||||
mu = sum(pixel) / 3
|
||||
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
|
||||
MSE = float(SSE) / (40 * 40)
|
||||
if MSE > 22:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def fillCheck(self):
|
||||
if self.opt.bordersColor:
|
||||
return self.opt.bordersColor
|
||||
else:
|
||||
bw = self.image.convert('L').point(lambda x: 0 if x < 128 else 255, '1')
|
||||
imageBoxA = bw.getbbox()
|
||||
imageBoxB = ImageChops.invert(bw).getbbox()
|
||||
if imageBoxA is None or imageBoxB is None:
|
||||
surfaceB, surfaceW = 0, 0
|
||||
diff = 0
|
||||
else:
|
||||
surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1])
|
||||
surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1])
|
||||
diff = ((max(surfaceB, surfaceW) - min(surfaceB, surfaceW)) / min(surfaceB, surfaceW)) * 100
|
||||
if diff > 0.5:
|
||||
if surfaceW < surfaceB:
|
||||
return 'white'
|
||||
elif surfaceW > surfaceB:
|
||||
return 'black'
|
||||
else:
|
||||
fill = 0
|
||||
startY = 0
|
||||
while startY < bw.size[1]:
|
||||
if startY + 5 > bw.size[1]:
|
||||
startY = bw.size[1] - 5
|
||||
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY + 5)))
|
||||
startY += 5
|
||||
startX = 0
|
||||
while startX < bw.size[0]:
|
||||
if startX + 5 > bw.size[0]:
|
||||
startX = bw.size[0] - 5
|
||||
fill += self.getImageHistogram(bw.crop((startX, 0, startX + 5, bw.size[1])))
|
||||
startX += 5
|
||||
if fill > 0:
|
||||
return 'black'
|
||||
else:
|
||||
return 'white'
|
||||
|
||||
|
||||
class ComicPage:
|
||||
def __init__(self, options, mode, path, image, color, fill):
|
||||
self.opt = options
|
||||
_, self.size, self.palette, self.gamma = self.opt.profileData
|
||||
if self.opt.hq:
|
||||
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
||||
self.image = image
|
||||
self.color = color
|
||||
self.fill = fill
|
||||
self.rotated = False
|
||||
self.orgPath = os.path.join(path[0], path[1])
|
||||
if 'N' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
|
||||
elif 'R' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
|
||||
self.rotated = True
|
||||
elif 'S1' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-B'
|
||||
elif 'S2' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-C'
|
||||
|
||||
def saveToDir(self):
|
||||
try:
|
||||
flags = []
|
||||
if not self.opt.forcecolor and not self.opt.forcepng:
|
||||
self.image = self.image.convert('L')
|
||||
if self.rotated:
|
||||
flags.append('Rotated')
|
||||
if self.fill != 'white':
|
||||
flags.append('BlackBackground')
|
||||
if self.opt.forcepng:
|
||||
self.targetPath += '.png'
|
||||
self.image.save(self.targetPath, 'PNG', optimize=1)
|
||||
else:
|
||||
self.targetPath += '.jpg'
|
||||
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
|
||||
return [md5Checksum(self.targetPath), flags, self.orgPath]
|
||||
except IOError as err:
|
||||
raise RuntimeError('Cannot save image. ' + str(err))
|
||||
|
||||
def autocontrastImage(self):
|
||||
gamma = self.opt.gamma
|
||||
if gamma < 0.1:
|
||||
gamma = self.gamma
|
||||
if self.gamma != 1.0 and self.color:
|
||||
gamma = 1.0
|
||||
if gamma == 1.0:
|
||||
self.image = ImageOps.autocontrast(self.image)
|
||||
else:
|
||||
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: 255 * (a / 255.) ** gamma))
|
||||
|
||||
def quantizeImage(self):
|
||||
colors = len(self.palette) // 3
|
||||
if colors < 256:
|
||||
self.palette += self.palette[:3] * (256 - colors)
|
||||
palImg = Image.new('P', (1, 1))
|
||||
palImg.putpalette(self.palette)
|
||||
self.image = self.image.convert('L')
|
||||
self.image = self.image.convert('RGB')
|
||||
# Quantize is deprecated but new function call it internally anyway...
|
||||
self.image = self.image.quantize(palette=palImg)
|
||||
|
||||
def resizeImage(self):
|
||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
||||
method = Image.BICUBIC
|
||||
else:
|
||||
method = Image.LANCZOS
|
||||
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)
|
||||
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' or self.opt.kfx:
|
||||
borderw = int((self.size[0] - self.image.size[0]) / 2)
|
||||
borderh = int((self.size[1] - self.image.size[1]) / 2)
|
||||
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]:
|
||||
self.image = ImageOps.fit(self.image, self.size, method=Image.BICUBIC, centering=(0.5, 0.5))
|
||||
else:
|
||||
if self.opt.format == 'CBZ' or self.opt.kfx:
|
||||
ratioDev = float(self.size[0]) / float(self.size[1])
|
||||
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
|
||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
||||
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
|
||||
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
||||
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
||||
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
|
||||
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
|
||||
else:
|
||||
hpercent = self.size[1] / float(self.image.size[1])
|
||||
wsize = int((float(self.image.size[0]) * float(hpercent)))
|
||||
self.image = self.image.resize((wsize, self.size[1]), method)
|
||||
if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]:
|
||||
self.image.thumbnail(self.size, Image.LANCZOS)
|
||||
|
||||
def getBoundingBox(self, tmptmg):
|
||||
min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
|
||||
max_margin = [int(0.1 * i + 0.5) for i in tmptmg.size]
|
||||
bbox = tmptmg.getbbox()
|
||||
bbox = (
|
||||
max(0, min(max_margin[0], bbox[0] - min_margin[0])),
|
||||
max(0, min(max_margin[1], bbox[1] - min_margin[1])),
|
||||
min(tmptmg.size[0],
|
||||
max(tmptmg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
|
||||
min(tmptmg.size[1],
|
||||
max(tmptmg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
|
||||
)
|
||||
return bbox
|
||||
|
||||
def cropPageNumber(self, power):
|
||||
if self.fill != 'white':
|
||||
tmptmg = self.image.convert(mode='L')
|
||||
else:
|
||||
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
||||
tmptmg = tmptmg.point(lambda x: x and 255)
|
||||
tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
|
||||
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
|
||||
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
||||
self.image = self.image.crop(tmptmg.getbbox()) if tmptmg.getbbox() else self.image
|
||||
|
||||
def cropMargin(self, power):
|
||||
if self.fill != 'white':
|
||||
tmptmg = self.image.convert(mode='L')
|
||||
else:
|
||||
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
||||
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
|
||||
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
||||
self.image = self.image.crop(self.getBoundingBox(tmptmg)) if tmptmg.getbbox() else self.image
|
||||
|
||||
|
||||
class Cover:
|
||||
def __init__(self, source, target, opt, tomeid):
|
||||
self.options = opt
|
||||
self.source = source
|
||||
self.target = target
|
||||
if tomeid == 0:
|
||||
self.tomeid = 1
|
||||
else:
|
||||
self.tomeid = tomeid
|
||||
self.image = Image.open(source)
|
||||
self.process()
|
||||
|
||||
def process(self):
|
||||
self.image = self.image.convert('RGB')
|
||||
self.image = ImageOps.autocontrast(self.image)
|
||||
if not self.options.forcecolor:
|
||||
self.image = self.image.convert('L')
|
||||
self.image.thumbnail(self.options.profileData[1], Image.LANCZOS)
|
||||
self.save()
|
||||
|
||||
def save(self):
|
||||
try:
|
||||
self.image.save(self.target, "JPEG", optimize=1, quality=85)
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to save cover.')
|
||||
|
||||
def saveToKindle(self, kindle, asin):
|
||||
self.image = self.image.resize((300, 470), Image.ANTIALIAS)
|
||||
try:
|
||||
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
|
||||
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=85)
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to upload cover.')
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -18,14 +18,9 @@
|
||||
|
||||
import os
|
||||
from xml.dom.minidom import parse, Document
|
||||
from re import compile
|
||||
from zipfile import is_zipfile, ZipFile, ZIP_DEFLATED
|
||||
from subprocess import STDOUT, PIPE
|
||||
from psutil import Popen
|
||||
from tempfile import mkdtemp
|
||||
from shutil import rmtree
|
||||
from .shared import removeFromZIP, check7ZFile as is_7zfile
|
||||
from . import rarfile
|
||||
from . import comicarchive
|
||||
|
||||
|
||||
class MetadataParser:
|
||||
@@ -39,50 +34,20 @@ class MetadataParser:
|
||||
'Inkers': [],
|
||||
'Colorists': [],
|
||||
'Summary': '',
|
||||
'MUid': '',
|
||||
'Bookmarks': []}
|
||||
self.rawdata = None
|
||||
self.compressor = None
|
||||
if self.source.endswith('.xml'):
|
||||
self.format = None
|
||||
if self.source.endswith('.xml') and os.path.exists(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()
|
||||
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):
|
||||
if len(self.rawdata.getElementsByTagName('Series')) != 0:
|
||||
@@ -99,11 +64,6 @@ class MetadataParser:
|
||||
self.data[field + 's'].append(person)
|
||||
self.data[field + 's'] = list(set(self.data[field + 's']))
|
||||
self.data[field + 's'].sort()
|
||||
if len(self.rawdata.getElementsByTagName('ScanInformation')) != 0:
|
||||
coverId = compile('(MCD\\()(\\d+)(\\))')\
|
||||
.search(self.rawdata.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue)
|
||||
if coverId:
|
||||
self.data['MUid'] = coverId.group(2)
|
||||
if len(self.rawdata.getElementsByTagName('Page')) != 0:
|
||||
for page in self.rawdata.getElementsByTagName('Page'):
|
||||
if 'Bookmark' in page.attributes and 'Image' in page.attributes:
|
||||
@@ -116,8 +76,7 @@ class MetadataParser:
|
||||
for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
|
||||
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
|
||||
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
|
||||
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']],
|
||||
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
|
||||
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]):
|
||||
if self.rawdata.getElementsByTagName(row[0]):
|
||||
node = self.rawdata.getElementsByTagName(row[0])[0]
|
||||
if row[1]:
|
||||
@@ -138,8 +97,7 @@ class MetadataParser:
|
||||
for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
|
||||
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
|
||||
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
|
||||
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']],
|
||||
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
|
||||
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]):
|
||||
if row[1]:
|
||||
main = doc.createElement(row[0])
|
||||
root.appendChild(main)
|
||||
@@ -154,20 +112,9 @@ class MetadataParser:
|
||||
tmpXML = os.path.join(workdir, 'ComicInfo.xml')
|
||||
with open(tmpXML, 'w', encoding='utf-8') as f:
|
||||
self.rawdata.writexml(f, encoding='utf-8')
|
||||
if is_zipfile(self.source):
|
||||
removeFromZIP(self.source, 'ComicInfo.xml')
|
||||
with ZipFile(self.source, mode='a', compression=ZIP_DEFLATED) as zip_file:
|
||||
zip_file.write(tmpXML, arcname=tmpXML.split(os.sep)[-1])
|
||||
elif rarfile.is_rarfile(self.source):
|
||||
raise NotImplementedError
|
||||
elif is_7zfile(self.source):
|
||||
output = Popen('7za a "' + self.source + '" "' + tmpXML + '"',
|
||||
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||
extracted = False
|
||||
for line in output.stdout:
|
||||
if b"Everything is Ok" in line:
|
||||
extracted = True
|
||||
if not extracted:
|
||||
rmtree(workdir)
|
||||
raise OSError('Failed to modify 7ZIP file.')
|
||||
try:
|
||||
cbx = comicarchive.ComicArchive(self.source)
|
||||
cbx.addFile(tmpXML)
|
||||
except OSError as e:
|
||||
raise UserWarning(e.strerror)
|
||||
rmtree(workdir)
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Based upon the code snippet by Ned Batchelder
|
||||
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
|
||||
@@ -25,17 +27,16 @@ from string import ascii_uppercase, digits
|
||||
|
||||
|
||||
class PdfJpgExtract:
|
||||
def __init__(self, origFileName):
|
||||
self.origFileName = origFileName
|
||||
self.filename = os.path.splitext(origFileName)
|
||||
# noinspection PyUnusedLocal
|
||||
self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for x in range(3))
|
||||
def __init__(self, fname):
|
||||
self.fname = fname
|
||||
self.filename = os.path.splitext(fname)
|
||||
self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for _ in range(3))
|
||||
|
||||
def getPath(self):
|
||||
return self.path
|
||||
|
||||
def extract(self):
|
||||
pdf = open(self.origFileName, "rb").read()
|
||||
pdf = open(self.fname, "rb").read()
|
||||
startmark = b"\xff\xd8"
|
||||
startfix = 0
|
||||
endmark = b"\xff\xd9"
|
||||
@@ -61,7 +62,6 @@ class PdfJpgExtract:
|
||||
iend += endfix
|
||||
jpg = pdf[istart:iend]
|
||||
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
|
||||
# noinspection PyTypeChecker
|
||||
jpgfile.write(jpg)
|
||||
jpgfile.close()
|
||||
njpg += 1
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2016 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
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -17,20 +19,11 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from sys import version_info
|
||||
from hashlib import md5
|
||||
from html.parser import HTMLParser
|
||||
from distutils.version import StrictVersion
|
||||
from time import sleep
|
||||
from shutil import rmtree, copy
|
||||
from tempfile import mkdtemp
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
from re import split
|
||||
from traceback import format_tb
|
||||
try:
|
||||
from scandir import walk
|
||||
except ImportError:
|
||||
walk = os.walk
|
||||
|
||||
|
||||
class HTMLStripper(HTMLParser):
|
||||
@@ -54,7 +47,7 @@ class HTMLStripper(HTMLParser):
|
||||
def getImageFileName(imgfile):
|
||||
name, ext = os.path.splitext(imgfile)
|
||||
ext = ext.lower()
|
||||
if name.startswith('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg' and ext != '.gif'):
|
||||
if (name.startswith('.') and len(name) == 1) or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp']:
|
||||
return None
|
||||
return [name, ext]
|
||||
|
||||
@@ -71,7 +64,7 @@ def walkLevel(some_dir, level=1):
|
||||
some_dir = some_dir.rstrip(os.path.sep)
|
||||
assert os.path.isdir(some_dir)
|
||||
num_sep = some_dir.count(os.path.sep)
|
||||
for root, dirs, files in walk(some_dir):
|
||||
for root, dirs, files in os.walk(some_dir):
|
||||
dirs, files = walkSort(dirs, files)
|
||||
yield root, dirs, files
|
||||
num_sep_this = root.count(os.path.sep)
|
||||
@@ -79,8 +72,8 @@ def walkLevel(some_dir, level=1):
|
||||
del dirs[:]
|
||||
|
||||
|
||||
def md5Checksum(filePath):
|
||||
with open(filePath, 'rb') as fh:
|
||||
def md5Checksum(fpath):
|
||||
with open(fpath, 'rb') as fh:
|
||||
m = md5()
|
||||
while True:
|
||||
data = fh.read(8192)
|
||||
@@ -90,66 +83,19 @@ def md5Checksum(filePath):
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
def check7ZFile(filePath):
|
||||
with open(filePath, 'rb') as fh:
|
||||
header = fh.read(6)
|
||||
return header == b"7z\xbc\xaf'\x1c"
|
||||
|
||||
|
||||
def saferReplace(old, new):
|
||||
for x in range(30):
|
||||
try:
|
||||
os.replace(old, new)
|
||||
except PermissionError:
|
||||
sleep(1)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
raise PermissionError("Failed to move the file.")
|
||||
|
||||
|
||||
def saferRemove(target):
|
||||
for x in range(30):
|
||||
try:
|
||||
os.remove(target)
|
||||
except PermissionError:
|
||||
sleep(1)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
raise PermissionError("Failed to remove the file.")
|
||||
|
||||
|
||||
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))
|
||||
for x in range(30):
|
||||
try:
|
||||
copy(tempname, zipfname)
|
||||
except PermissionError:
|
||||
sleep(1)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
raise PermissionError
|
||||
finally:
|
||||
rmtree(tempdir, True)
|
||||
|
||||
|
||||
def sanitizeTrace(traceback):
|
||||
return ''.join(format_tb(traceback))\
|
||||
.replace('C:\\Users\\pawel\\Documents\\Projekty\\KCC\\', '') \
|
||||
.replace('C:\\Users\\Paweł\\Documents\\Projekty\\KCC\\', '') \
|
||||
.replace('C:\\Python35\\', '')\
|
||||
.replace('c:\\python35\\', '')
|
||||
.replace('C:/projects/kcc/', '')\
|
||||
.replace('c:/projects/kcc/', '')\
|
||||
.replace('C:/python37-x64/', '')\
|
||||
.replace('c:/python37-x64/', '')\
|
||||
.replace('C:\\projects\\kcc\\', '')\
|
||||
.replace('c:\\projects\\kcc\\', '')\
|
||||
.replace('C:\\python37-x64\\', '')\
|
||||
.replace('c:\\python37-x64\\', '')
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def dependencyCheck(level):
|
||||
missing = []
|
||||
if level > 2:
|
||||
@@ -162,33 +108,26 @@ def dependencyCheck(level):
|
||||
try:
|
||||
import raven
|
||||
except ImportError:
|
||||
missing.append('raven 5.13.0+')
|
||||
missing.append('raven 6.0.0+')
|
||||
if level > 1:
|
||||
try:
|
||||
from psutil import __version__ as psutilVersion
|
||||
if StrictVersion('4.1.0') > StrictVersion(psutilVersion):
|
||||
missing.append('psutil 4.1.0+')
|
||||
if StrictVersion('5.0.0') > StrictVersion(psutilVersion):
|
||||
missing.append('psutil 5.0.0+')
|
||||
except ImportError:
|
||||
missing.append('psutil 4.1.0+')
|
||||
missing.append('psutil 5.0.0+')
|
||||
try:
|
||||
from slugify import __version__ as slugifyVersion
|
||||
if StrictVersion('1.2.0') > StrictVersion(slugifyVersion):
|
||||
missing.append('python-slugify 1.2.0+')
|
||||
if StrictVersion('1.2.1') > StrictVersion(slugifyVersion):
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
except ImportError:
|
||||
missing.append('python-slugify 1.2.0+')
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
try:
|
||||
from PIL import PILLOW_VERSION as pillowVersion
|
||||
if StrictVersion('3.2.0') > StrictVersion(pillowVersion):
|
||||
missing.append('Pillow 3.2.0+')
|
||||
from PIL import __version__ as pillowVersion
|
||||
if StrictVersion('5.2.0') > StrictVersion(pillowVersion):
|
||||
missing.append('Pillow 5.2.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 3.2.0+')
|
||||
if version_info[1] < 5:
|
||||
try:
|
||||
from scandir import __version__ as scandirVersion
|
||||
if StrictVersion('1.2') > StrictVersion(scandirVersion):
|
||||
missing.append('scandir 1.2+')
|
||||
except ImportError:
|
||||
missing.append('scandir 1.2+')
|
||||
missing.append('Pillow 5.2.0+')
|
||||
if len(missing) > 0:
|
||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||
exit(1)
|
||||
56
kindlecomicconverter/startup.py
Normal file
56
kindlecomicconverter/startup.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- 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
|
||||
import sys
|
||||
from . import __version__
|
||||
from .shared import dependencyCheck
|
||||
|
||||
|
||||
def start():
|
||||
dependencyCheck(3)
|
||||
from . import KCC_gui
|
||||
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1"
|
||||
KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
|
||||
if KCCAplication.isRunning():
|
||||
for i in range(1, len(sys.argv)):
|
||||
KCCAplication.sendMessage(sys.argv[i])
|
||||
else:
|
||||
KCCAplication.sendMessage('ARISE')
|
||||
else:
|
||||
KCCWindow = KCC_gui.QMainWindowKCC()
|
||||
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
|
||||
for i in range(1, len(sys.argv)):
|
||||
KCCUI.handleMessage(sys.argv[i])
|
||||
sys.exit(KCCAplication.exec_())
|
||||
|
||||
|
||||
def startC2E():
|
||||
dependencyCheck(2)
|
||||
from .comic2ebook import main
|
||||
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
|
||||
|
||||
def startC2P():
|
||||
dependencyCheck(1)
|
||||
from .comic2panel import main
|
||||
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -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
|
||||
@@ -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;
|
||||
BIN
other/osx/7z
Executable file
BIN
other/osx/7z
Executable file
Binary file not shown.
BIN
other/osx/7z.so
Normal file
BIN
other/osx/7z.so
Normal file
Binary file not shown.
BIN
other/osx/7za
BIN
other/osx/7za
Binary file not shown.
@@ -6,10 +6,31 @@
|
||||
<string>English</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Kindle Comic Converter</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cbz</string>
|
||||
<string>cbr</string>
|
||||
<string>cb7</string>
|
||||
<string>zip</string>
|
||||
<string>rar</string>
|
||||
<string>7z</string>
|
||||
<string>pdf</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>comic2ebook.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Comics</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>MacOS/Kindle Comic Converter</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>KindleComicConverter 5.1.2, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski</string>
|
||||
<string>KindleComicConverter 5.5.2, written 2012-2019 by Ciro Mattia Gonano and Pawel Jastrzebski</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>comic2ebook.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -21,20 +42,20 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.1.2</string>
|
||||
<string>5.5.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>5.1.2</string>
|
||||
<string>5.5.2</string>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>./../Resources:/usr/local/bin:/usr/bin:/bin</string>
|
||||
<string>./../Resources:/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS:/usr/local/bin:/usr/bin:/bin</string>
|
||||
</dict>
|
||||
<key>LSHasLocalizedDisplayName</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.9.0</string>
|
||||
<string>10.14.0</string>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<false/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
@@ -43,5 +64,9 @@
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSRequiresAquaSystemAppearance</key>
|
||||
<string>false</string>
|
||||
<key>NSInitialToolTipDelay</key>
|
||||
<integer>1000</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
BIN
other/osx/Rar.so
Normal file
BIN
other/osx/Rar.so
Normal file
Binary file not shown.
BIN
other/osx/unrar
BIN
other/osx/unrar
Binary file not shown.
BIN
other/windows/7z.dll
Normal file
BIN
other/windows/7z.dll
Normal file
Binary file not shown.
BIN
other/windows/7z.exe
Normal file
BIN
other/windows/7z.exe
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,56 +1,22 @@
|
||||
****** ***** ****** UnRAR - free utility for RAR archives
|
||||
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
****** ******* ****** License for use and distribution of
|
||||
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
** ** ** ** ** ** FREEWARE version
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The UnRAR utility is freeware. This means:
|
||||
|
||||
1. All copyrights to RAR and the utility UnRAR are exclusively
|
||||
owned by the author - Alexander Roshal.
|
||||
|
||||
2. The UnRAR utility may be freely distributed. It is allowed
|
||||
to distribute UnRAR inside of other software packages.
|
||||
|
||||
3. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS".
|
||||
NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT
|
||||
YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS,
|
||||
DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
|
||||
OR MISUSING THIS SOFTWARE.
|
||||
|
||||
4. Neither RAR binary code, WinRAR binary code, UnRAR source or UnRAR
|
||||
binary code may be used or reverse engineered to re-create the RAR
|
||||
compression algorithm, which is proprietary, without written
|
||||
permission of the author.
|
||||
|
||||
5. If you don't agree with terms of the license you must remove
|
||||
UnRAR files from your storage devices and cease to use the
|
||||
utility.
|
||||
|
||||
Thank you for your interest in RAR and UnRAR.
|
||||
|
||||
|
||||
Alexander L. Roshal
|
||||
|
||||
7-Zip
|
||||
~~~~~
|
||||
License for use and distribution
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
7-Zip Copyright (C) 1999-2012 Igor Pavlov.
|
||||
7-Zip Copyright (C) 1999-2018 Igor Pavlov.
|
||||
|
||||
Licenses for files are:
|
||||
The licenses for files are:
|
||||
|
||||
1) 7z.dll: GNU LGPL + unRAR restriction
|
||||
2) All other files: GNU LGPL
|
||||
1) 7z.dll:
|
||||
- The "GNU LGPL" as main license for most of the code
|
||||
- The "GNU LGPL" with "unRAR license restriction" for some code
|
||||
- The "BSD 3-clause License" for some code
|
||||
2) All other files: the "GNU LGPL".
|
||||
|
||||
The GNU LGPL + unRAR restriction means that you must follow both
|
||||
GNU LGPL rules and unRAR restriction rules.
|
||||
Redistributions in binary form must reproduce related license information from this file.
|
||||
|
||||
|
||||
Note:
|
||||
You can use 7-Zip on any computer, including a computer in a commercial
|
||||
Note:
|
||||
You can use 7-Zip on any computer, including a computer in a commercial
|
||||
organization. You don't need to register or pay for 7-Zip.
|
||||
|
||||
|
||||
@@ -67,21 +33,54 @@
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You can receive a copy of the GNU Lesser General Public License from
|
||||
You can receive a copy of the GNU Lesser General Public License from
|
||||
http://www.gnu.org/
|
||||
|
||||
|
||||
unRAR restriction
|
||||
-----------------
|
||||
|
||||
The decompression engine for RAR archives was developed using source
|
||||
|
||||
BSD 3-clause License
|
||||
--------------------
|
||||
|
||||
The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression.
|
||||
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
|
||||
that also uses the "BSD 3-clause License":
|
||||
|
||||
----
|
||||
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
unRAR license restriction
|
||||
-------------------------
|
||||
|
||||
The decompression engine for RAR archives was developed using source
|
||||
code of unRAR program.
|
||||
All copyrights to original unRAR code are owned by Alexander Roshal.
|
||||
|
||||
The license for original unRAR code has the following restriction:
|
||||
|
||||
The unRAR sources cannot be used to re-create the RAR compression algorithm,
|
||||
which is proprietary. Distribution of modified unRAR sources in separate form
|
||||
The unRAR sources cannot be used to re-create the RAR compression algorithm,
|
||||
which is proprietary. Distribution of modified unRAR sources in separate form
|
||||
or as a part of other software is permitted, provided that it is clearly
|
||||
stated in the documentation and source comments that the code may
|
||||
not be used to develop a RAR (WinRAR) compatible archiver.
|
||||
|
||||
@@ -3,19 +3,20 @@
|
||||
{\flomajor\f31500\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\fhimajor\f31502\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\flominor\f31504\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f41\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||
{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f41\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||
{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f411\fbidi \fswiss\fcharset0\fprq2 Calibri;}
|
||||
{\f410\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f412\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f413\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f416\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
|
||||
{\f417\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
|
||||
{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
||||
{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;}
|
||||
{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}
|
||||
{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f44\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||
{\f43\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f45\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f46\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f47\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\f48\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f49\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f50\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f44\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||
{\f43\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f45\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f46\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f47\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\f48\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f49\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f50\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f414\fbidi \fswiss\fcharset0\fprq2 Calibri;}
|
||||
{\f413\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f415\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f416\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f417\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}
|
||||
{\f418\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\f419\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f420\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
|
||||
{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}
|
||||
{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}
|
||||
{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31540\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||
{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
@@ -26,20 +27,21 @@
|
||||
{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31570\fbidi \fswiss\fcharset0\fprq2 Calibri;}
|
||||
{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}
|
||||
{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31580\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||
{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;
|
||||
\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;
|
||||
\chyperlink\ctint255\cshade255\red5\green99\blue193;\cfollowedhyperlink\ctint255\cshade255\red149\green79\blue114;}{\*\defchp \f31506\fs22\lang1045\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1
|
||||
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0
|
||||
\f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
|
||||
{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
|
||||
{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31580\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}
|
||||
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;
|
||||
\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\chyperlink\ctint255\cshade255\red5\green99\blue193;\cfollowedhyperlink\ctint255\cshade255\red149\green79\blue114;}{\*\defchp
|
||||
\f31506\fs22\lang1045\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1
|
||||
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive
|
||||
\ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
|
||||
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
|
||||
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive
|
||||
\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf17 \sbasedon10 \sunhideused \styrsid3562894 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf18 \sbasedon10 \ssemihidden \sunhideused \styrsid7678248 FollowedHyperlink;}}{\*\rsidtbl \rsid1081196
|
||||
\rsid3146412\rsid3562894\rsid5731975\rsid7678248\rsid9265883\rsid11107340\rsid12600926\rsid13187577}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info
|
||||
{\author Pawe\'b3 Jastrz\'eabski}{\operator Pawe\'b3 Jastrz\'eabski}{\creatim\yr2013\mo10\dy29\hr15\min17}{\revtim\yr2013\mo10\dy29\hr15\min28}{\version8}{\edmins8}{\nofpages1}{\nofwords33}{\nofchars200}{\nofcharsws232}{\vern57435}}{\*\xmlnstbl {\xmlns1 h
|
||||
ttp://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect
|
||||
\rsid3146412\rsid3562894\rsid5731975\rsid7678248\rsid9265883\rsid11107340\rsid11629590\rsid12600926\rsid13187577}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info
|
||||
{\author Pawe\'b3 Jastrz\'eabski}{\operator Pawe\'b3 Jastrz\'eabski}{\creatim\yr2013\mo10\dy29\hr15\min17}{\revtim\yr2017\mo8\dy20\hr17\min40}{\version9}{\edmins8}{\nofpages1}{\nofwords33}{\nofchars201}{\nofcharsws233}{\vern39}}{\*\xmlnstbl {\xmlns1 http:
|
||||
//schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect
|
||||
\deftab708\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0
|
||||
\showxmlerrors1\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1
|
||||
\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct
|
||||
@@ -52,12 +54,13 @@ ttp://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\ma
|
||||
\b\fs52\cf6\lang2057\langfe1033\langnp2057\insrsid13187577\charrsid3562894
|
||||
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid1081196 Creation of}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 MOBI}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 files }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
||||
\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 require additional software.}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
|
||||
\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 require}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid11629590 s}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
||||
\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 additional software.}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
|
||||
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 Please download: }{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 HYPERLINK "http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211" }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
|
||||
{\*\datafield
|
||||
00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b9600000068007400740070003a002f002f007700770077002e0061006d0061007a006f006e002e0063006f006d002f00670070002f0066006500610074007500720065002e00680074006d006c003f00690065003d00
|
||||
5500540046003800260064006f006300490064003d0031003000300030003700360035003200310031000000795881f43b1d7f48af2c825dc485276300000000a5ab000000}}}{\fldrslt {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
||||
5500540046003800260064006f006300490064003d0031003000300030003700360035003200310031000000795881f43b1d7f48af2c825dc485276300000000a5ab00000000}}}{\fldrslt {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
||||
\cs15\b\fs28\ul\cf17\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 KindleGen}}}\sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
|
||||
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 And place }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \i\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 kindlegen.exe}{\rtlch\fcs1
|
||||
@@ -119,7 +122,7 @@ d40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719a
|
||||
617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
|
||||
6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
|
||||
656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
|
||||
{\*\latentstyles\lsdstimax371\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
|
||||
{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;
|
||||
@@ -196,7 +199,8 @@ d40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719a
|
||||
\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5;
|
||||
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
|
||||
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
|
||||
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;}}{\*\datastore 010500000200000018000000
|
||||
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000
|
||||
4d73786d6c322e534158584d4c5265616465722e362e30000000000000000000000e0000
|
||||
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000000200000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
@@ -206,24 +210,24 @@ fffffffffffffffffdffffff04000000feffffff05000000fefffffffeffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff010000000c6ad98892f1d411a65f0040963251e50000000000000000000000006069
|
||||
e214b3d4ce010300000080020000000000004d0073006f004400610074006100530074006f0072006500000000000000000000000000000000000000000000000000000000000000000000000000000000001a000101ffffffffffffffff0200000000000000000000000000000000000000000000006069e214b3d4ce01
|
||||
6069e214b3d4ce010000000000000000000000003500d900ca00dd00ce004400cc00c8005a0045004700c400cd0057004900c500d400c900cb00ce00570051003d003d000000000000000000000000000000000032000101ffffffffffffffff0300000000000000000000000000000000000000000000006069e214b3d4
|
||||
ce016069e214b3d4ce010000000000000000000000004900740065006d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000
|
||||
00000000000000000000000000000000fc00000000000000010000000200000003000000feffffff0500000006000000070000000800000009000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff010000000c6ad98892f1d411a65f0040963251e5000000000000000000000000b00f
|
||||
9da8ca19d30103000000c0020000000000004d0073006f004400610074006100530074006f0072006500000000000000000000000000000000000000000000000000000000000000000000000000000000001a000101ffffffffffffffff020000000000000000000000000000000000000000000000b00f9da8ca19d301
|
||||
b00f9da8ca19d301000000000000000000000000ca0041004300c300d300d300c70058004d00d4003000c9004d00c200590043003100320055004a00300051003d003d000000000000000000000000000000000032000101ffffffffffffffff030000000000000000000000000000000000000000000000b00f9da8ca19
|
||||
d301b00f9da8ca19d3010000000000000000000000004900740065006d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000
|
||||
00000000000000000000000000000000320100000000000001000000020000000300000004000000feffffff060000000700000008000000090000000a000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3c623a536f75726365732053656c65637465645374796c653d225c415041536978746845646974696f6e4f66666963654f6e6c696e652e78736c22205374796c654e616d653d22415041222056657273696f6e3d22362220786d6c6e733a
|
||||
623d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f6772617068792220786d6c6e733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e74
|
||||
2f323030362f6269626c696f677261706879223e3c2f623a536f75726365733e000000003c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e0d0a3c64733a6461746173746f72654974656d2064733a6974656d49443d227b42384244
|
||||
394137462d323833422d343136342d413442352d3632323544323941454535397d2220786d6c6e733a64733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f637573746f6d586d6c223e3c64733a736368656d61526566733e3c
|
||||
64733a736368656d615265662064733a7572693d22687474703a2f2f736368656d61732e6f70656e500072006f007000650072007400690065007300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000200ffffffffffffffffffffffff000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000400000055010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e3c623a536f75726365732053656c65637465645374796c653d225c41504153697874684564697469
|
||||
6f6e4f66666963654f6e6c696e652e78736c22205374796c654e616d653d22415041222056657273696f6e3d22362220786d6c6e733a623d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222078
|
||||
6d6c6e733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879223e3c2f623a536f75726365733e00000000000000000000000000003c3f786d6c2076657273696f6e3d22312e302220656e636f6469
|
||||
6e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e0d0a3c64733a6461746173746f72654974656d2064733a6974656d49443d227b43464133303041382d443733392d343633332d413933322d3236303236444335303936397d2220786d6c6e733a64733d22687474703a2f2f736368656d61732e6f70
|
||||
656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f637573500072006f007000650072007400690065007300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000200ffffffffffffffffffffffff000000000000
|
||||
0000000000000000000000000000000000000000000000000000000000000500000055010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000
|
||||
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000
|
||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
|
||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222f3e3c2f64733a736368656d61526566733e3c2f64733a6461746173746f
|
||||
72654974656d3e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746f6d586d6c223e3c64733a736368656d61526566733e3c64733a736368656d615265662064733a7572693d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f7267
|
||||
2f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222f3e3c2f64733a736368656d61526566733e3c2f64733a6461746173746f72654974656d3e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105000000000000}}
|
||||
Binary file not shown.
Binary file not shown.
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
PyQt5>=5.6.0
|
||||
Pillow>=5.2.0
|
||||
psutil>=5.0.0
|
||||
python-slugify>=1.2.1,<3.0.0
|
||||
raven>=6.0.0
|
||||
126
setup.py
126
setup.py
@@ -1,12 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
pip/pyinstaller build script for KCC.
|
||||
|
||||
Usage (Windows):
|
||||
py -3 setup.py build_binary
|
||||
Install as Python package:
|
||||
python3 setup.py install
|
||||
|
||||
Usage (Linux/OS X):
|
||||
python3 setup.py build_binary or python3 setup.py build_binary --pyz
|
||||
Create EXE/APP:
|
||||
python3 setup.py build_binary
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -14,116 +15,49 @@ import sys
|
||||
import shutil
|
||||
import setuptools
|
||||
import distutils.cmd
|
||||
from distutils.command.build import build
|
||||
from kcc import __version__
|
||||
from kindlecomicconverter import __version__
|
||||
|
||||
NAME = 'KindleComicConverter'
|
||||
MAIN = 'kcc.py'
|
||||
VERSION = __version__
|
||||
OPTIONS = {}
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
class BuildBinaryCommand(distutils.cmd.Command):
|
||||
description = 'build binary release'
|
||||
user_options = [
|
||||
('pyz', None, 'build PYZ package'),
|
||||
]
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
# noinspection PyAttributeOutsideInit
|
||||
self.pyz = False
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def run(self):
|
||||
VERSION = __version__
|
||||
if sys.platform == 'darwin':
|
||||
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s --noupx kcc.py')
|
||||
shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||
shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
||||
os.makedirs('dist/Kindle Comic Converter.app/Contents/Resources/Codecs')
|
||||
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('LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||
shutil.copy('other/windows/Additional-LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/unrar', 0o777)
|
||||
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7za', 0o777)
|
||||
if os.path.isfile('setup.sh'):
|
||||
os.system('./setup.sh')
|
||||
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7z', 0o777)
|
||||
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
|
||||
exit(0)
|
||||
elif sys.platform == 'win32':
|
||||
os.system('pyinstaller -y -F -i icons\comic2ebook.ico -n KCC -w --noupx kcc.py')
|
||||
if os.path.isfile('setup.bat'):
|
||||
os.system('setup.bat')
|
||||
os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC -w --noupx kcc.py')
|
||||
exit(0)
|
||||
else:
|
||||
if self.pyz:
|
||||
script = '''
|
||||
cp kcc.py __main__.py
|
||||
zip kcc.zip __main__.py kcc/*.py
|
||||
echo "#!/usr/bin/env python3" > kcc-bin
|
||||
cat kcc.zip >> kcc-bin
|
||||
chmod +x kcc-bin
|
||||
|
||||
cp kcc-c2e.py __main__.py
|
||||
zip kcc-c2e.zip __main__.py kcc/*.py
|
||||
echo "#!/usr/bin/env python3" > kcc-c2e-bin
|
||||
cat kcc-c2e.zip >> kcc-c2e-bin
|
||||
chmod +x kcc-c2e-bin
|
||||
|
||||
cp kcc-c2p.py __main__.py
|
||||
zip kcc-c2p.zip __main__.py kcc/*.py
|
||||
echo "#!/usr/bin/env python3" > kcc-c2p-bin
|
||||
cat kcc-c2p.zip >> kcc-c2p-bin
|
||||
chmod +x kcc-c2p-bin
|
||||
|
||||
mkdir dist
|
||||
tar --xform s:^.*/:: \
|
||||
--xform s/LICENSE.txt/LICENSE/ \
|
||||
--xform s/kcc-bin/kcc/ \
|
||||
--xform s/kcc-c2p-bin/kcc-c2p/ \
|
||||
--xform s/kcc-c2e-bin/kcc-c2e/ \
|
||||
--xform s/comic2ebook/kcc/ \
|
||||
-czf dist/KindleComicConverter_linux_''' + VERSION + '''.tar.gz \
|
||||
kcc-bin kcc-c2e-bin kcc-c2p-bin LICENSE.txt README.md icons/comic2ebook.png
|
||||
rm __main__.py kcc.zip kcc-c2e.zip kcc-c2p.zip kcc-bin kcc-c2e-bin kcc-c2p-bin
|
||||
'''
|
||||
os.system("bash -c '%s'" % script)
|
||||
exit(0)
|
||||
else:
|
||||
os.system('docker run --rm -v ' + os.getcwd() + ':/app -e KCCVER=' + VERSION + ' acidweb/kcc')
|
||||
exit(0)
|
||||
|
||||
|
||||
class BuildCommand(build):
|
||||
def run(self):
|
||||
os.makedirs('build/_scripts/', exist_ok=True)
|
||||
shutil.copyfile('kcc.py', 'build/_scripts/kcc')
|
||||
shutil.copyfile('kcc-c2e.py', 'build/_scripts/kcc-c2e')
|
||||
shutil.copyfile('kcc-c2p.py', 'build/_scripts/kcc-c2p')
|
||||
# noinspection PyShadowingNames
|
||||
OPTIONS = dict(
|
||||
scripts=['build/_scripts/kcc',
|
||||
'build/_scripts/kcc-c2e',
|
||||
'build/_scripts/kcc-c2p'],
|
||||
packages=['kcc'],
|
||||
install_requires=[
|
||||
'PyQt5>=5.6.0'
|
||||
'Pillow>=3.2.0',
|
||||
'psutil>=4.1.0',
|
||||
'python-slugify>=1.2.0',
|
||||
'raven>=5.13.0',
|
||||
],
|
||||
zip_safe=False,
|
||||
)
|
||||
if sys.version_info[1] < 5:
|
||||
OPTIONS['install_requires'].append('scandir>=1.2.0')
|
||||
build.run(self)
|
||||
exit(0)
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
cmdclass={
|
||||
'build_binary': BuildBinaryCommand,
|
||||
'build': BuildCommand,
|
||||
},
|
||||
name=NAME,
|
||||
version=VERSION,
|
||||
@@ -131,7 +65,25 @@ setuptools.setup(
|
||||
author_email='ciromattia@gmail.com, pawelj@iosphe.re',
|
||||
description='Comic and Manga converter for e-book readers.',
|
||||
license='ISC License (ISCL)',
|
||||
keywords='kindle comic mobipocket mobi cbz cbr manga',
|
||||
keywords=['kindle', 'kobo', 'comic', 'manga', 'mobi', 'epub', 'cbz'],
|
||||
url='http://github.com/ciromattia/kcc',
|
||||
**OPTIONS
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'kcc-c2e = kindlecomicconverter.startup:startC2E',
|
||||
'kcc-c2p = kindlecomicconverter.startup:startC2P',
|
||||
],
|
||||
'gui_scripts': [
|
||||
'kcc = kindlecomicconverter.startup:start',
|
||||
],
|
||||
},
|
||||
packages=['kindlecomicconverter'],
|
||||
install_requires=[
|
||||
'PyQt5>=5.6.0',
|
||||
'Pillow>=5.2.0',
|
||||
'psutil>=5.0.0',
|
||||
'python-slugify>=1.2.1',
|
||||
'raven>=6.0.0',
|
||||
],
|
||||
classifiers=[],
|
||||
zip_safe=False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user