1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-17 14:38:47 +00:00

Compare commits

...

53 Commits
4.3 ... 4.5

Author SHA1 Message Date
Paweł Jastrzębski
00d239e1d8 Merge pull request #131 from ciromattia/dev
4.5
2015-03-08 18:08:15 +01:00
Paweł Jastrzębski
26bd2d3ed0 Version bump 2015-03-08 18:07:47 +01:00
Paweł Jastrzębski
e7aa49b70c Fixed bookmark drift caused by image splits 2015-03-08 18:06:27 +01:00
Paweł Jastrzębski
da41edc2f1 GUI tweak 2015-02-26 18:27:10 +01:00
fsteffek
ecbf60fb28 Add ComicInfo bookmarks -> EPUB chapter support
Bookmarks from ComicRack's ComicInfo.xml are now converted to EPUB
chapters. `Bookmark` is an attribute to the `Page` element in
ComicInfo.xml.

 * Works with flat directory structure (no sub-folders support)
2015-02-21 18:40:59 +01:00
Paweł Jastrzębski
57b571b6c2 Fixed CLI version 2015-02-21 18:30:54 +01:00
Paweł Jastrzębski
44bdc0245b Fool proofing file sorting 2015-02-21 18:25:55 +01:00
fsteffek
1ec07fe4ec Fix random order of chapter list (close #129)
Directories and files returned by walk() are not always sorted.
Currently we sort the filelist after creating the it. When walk()-ing
topdown (we do) the directory list can be modified in-place. By sorting
the directories and files before creating the filelist, we guarantee
a sorted chapter list.
2015-02-21 17:59:46 +01:00
Paweł Jastrzębski
5f8f7e0919 Merge pull request #128 from fsteffek/dev
Fixed first file detection
2015-02-20 10:01:34 +01:00
fsteffek
f404b9090d Set first image in folder as cover
The walk() function does not necessarily return filenames in
alphabetical order. This leads to a random cover image.

Sorting filenames before setting the cover images fixes this issue.
2015-02-19 22:33:25 +01:00
Paweł Jastrzębski
68521f7c63 Updated installer 2015-02-17 18:08:23 +01:00
Paweł Jastrzębski
f5dd813c4c General refactoring and tweaks 2015-02-14 09:47:14 +01:00
Paweł Jastrzębski
7924c492b3 Updated OSX and Linux GUI 2015-02-08 12:52:42 +01:00
Paweł Jastrzębski
2fc21c33e2 Added metadata editor 2015-02-08 11:53:55 +01:00
Paweł Jastrzębski
cb76504acb Updated installer 2015-02-01 08:57:24 +01:00
Paweł Jastrzębski
db6b0eddfe Completely replaced Virtual Panel View with Panel View 2015-01-31 11:10:01 +01:00
Paweł Jastrzębski
7d529a2acc Added Metadata editor class 2015-01-24 18:36:15 +01:00
Paweł Jastrzębski
ad3ff35aaa Yet another workaround for file lock problems (#125) 2015-01-24 10:07:27 +01:00
Paweł Jastrzębski
c62eeeb712 Re-enabled MCD support 2015-01-21 19:15:38 +01:00
Paweł Jastrzębski
5a36a13105 Replaced os.walk 2015-01-21 18:19:54 +01:00
Paweł Jastrzębski
12684d6562 Code cleanup 2015-01-20 21:45:22 +01:00
Paweł Jastrzębski
c5f68ae12a Merge pull request #124 from ciromattia/dev
4.4.1
2015-01-11 16:20:43 +01:00
Paweł Jastrzębski
7bd9c766cc Version bump 2015-01-11 16:20:01 +01:00
Paweł Jastrzębski
c6b1417d9c Added one Windows DLL 2015-01-11 16:17:04 +01:00
Paweł Jastrzębski
98bf28a713 Fixed OSX GUI anomalies 2015-01-10 11:09:28 +01:00
Paweł Jastrzębski
f2d6d5b458 Fixed upgrade freeze (close #123) 2015-01-05 10:47:21 +01:00
Paweł Jastrzębski
5de492ffb6 Overhauled dependency check 2015-01-05 08:43:15 +01:00
Paweł Jastrzębski
5c2c6ed825 Tweaked OSX hack 2015-01-04 18:59:12 +01:00
Paweł Jastrzębski
c2730ab01c Merge pull request #122 from ciromattia/dev
4.4
2015-01-04 14:57:09 +01:00
Paweł Jastrzębski
bfba66d47d Improved KindleGen detection 2015-01-04 14:53:14 +01:00
Paweł Jastrzębski
b5bc2f8e00 Updated README + version bump 2015-01-04 10:49:59 +01:00
Paweł Jastrzębski
917eaef548 GUI tweaks 2015-01-04 10:27:13 +01:00
Paweł Jastrzębski
3187ebb054 Generic processing improvements 2015-01-04 09:13:20 +01:00
Paweł Jastrzębski
b9276e9ede WebToon processing improvements 2015-01-04 09:11:46 +01:00
Paweł Jastrzębski
147d815057 Code cleanup 2015-01-04 09:09:33 +01:00
Paweł Jastrzębski
180123fee2 Improved 7zip detection 2015-01-03 09:17:00 +01:00
Paweł Jastrzębski
2768e622f2 PyQT 5.4 workarounds 2015-01-02 11:51:14 +01:00
Paweł Jastrzębski
b629b45d46 Pillow update 2015-01-02 09:55:34 +01:00
Paweł Jastrzębski
f66c83425c PyQT 5.4 update 2015-01-01 15:24:59 +01:00
Paweł Jastrzębski
68b4b7114d Added RAR5 support 2014-12-30 11:07:11 +01:00
Paweł Jastrzębski
36f8c82eaf Fixed OSX race condition 2014-12-30 11:00:21 +01:00
Paweł Jastrzębski
bd665c3261 Merge pull request #119 from ciromattia/4.x
4.3.1
2014-11-24 20:13:17 +01:00
Paweł Jastrzębski
94586fa590 Version bump 2014-11-24 19:07:50 +01:00
Paweł Jastrzębski
89806dfcfc Disabled MCD integration 2014-11-24 18:56:24 +01:00
Paweł Jastrzębski
c9eb73ab90 OS X: Miscellaneous fixes (close #115) 2014-11-24 18:47:53 +01:00
Paweł Jastrzębski
2edcc0369a Updated UnRAR 2014-11-22 15:22:31 +01:00
Paweł Jastrzębski
bc0a52b848 PEP 8 2014-11-21 22:46:43 +01:00
Paweł Jastrzębski
5ae72bf06d Fixed Kindle Voyage profile 2014-11-21 22:41:51 +01:00
Paweł Jastrzębski
24c32643c1 Tweaked glob support 2014-11-10 19:33:03 +01:00
Paweł Jastrzębski
40b988f964 Merge branch 'multifile' of git://github.com/theaquamarine/kcc into 4.x 2014-10-29 07:14:39 +01:00
blue
ac794eff85 Use glob to resolve file globs
For systems where the shell doesn't do glob expansion, eg Windows.
2014-10-28 18:34:14 +00:00
blue
eaa387a9d6 Add basic support for converting more >1 file 2014-10-28 16:51:31 +00:00
Paweł Jastrzębski
d0f5d6dac4 Tweaked title sorting 2014-10-17 14:15:35 +02:00
35 changed files with 6666 additions and 3937 deletions

27
KCC.qrc
View File

@@ -1,27 +0,0 @@
<RCC>
<qresource prefix="Icon">
<file>icons/comic2ebook.png</file>
</qresource>
<qresource prefix="Devices">
<file>icons/Kobo.png</file>
<file>icons/Other.png</file>
<file>icons/Kindle.png</file>
</qresource>
<qresource prefix="Formats">
<file>icons/CBZ.png</file>
<file>icons/EPUB.png</file>
<file>icons/MOBI.png</file>
</qresource>
<qresource prefix="Status">
<file>icons/error.png</file>
<file>icons/info.png</file>
<file>icons/warning.png</file>
</qresource>
<qresource prefix="Other">
<file>icons/list_background.png</file>
<file>icons/clear.png</file>
<file>icons/convert.png</file>
<file>icons/document_new.png</file>
<file>icons/folder_new.png</file>
</qresource>
</RCC>

View File

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

391
README.md
View File

@@ -13,8 +13,7 @@ _KC2_ in no way is a replacement for **KCC** so you can be quite confident we'll
### Issues / new features / donations ### Issues / new features / donations
If you have general questions about usage, feedback etc. please [post it here](http://www.mobileread.com/forums/showthread.php?t=207461). If you have general questions about usage, feedback etc. please [post it here](http://www.mobileread.com/forums/showthread.php?t=207461).
If you have some **technical** problems using KCC please [file an issue here](https://github.com/ciromattia/kcc/issues/new). If you have some **technical** problems using KCC please [file an issue here](https://github.com/ciromattia/kcc/issues/new).
If you can fix an open issue, fork & make a pull request. If you can fix an open issue, fork & make a pull request.
If you want more chances an issue is fixes or your wanted feature added, consider [placing a bounty](https://www.bountysource.com/trackers/65571-ciromattia-kcc)!
If you find **KCC** valuable you can consider donating to the authors: If you find **KCC** valuable you can consider donating to the authors:
- Ciro Mattia Gonano: - Ciro Mattia Gonano:
@@ -32,7 +31,7 @@ You can find the latest released binary at the following links:
## INPUT FORMATS ## INPUT FORMATS
**KCC** can understand and convert, at the moment, the following input types: **KCC** can understand and convert, at the moment, the following input types:
- Folders containing: PNG, JPG, GIF, TIFF or BMP files - Folders containing: PNG, JPG or GIF files
- CBZ, ZIP - CBZ, ZIP
- CBR, RAR *(With `unrar` executable)* - CBR, RAR *(With `unrar` executable)*
- CB7, 7Z *(With `7za` executable)* - CB7, 7Z *(With `7za` executable)*
@@ -45,20 +44,21 @@ You can find the latest released binary at the following links:
### For running from source: ### For running from source:
- Python 3.3+ - Python 3.3+
- [PyQt5](http://www.riverbankcomputing.co.uk/software/pyqt/download5) 5.2.0+ - [PyQt](http://www.riverbankcomputing.co.uk/software/pyqt/download5) 5.2.0+
- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.5.0+ - [Pillow](http://pypi.python.org/pypi/Pillow/) 2.7.0+
- [psutil](https://pypi.python.org/pypi/psutil) 2.0+ - [psutil](https://pypi.python.org/pypi/psutil) 2.0+
- [python-slugify](http://pypi.python.org/pypi/python-slugify) - [python-slugify](http://pypi.python.org/pypi/python-slugify) 0.1.0+
- [scandir](https://pypi.python.org/pypi/scandir) 0.9+
On Debian based distributions these two commands should install all dependencies: On Debian based distributions these two commands should install all dependencies:
``` ```
sudo apt-get install python3 python3-dev python3-pip python3-pyqt5 libtiff-dev libpng-dev libjpeg-dev p7zip-full unrar sudo apt-get install python3 python3-dev python3-pip python3-pyqt5 libpng-dev libjpeg-dev p7zip-full unrar
sudo pip3 install pillow python-slugify psutil sudo pip3 install pillow python-slugify psutil scandir
``` ```
### For freezing code: ### For freezing code:
- Windows - [py2exe](https://pypi.python.org/pypi/py2exe) 0.9.2+ - Windows - [py2exe](https://pypi.python.org/pypi/py2exe) 0.9.2.2+
- OS X - [py2app](https://bitbucket.org/ronaldoussoren/py2app) 0.8.0+ - OS X - [py2app](https://bitbucket.org/ronaldoussoren/py2app) 0.9.0+
## USAGE ## USAGE
@@ -143,7 +143,7 @@ This script born as a cross-platform alternative to `KindleComicParser` by **Dc5
The app relies and includes the following scripts: The app relies and includes the following scripts:
- `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License. - `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License.
- `rarfile.py` script &copy; 2005-2011 **Marko Kreen** <markokr@gmail.com>. Released with ISC License. - `rarfile.py` script &copy; 2005-2014 **Marko Kreen** <markokr@gmail.com>. Released with ISC License.
- `image.py` class from **Alex Yatskov**'s [Mangle](https://github.com/FooSoft/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches. - `image.py` class from **Alex Yatskov**'s [Mangle](https://github.com/FooSoft/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches.
- Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License. - Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License.
@@ -159,113 +159,128 @@ The app relies and includes the following scripts:
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu!-KoAH2O.cbz) * [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu!-KoAH2O.cbz)
## CHANGELOG ## CHANGELOG
####1.0 ####4.5:
* Initial version * Added simple ComicRack medadata 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
####1.1 ####4.4.1:
* Added support for CBZ/CBR files in comic2ebook.py * Fixed problems with OSX GUI
* Added one missing DLL to Windows installer
####1.1.1 ####4.4:
* Added support for CBZ/CBR files in Kindle Comic Converter * Improved speed and quality of conversion
* Added RAR5 support
* Dropped BMP and TIFF support
* Fixed some WebToon mode bugs
* Fixed CBR parsing on OSX
####1.2 ####4.3.1:
* 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! * 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
####1.3 ####4.3:
* Fixed an issue in OPF generation for device resolution * Added profiles for Kindle Voyage and Kobo Aura H2O
* Reworked options system (call with -h option to get the inline help) * Added missing features to CLI version
* Other minor bug fixes
####1.4 ####4.2.1:
* Added some options for controlling image optimization * Improved margin color detection
* Further optimization (ImageOps, page numbering cut, autocontrast) * 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
####1.4.1 ####4.2:
* Fixed a serious bug on resizing when img ratio was bigger than device one * 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
####1.5 ####4.1:
* Added subfolder support for multiple chapters. * 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
####2.0 ####4.0.2:
* GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support. * Fixed some Windows and OSX specific bugs
* Fixed problem with marigns when using HQ mode
####2.1 ####4.0.1:
* Added basic error reporting * 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
####2.2: ####4.0:
* Added (valid!) EPUB 2.0 output * KCC now use Python 3.3 and Qt 5.2
* Rename .zip files to .cbz to avoid overwriting * 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
####2.3 ####3.7.2:
* Fixed win32 EPUB generation, folder handling, filenames with spaces and subfolders * Fixed problems with HQ mode
####2.4 ####3.7.1:
* Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming) * Hotfixed Kobo profiles
* Fixed "add folders" from GUI.
####2.5 ####3.7:
* Added --black-borders option to set added borders black when page's ratio is not the device's one (#11). * Added profiles for KOBO devices
* Fixes EPUB containing zipped itself (#10) * Improved Panel View support
* Improved WebToon splitter
* Improved margin color autodetection
* Tweaked EPUB output
* Fixed stretching option
* GUI tweaks and minor bugfixes
####2.6 ####3.6.2:
* Added --rotate option to rotate landscape images instead of splitting them (#16, #24) * Fixed previous PNG output fix
* Added --output option to customize EPUB output dir/file (#22) * Fixed Panel View anomalies
* Add rendition:layout and rendition:orientation EPUB meta tags (supported by new kindlegen 2.8)
* Fixed natural sorting for files (#18)
####2.7 ####3.6.1:
* Lots of GUI improvements (#27, #13) * Fixed PNG output
* 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.8 ####3.6:
* Updated rarfile library * Increased quality of Panel View zoom
* Panel View support + HQ support (#36) - new option: --nopanelviewhq * Creation of multipart MOBI output is now faster on machines with 4GB+ RAM
* Split profiles for K4NT and K4T * Automatic gamma correction now distinguishes color and grayscale images
* Rewrite of Landscape Mode support (huge readability improvement for KPW) * Added ComicRack metadata parser
* Upscale use now BILINEAR method * Implemented new method to detect border color in non-webtoon comics
* Added generic CSS file * Upscaling is now enabled by default for Kindle Fire HD/HDX
* Optimized archive extraction for zip/rar files (#40) * Windows nad Linux releases now have tray icon
* Fixed Kindle Fire HDX 7" output
* Increased target resolution for Kindle DX/DXG CBZ output
####2.9 ####3.5:
* Added support for generating a plain CBZ (skipping all the EPUB/MOBI generation) (#45) * Added simple content server - Converted files can be now delivered wireless
* Prevent output file overwriting the source one: if a duplicate name is detected, append _kcc to the name * Added proper Windows installer
* Rarfile library updated to 2.6 * Improved multiprocessing speed
* Added GIF, TIFF and BMP to supported formats (#42) * GUI tweaks and minor bug fixes
* Filenames slugifications (#28, #31, #9, #8)
####2.10: ####3.4:
* Multiprocessing support * Improved PNG output
* Kindle Fire support (color EPUB/MOBI) * Increased quality of upscaling
* Panel View support for horizontal content * Added support of file association - KCC can now open CBZ, CBR, CB7, ZIP, RAR, 7Z and PDF files directly
* Fixed panel order for horizontal pages when --rotate is enabled * Paths that contain UTF-8 characters are now supported
* Disabled cropping and page number cutting for blank pages * Migrated to new version of Pillow library
* Fixed some slugify issues with specific file naming conventions (#50, #51) * Merged DX and DXG profiles
* Many other minor bug fixes and GUI 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
####3.1:
* Added profile: Kindle for Android
* Add file/directory dialogs now support multiselect
* Many small fixes and 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.3: ####3.3:
* Margins are now automatically omitted in Panel View mode * Margins are now automatically omitted in Panel View mode
@@ -281,105 +296,117 @@ The app relies and includes the following scripts:
* Windows release is now bundled with UnRAR and 7za * Windows release is now bundled with UnRAR and 7za
* Small GUI tweaks * Small GUI tweaks
####3.4: ####3.2:
* Improved PNG output * Too big EPUB files are now splitted before conversion to MOBI
* Increased quality of upscaling * Added experimental parser of manga webtoons
* Added support of file association - KCC can now open CBZ, CBR, CB7, ZIP, RAR, 7Z and PDF files directly * Improved error handling
* 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.5: ####3.2.1:
* Added simple content server - Converted files can be now delivered wireless * Hotfixed crash occurring on OS with Russian locale
* Added proper Windows installer
* Improved multiprocessing speed
* GUI tweaks and minor bug fixes
####3.6: ####3.1:
* Increased quality of Panel View zoom * Added profile: Kindle for Android
* Creation of multipart MOBI output is now faster on machines with 4GB+ RAM * Add file/directory dialogs now support multiselect
* Automatic gamma correction now distinguishes color and grayscale images * Many small fixes and tweaks
* 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.6.1: ####3.0:
* Fixed PNG output * New QT GUI
* Merge with AWKCC
* Added ultra quality mode
* Added support for custom width/height
* Added option to disable color conversion
####3.6.2: ####2.10:
* Fixed previous PNG output fix * Multiprocessing support
* Fixed Panel View anomalies * 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)
####3.7: ####2.9
* Added profiles for KOBO devices * Added support for generating a plain CBZ (skipping all the EPUB/MOBI generation) (#45)
* Improved Panel View support * Prevent output file overwriting the source one: if a duplicate name is detected, append _kcc to the name
* Improved WebToon splitter * Rarfile library updated to 2.6
* Improved margin color autodetection * Added GIF, TIFF and BMP to supported formats (#42)
* Tweaked EPUB output * Filenames slugifications (#28, #31, #9, #8)
* Fixed stretching option
* GUI tweaks and minor bugfixes
####3.7.1: ####2.8
* Hotfixed Kobo profiles * 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)
####3.7.2: ####2.7
* Fixed problems with HQ mode * 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
####4.0: ####2.6
* KCC now use Python 3.3 and Qt 5.2 * Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
* Full UTF-8 awareness * Added --output option to customize EPUB output dir/file (#22)
* CBZ output now support Manga mode * Add rendition:layout and rendition:orientation EPUB meta tags (supported by new kindlegen 2.8)
* Improved Panel View support and margin color detection * Fixed natural sorting for files (#18)
* 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
####4.0.1: ####2.5
* Fixed file lock problems that plagued some Windows users * Added --black-borders option to set added borders black when page's ratio is not the device's one (#11).
* Fixed content server failing to start on Windows * Fixes EPUB containing zipped itself (#10)
* Improved performance of WebToon splitter
* Tweaked margin color detection
####4.0.2: ####2.4
* Fixed some Windows and OSX specific bugs * Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
* Fixed problem with marigns when using HQ mode * Fixed "add folders" from GUI.
####4.1: ####2.3
* Thanks to code contributed by Kevin Hendricks speed of MOBI creation was greatly increased * Fixed win32 EPUB generation, folder handling, filenames with spaces and subfolders
* 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.2: ####2.2:
* Added [Manga Cover Database](http://manga.joentjuh.nl/) support * Added (valid!) EPUB 2.0 output
* Officially dropped Windows XP support * Rename .zip files to .cbz to avoid overwriting
* Fixed _Other_ profile
* Fixed problems with page order on stock KOBO CBZ reader
* Many other small bug fixes and tweaks
####4.2.1: ####2.1
* Improved margin color detection * Added basic error reporting
* 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.3: ####2.0
* Added profiles for Kindle Voyage and Kobo Aura H2O * GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support.
* Added missing features to CLI version
* Other minor bug fixes ####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
## KNOWN ISSUES ## KNOWN ISSUES
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues). Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
## COPYRIGHT ## COPYRIGHT
Copyright (c) 2012-2014 Ciro Mattia Gonano and Paweł Jastrzębski. Copyright (c) 2012-2015 Ciro Mattia Gonano and Paweł Jastrzębski.
**KCC** is released under ISC LICENSE; see LICENSE.txt for further details. **KCC** is released under ISC LICENSE; see LICENSE.txt for further details.

View File

@@ -71,7 +71,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable image optimizations.&lt;br/&gt;Input images must be already resized.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable image optimizations.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Input images must be already resized.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>No optimisation</string> <string>No optimisation</string>
@@ -110,7 +110,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable auto-splitting of webtoons like &lt;span style=&quot; font-style:italic;&quot;&gt;Tower of God&lt;/span&gt; or &lt;span style=&quot; font-style:italic;&quot;&gt;Noblesse&lt;/span&gt;.&lt;br/&gt;This mode was created for pages with a low width, high height and vertical panel flow.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Enable special parsing mode for WebToons.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Webtoon mode</string> <string>Webtoon mode</string>
@@ -167,7 +167,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable splitting and rotation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable page splitting and rotation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>No split/rotate</string> <string>No split/rotate</string>
@@ -241,7 +241,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to select the output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to select the output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Convert</string> <string>Convert</string>
@@ -269,6 +269,9 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add directory containing JPG, PNG or GIF files to queue.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;CBR, CBZ and CB7 files inside will not be processed!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Add directory</string> <string>Add directory</string>
</property> </property>
@@ -295,6 +298,9 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add CBR, CBZ, CB7 or PDF file to queue.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Add file</string> <string>Add file</string>
</property> </property>
@@ -386,7 +392,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - Normal quality mode&lt;/span&gt;&lt;br/&gt;Maximal quality of images but very poor magnification quality.&lt;br/&gt;Use it only when zoom is not needed or output files needs to be small.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - High quality mode&lt;/span&gt;&lt;br/&gt;In most cases high quality of images and magnification.&lt;br/&gt;Overall quality highly depends on the resolution of source files.&lt;br/&gt;On Kindle models older than Paperwhite non-zoomed images might be a little blurred.&lt;br/&gt;&lt;br/&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Ultra quality mode&lt;/span&gt;&lt;br/&gt;Highest possible quality. Output files will be big.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Quality of Panel View/zoom. Highly impact size of output file.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;This option control only quality of magnification!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>High/Ultra quality</string> <string>High/Ultra quality</string>
@@ -457,19 +463,13 @@
<height>18</height> <height>18</height>
</size> </size>
</property> </property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
</widget> </widget>
<widget class="QPushButton" name="BasicModeButton"> <widget class="QPushButton" name="BasicModeButton">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>10</x> <x>10</x>
<y>10</y> <y>10</y>
<width>195</width> <width>141</width>
<height>32</height> <height>32</height>
</rect> </rect>
</property> </property>
@@ -489,9 +489,9 @@
<widget class="QPushButton" name="AdvModeButton"> <widget class="QPushButton" name="AdvModeButton">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>217</x> <x>260</x>
<y>10</y> <y>10</y>
<width>195</width> <width>151</width>
<height>32</height> <height>32</height>
</rect> </rect>
</property> </property>
@@ -661,7 +661,7 @@
</font> </font>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Custom width: </string> <string>Custom width: </string>
@@ -694,7 +694,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="inputMask"> <property name="inputMask">
<string>0000</string> <string>0000</string>
@@ -712,7 +712,7 @@
</font> </font>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Custom height: </string> <string>Custom height: </string>
@@ -745,7 +745,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Resolution of target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="inputMask"> <property name="inputMask">
<string>0000</string> <string>0000</string>
@@ -758,6 +758,32 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<widget class="QPushButton" name="EditorButton">
<property name="geometry">
<rect>
<x>160</x>
<y>10</y>
<width>91</width>
<height>32</height>
</rect>
</property>
<property name="font">
<font>
<family>DejaVu Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Editor</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
</property>
</widget>
<zorder>OptionsAdvanced</zorder> <zorder>OptionsAdvanced</zorder>
<zorder>DeviceBox</zorder> <zorder>DeviceBox</zorder>
<zorder>FormatBox</zorder> <zorder>FormatBox</zorder>
@@ -771,6 +797,7 @@
<zorder>AdvModeButton</zorder> <zorder>AdvModeButton</zorder>
<zorder>OptionsAdvancedGamma</zorder> <zorder>OptionsAdvancedGamma</zorder>
<zorder>OptionsExpert</zorder> <zorder>OptionsExpert</zorder>
<zorder>EditorButton</zorder>
<zorder>ProgressBar</zorder> <zorder>ProgressBar</zorder>
</widget> </widget>
<widget class="QStatusBar" name="statusBar"> <widget class="QStatusBar" name="statusBar">

View File

@@ -69,7 +69,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable image optimizations.&lt;br/&gt;Input images must be already resized.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable image optimizations.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Input images must be already resized.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>No optimisation</string> <string>No optimisation</string>
@@ -110,7 +110,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable auto-splitting of webtoons like &lt;span style=&quot; font-style:italic;&quot;&gt;Tower of God &lt;/span&gt;or &lt;span style=&quot; font-style:italic;&quot;&gt;Noblesse&lt;/span&gt;.&lt;br/&gt;This mode was created for pages with a low width, high height and vertical panel flow.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Enable special parsing mode for WebToons.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Webtoon mode</string> <string>Webtoon mode</string>
@@ -170,7 +170,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable splitting and rotation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable page splitting and rotation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>No split/rotate</string> <string>No split/rotate</string>
@@ -220,7 +220,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Output format.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Output format.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
<widget class="QPushButton" name="ConvertButton"> <widget class="QPushButton" name="ConvertButton">
@@ -244,7 +244,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to select the output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to select the output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Convert</string> <string>Convert</string>
@@ -272,6 +272,9 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add directory containing JPG, PNG or GIF files to queue.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;CBR, CBZ and CB7 files inside will not be processed!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Add directory</string> <string>Add directory</string>
</property> </property>
@@ -298,6 +301,9 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add CBR, CBZ, CB7 or PDF file to queue.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Add file</string> <string>Add file</string>
</property> </property>
@@ -391,7 +397,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - Normal quality mode&lt;/span&gt;&lt;br/&gt;Maximal quality of images but very poor magnification quality.&lt;br/&gt;Use it only when zoom is not needed or output files needs to be small.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - High quality mode&lt;/span&gt;&lt;br/&gt;In most cases high quality of images and magnification.&lt;br/&gt;Overall quality highly depends on the resolution of source files.&lt;br/&gt;On Kindle models older than Paperwhite non-zoomed images might be a little blurred.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Ultra quality mode&lt;/span&gt;&lt;br/&gt;Highest possible quality. Output files will be big.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Quality of Panel View/zoom. Highly impact size of output file.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;This option control only quality of magnification!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>High/Ultra quality</string> <string>High/Ultra quality</string>
@@ -456,19 +462,13 @@
<property name="selectionMode"> <property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum> <enum>QAbstractItemView::NoSelection</enum>
</property> </property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
</widget> </widget>
<widget class="QPushButton" name="BasicModeButton"> <widget class="QPushButton" name="BasicModeButton">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>5</x> <x>5</x>
<y>10</y> <y>10</y>
<width>210</width> <width>156</width>
<height>41</height> <height>41</height>
</rect> </rect>
</property> </property>
@@ -490,9 +490,9 @@
<widget class="QPushButton" name="AdvModeButton"> <widget class="QPushButton" name="AdvModeButton">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>207</x> <x>260</x>
<y>10</y> <y>10</y>
<width>210</width> <width>156</width>
<height>41</height> <height>41</height>
</rect> </rect>
</property> </property>
@@ -773,9 +773,34 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<widget class="QPushButton" name="EditorButton">
<property name="geometry">
<rect>
<x>160</x>
<y>10</y>
<width>101</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<family>Lucida Grande</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Editor</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
</property>
</widget>
<zorder>BasicModeButton</zorder> <zorder>BasicModeButton</zorder>
<zorder>AdvModeButton</zorder> <zorder>AdvModeButton</zorder>
<zorder>ProgressBar</zorder>
<zorder>JobList</zorder> <zorder>JobList</zorder>
<zorder>OptionsAdvanced</zorder> <zorder>OptionsAdvanced</zorder>
<zorder>DeviceBox</zorder> <zorder>DeviceBox</zorder>
@@ -787,6 +812,8 @@
<zorder>OptionsBasic</zorder> <zorder>OptionsBasic</zorder>
<zorder>OptionsAdvancedGamma</zorder> <zorder>OptionsAdvancedGamma</zorder>
<zorder>OptionsExpert</zorder> <zorder>OptionsExpert</zorder>
<zorder>EditorButton</zorder>
<zorder>ProgressBar</zorder>
</widget> </widget>
<widget class="QStatusBar" name="statusBar"> <widget class="QStatusBar" name="statusBar">
<property name="font"> <property name="font">

28
gui/KCC.qrc Normal file
View File

@@ -0,0 +1,28 @@
<RCC>
<qresource prefix="Icon">
<file>../icons/comic2ebook.png</file>
</qresource>
<qresource prefix="Devices">
<file>../icons/Kobo.png</file>
<file>../icons/Other.png</file>
<file>../icons/Kindle.png</file>
</qresource>
<qresource prefix="Formats">
<file>../icons/CBZ.png</file>
<file>../icons/EPUB.png</file>
<file>../icons/MOBI.png</file>
</qresource>
<qresource prefix="Status">
<file>../icons/error.png</file>
<file>../icons/info.png</file>
<file>../icons/warning.png</file>
</qresource>
<qresource prefix="Other">
<file>../icons/editor.png</file>
<file>../icons/list_background.png</file>
<file>../icons/clear.png</file>
<file>../icons/convert.png</file>
<file>../icons/document_new.png</file>
<file>../icons/folder_new.png</file>
</qresource>
</RCC>

View File

@@ -65,7 +65,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable image optimizations.&lt;br/&gt;Input images must be already resized.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable image optimizations.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Input images must be already resized.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>No optimisation</string> <string>No optimisation</string>
@@ -94,7 +94,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style=&quot;white-space:pre&quot;&gt;Enable auto-splitting of webtoons like &lt;span style=&quot; font-style:italic;&quot;&gt;Tower of God&lt;/span&gt; or &lt;span style=&quot; font-style:italic;&quot;&gt;Noblesse&lt;/span&gt;.&lt;br/&gt;This mode is created for pages with a low width, high height and vertical panel flow.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Enable special parsing mode for WebToons.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Webtoon mode</string> <string>Webtoon mode</string>
@@ -136,7 +136,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable splitting and rotation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Disable page splitting and rotation.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>No split/rotate</string> <string>No split/rotate</string>
@@ -234,6 +234,9 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add directory containing JPG, PNG or GIF files to queue.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;CBR, CBZ and CB7 files inside will not be processed!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Add directory</string> <string>Add directory</string>
</property> </property>
@@ -259,6 +262,9 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add CBR, CBZ, CB7 or PDF file to queue.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Add file</string> <string>Add file</string>
</property> </property>
@@ -338,7 +344,7 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style=&quot;white-space:pre&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - Normal quality mode&lt;br/&gt;&lt;/span&gt;Maximal quality of images but very poor magnification quality.&lt;br/&gt;Use it only when zoom is not needed or output files needs to be small.&lt;/p&gt;&lt;p style=&quot;white-space:pre&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - High quality mode&lt;br/&gt;&lt;/span&gt;In most cases high quality of images and magnification.&lt;br/&gt;Overall quality highly depends on the resolution of source files.&lt;br/&gt;On Kindle models older than Paperwhite non-zoomed images might be a little blurred.&lt;/p&gt;&lt;p style=&quot;white-space:pre&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Ultra quality mode&lt;br/&gt;&lt;/span&gt;Highest possible quality. Output files will be big.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Quality of Panel View/zoom. Highly impact size of output file.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;This option control only quality of magnification!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>High/Ultra quality</string> <string>High/Ultra quality</string>
@@ -391,19 +397,13 @@
<property name="selectionMode"> <property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum> <enum>QAbstractItemView::NoSelection</enum>
</property> </property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
</widget> </widget>
<widget class="QPushButton" name="BasicModeButton"> <widget class="QPushButton" name="BasicModeButton">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>10</x> <x>10</x>
<y>10</y> <y>10</y>
<width>195</width> <width>141</width>
<height>32</height> <height>32</height>
</rect> </rect>
</property> </property>
@@ -422,9 +422,9 @@
<widget class="QPushButton" name="AdvModeButton"> <widget class="QPushButton" name="AdvModeButton">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>217</x> <x>261</x>
<y>10</y> <y>10</y>
<width>195</width> <width>151</width>
<height>32</height> <height>32</height>
</rect> </rect>
</property> </property>
@@ -647,6 +647,31 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<widget class="QPushButton" name="EditorButton">
<property name="geometry">
<rect>
<x>160</x>
<y>10</y>
<width>91</width>
<height>32</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Editor</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
</property>
</widget>
<zorder>OptionsAdvanced</zorder> <zorder>OptionsAdvanced</zorder>
<zorder>DeviceBox</zorder> <zorder>DeviceBox</zorder>
<zorder>FormatBox</zorder> <zorder>FormatBox</zorder>
@@ -660,6 +685,7 @@
<zorder>AdvModeButton</zorder> <zorder>AdvModeButton</zorder>
<zorder>OptionsAdvancedGamma</zorder> <zorder>OptionsAdvancedGamma</zorder>
<zorder>OptionsExpert</zorder> <zorder>OptionsExpert</zorder>
<zorder>EditorButton</zorder>
<zorder>ProgressBar</zorder> <zorder>ProgressBar</zorder>
</widget> </widget>
<widget class="QStatusBar" name="statusBar"> <widget class="QStatusBar" name="statusBar">

226
gui/MetaEditor-Linux.ui Normal file
View File

@@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MetaEditorDialog</class>
<widget class="QDialog" name="MetaEditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>320</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>320</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>320</height>
</size>
</property>
<property name="windowTitle">
<string>Metadata editor</string>
</property>
<property name="windowIcon">
<iconset resource="KCC.qrc">
<normaloff>:/Icon/icons/comic2ebook.png</normaloff>:/Icon/icons/comic2ebook.png</iconset>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>280</y>
<width>381</width>
<height>31</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="StatusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0);</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="OKButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Save</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/convert.png</normaloff>:/Other/icons/convert.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CancelButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="EditorFrame">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>381</width>
<height>271</height>
</rect>
</property>
<widget class="QWidget" name="formLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>381</width>
<height>266</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Series:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="SeriesLine"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Volume:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="VolumeLine"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Number:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="NumberLine"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Writer:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="WriterLine"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Penciller:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="PencillerLine"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Inker:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="InkerLine"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Colorist:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="ColoristLine"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;MUid:&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="MUidLine"/>
</item>
</layout>
</widget>
</widget>
<zorder>horizontalLayoutWidget</zorder>
<zorder>EditorFrame</zorder>
<zorder>StatusLabel</zorder>
</widget>
<resources>
<include location="KCC.qrc"/>
</resources>
<connections/>
</ui>

223
gui/MetaEditor-OSX.ui Normal file
View File

@@ -0,0 +1,223 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MetaEditorDialog</class>
<widget class="QDialog" name="MetaEditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>295</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>295</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>295</height>
</size>
</property>
<property name="windowTitle">
<string>Metadata editor</string>
</property>
<property name="windowIcon">
<iconset resource="KCC.qrc">
<normaloff>:/Icon/icons/comic2ebook.png</normaloff>:/Icon/icons/comic2ebook.png</iconset>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>260</y>
<width>381</width>
<height>32</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="StatusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0);</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="OKButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Save</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/convert.png</normaloff>:/Other/icons/convert.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CancelButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="EditorFrame">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>381</width>
<height>251</height>
</rect>
</property>
<widget class="QWidget" name="formLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>381</width>
<height>250</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Series:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="SeriesLine"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Volume:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="VolumeLine"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Number:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="NumberLine"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Writer:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="WriterLine"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Penciller:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="PencillerLine"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Inker:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="InkerLine"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Colorist:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="ColoristLine"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;MUid:&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="MUidLine"/>
</item>
</layout>
</widget>
</widget>
</widget>
<resources>
<include location="KCC.qrc"/>
</resources>
<connections/>
</ui>

222
gui/MetaEditor.ui Normal file
View File

@@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MetaEditorDialog</class>
<widget class="QDialog" name="MetaEditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>260</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>260</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>260</height>
</size>
</property>
<property name="windowTitle">
<string>Metadata editor</string>
</property>
<property name="windowIcon">
<iconset resource="KCC.qrc">
<normaloff>:/Icon/icons/comic2ebook.png</normaloff>:/Icon/icons/comic2ebook.png</iconset>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>220</y>
<width>381</width>
<height>31</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="StatusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0);</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="OKButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Save</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/convert.png</normaloff>:/Other/icons/convert.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CancelButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="EditorFrame">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>381</width>
<height>211</height>
</rect>
</property>
<widget class="QWidget" name="formLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>381</width>
<height>211</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Series:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="SeriesLine"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Volume:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="VolumeLine"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Number:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="NumberLine"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Writer:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="WriterLine"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Penciller:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="PencillerLine"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Inker:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="InkerLine"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Colorist:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="ColoristLine"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;MUid:&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="MUidLine"/>
</item>
</layout>
</widget>
</widget>
</widget>
<resources>
<include location="KCC.qrc"/>
</resources>
<connections/>
</ui>

BIN
icons/editor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -18,55 +18,20 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
__version__ = '4.3'
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import sys import sys
if sys.version_info[0] != 3: if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
# Dependency check from kcc.shared import dependencyCheck
missing = [] dependencyCheck(2)
try:
# noinspection PyUnresolvedReferences
import psutil
if tuple(map(int, ('2.0.0'.split(".")))) > tuple(map(int, psutil.version_info)):
missing.append('psutil 2.0.0+')
except ImportError:
missing.append('psutil 2.0.0+')
try:
# noinspection PyUnresolvedReferences
import PIL
if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))):
missing.append('Pillow 2.5.0+')
except ImportError:
missing.append('Pillow 2.5.0+')
try:
# noinspection PyUnresolvedReferences
import slugify
except ImportError:
missing.append('python-slugify')
if len(missing) > 0:
try:
# noinspection PyUnresolvedReferences
import tkinter
# noinspection PyUnresolvedReferences
import tkinter.messagebox
importRoot = tkinter.Tk()
importRoot.withdraw()
tkinter.messagebox.showerror('KCC - Error', 'ERROR: ' + ', '.join(missing) + ' is not installed!')
except ImportError:
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1)
from multiprocessing import freeze_support from multiprocessing import freeze_support
from kcc import __version__
from kcc.comic2ebook import main from kcc.comic2ebook import main
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() freeze_support()
print(('comic2ebook v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
main(sys.argv[1:]) main(sys.argv[1:])
sys.exit(0) sys.exit(0)

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -18,43 +18,20 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
__version__ = '4.3'
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import sys import sys
if sys.version_info[0] != 3: if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
# Dependency check from kcc.shared import dependencyCheck
missing = [] dependencyCheck(1)
try:
# noinspection PyUnresolvedReferences
import PIL
if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))):
missing.append('Pillow 2.5.0+')
except ImportError:
missing.append('Pillow 2.5.0+')
if len(missing) > 0:
try:
# noinspection PyUnresolvedReferences
import tkinter
# noinspection PyUnresolvedReferences
import tkinter.messagebox
importRoot = tkinter.Tk()
importRoot.withdraw()
tkinter.messagebox.showerror('KCC - Error', 'ERROR: ' + ', '.join(missing) + ' is not installed!')
except ImportError:
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1)
from multiprocessing import freeze_support from multiprocessing import freeze_support
from kcc import __version__
from kcc.comic2panel import main from kcc.comic2panel import main
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() freeze_support()
print(('comic2ebook v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
main(sys.argv[1:]) main(sys.argv[1:])
sys.exit(0) sys.exit(0)

50
kcc.iss
View File

@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter" #define MyAppName "Kindle Comic Converter"
#define MyAppVersion "4.3" #define MyAppVersion "4.5"
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski" #define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
#define MyAppURL "http://kcc.iosphe.re/" #define MyAppURL "http://kcc.iosphe.re/"
#define MyAppExeName "KCC.exe" #define MyAppExeName "KCC.exe"
@@ -12,7 +12,7 @@ AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL} AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL} AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL} AppUpdatesURL={#MyAppURL}
AppCopyright=Copyright (C) 2012-2014 Ciro Mattia Gonano and Paweł Jastrzębski AppCopyright=Copyright (C) 2012-2015 Ciro Mattia Gonano and Paweł Jastrzębski
DefaultDirName={pf}\{#MyAppName} DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
AllowNoIcons=yes AllowNoIcons=yes
@@ -85,3 +85,49 @@ Root: HKCR; SubKey: ".cb7"; ValueType: string; ValueData: "KCCCB7"; Flags: unins
Root: HKCR; SubKey: "KCCCB7"; ValueType: string; ValueData: "KCC 7z Archive"; Flags: uninsdeletekey; Tasks: CB7association Root: HKCR; SubKey: "KCCCB7"; ValueType: string; ValueData: "KCC 7z Archive"; Flags: uninsdeletekey; Tasks: CB7association
Root: HKCR; SubKey: "KCCCB7\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CB7association Root: HKCR; SubKey: "KCCCB7\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CB7association
Root: HKCR; Subkey: "KCCCB7\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CB7association Root: HKCR; Subkey: "KCCCB7\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CB7association
[Code]
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
Result := 0;
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;

137
kcc.py
View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -18,67 +18,18 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
__version__ = '4.3'
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import sys import sys
if sys.version_info[0] != 3: if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
# Dependency check
missing = []
try:
# noinspection PyUnresolvedReferences
from PyQt5 import QtCore, QtNetwork, QtWidgets
if tuple(map(int, ('5.2.0'.split(".")))) > tuple(map(int, (QtCore.qVersion().split(".")))):
missing.append('PyQt5 5.2.0+')
except ImportError:
missing.append('PyQt5 5.2.0+')
try:
# noinspection PyUnresolvedReferences
import psutil
if tuple(map(int, ('2.0.0'.split(".")))) > tuple(map(int, psutil.version_info)):
missing.append('psutil 2.0.0+')
except ImportError:
missing.append('psutil 2.0.0+')
try:
# noinspection PyUnresolvedReferences
import PIL
if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))):
missing.append('Pillow 2.5.0+')
except ImportError:
missing.append('Pillow 2.5.0+')
try:
# noinspection PyUnresolvedReferences
import slugify
except ImportError:
missing.append('python-slugify')
if len(missing) > 0:
try:
# noinspection PyUnresolvedReferences
import tkinter
# noinspection PyUnresolvedReferences
import tkinter.messagebox
importRoot = tkinter.Tk()
importRoot.withdraw()
tkinter.messagebox.showerror('KCC - Error', 'ERROR: ' + ', '.join(missing) + ' is not installed!')
except ImportError:
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1)
import os
from multiprocessing import freeze_support
from kcc import KCC_gui
# OS specific PATH variable workarounds # OS specific PATH variable workarounds
import os
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
if 'RESOURCEPATH' in os.environ: if 'RESOURCEPATH' not in os.environ:
os.environ['PATH'] = os.environ['RESOURCEPATH'] + ':' + os.environ['PATH']
else:
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/:' + os.environ['PATH'] os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/:' + os.environ['PATH']
else:
os.environ['PATH'] = './../Resources:/usr/local/bin:/usr/bin:/bin'
elif sys.platform.startswith('win'): elif sys.platform.startswith('win'):
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
os.chdir(os.path.dirname(os.path.abspath(sys.executable))) os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
@@ -96,79 +47,23 @@ elif sys.platform.startswith('win'):
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/;' + os.environ['PATH'] os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/;' + os.environ['PATH']
os.chdir(os.path.dirname(os.path.abspath(__file__))) os.chdir(os.path.dirname(os.path.abspath(__file__)))
from kcc.shared import dependencyCheck
dependencyCheck(3)
# Implementing detection of already running KCC instance and forwarding argv to it from multiprocessing import freeze_support
class QApplicationMessaging(QtWidgets.QApplication): from kcc import KCC_gui
messageFromOtherInstance = QtCore.pyqtSignal(bytes)
def __init__(self, argv):
QtWidgets.QApplication.__init__(self, argv)
self._key = 'KCC'
self._timeout = 1000
self._locked = False
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
if not socket.waitForConnected(self._timeout):
self._server = QtNetwork.QLocalServer(self)
# noinspection PyUnresolvedReferences
self._server.newConnection.connect(self.handleMessage)
self._server.listen(self._key)
else:
self._locked = True
socket.disconnectFromServer()
def __del__(self):
if not self._locked:
self._server.close()
def event(self, e):
if e.type() == QtCore.QEvent.FileOpen:
self.messageFromOtherInstance.emit(bytes(e.file(), 'UTF-8'))
return True
else:
return QtWidgets.QApplication.event(self, e)
def isRunning(self):
return self._locked
def handleMessage(self):
socket = self._server.nextPendingConnection()
if socket.waitForReadyRead(self._timeout):
self.messageFromOtherInstance.emit(socket.readAll().data())
def sendMessage(self, message):
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
socket.waitForConnected(self._timeout)
socket.write(bytes(message, 'UTF-8'))
socket.waitForBytesWritten(self._timeout)
socket.disconnectFromServer()
# Adding signals to QMainWindow
class QMainWindowKCC(QtWidgets.QMainWindow):
progressBarTick = QtCore.pyqtSignal(str)
modeConvert = QtCore.pyqtSignal(int)
addMessage = QtCore.pyqtSignal(str, str, bool)
addTrayMessage = QtCore.pyqtSignal(str, str)
showDialog = QtCore.pyqtSignal(str, str)
hideProgressBar = QtCore.pyqtSignal()
forceShutdown = QtCore.pyqtSignal()
dialogAnswer = QtCore.pyqtSignal(int)
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() freeze_support()
KCCAplication = QApplicationMessaging(sys.argv) KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
if KCCAplication.isRunning(): if KCCAplication.isRunning():
if len(sys.argv) > 1: if len(sys.argv) > 1:
KCCAplication.sendMessage(sys.argv[1]) KCCAplication.sendMessage(sys.argv[1])
sys.exit(0)
else: else:
KCCAplication.sendMessage('ARISE') KCCAplication.sendMessage('ARISE')
sys.exit(0) else:
KCCWindow = QMainWindowKCC() KCCWindow = KCC_gui.QMainWindowKCC()
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow) KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
if len(sys.argv) > 1: if len(sys.argv) > 1:
KCCUI.handleMessage(sys.argv[1]) KCCUI.handleMessage(sys.argv[1])
sys.exit(KCCAplication.exec_()) sys.exit(KCCAplication.exec_())

147
kcc/KCC_MetaEditor_ui.py Normal file
View File

@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'MetaEditor.ui'
#
# Created: Sun Feb 8 11:52:00 2015
# by: PyQt5 UI code generator 5.4
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MetaEditorDialog(object):
def setupUi(self, MetaEditorDialog):
MetaEditorDialog.setObjectName("MetaEditorDialog")
MetaEditorDialog.resize(400, 260)
MetaEditorDialog.setMinimumSize(QtCore.QSize(400, 260))
MetaEditorDialog.setMaximumSize(QtCore.QSize(400, 260))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
MetaEditorDialog.setWindowIcon(icon)
self.horizontalLayoutWidget = QtWidgets.QWidget(MetaEditorDialog)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 220, 381, 31))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.StatusLabel = QtWidgets.QLabel(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.StatusLabel.sizePolicy().hasHeightForWidth())
self.StatusLabel.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.StatusLabel.setFont(font)
self.StatusLabel.setStyleSheet("color: rgb(255, 0, 0);")
self.StatusLabel.setObjectName("StatusLabel")
self.horizontalLayout.addWidget(self.StatusLabel)
self.OKButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.OKButton.sizePolicy().hasHeightForWidth())
self.OKButton.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.OKButton.setFont(font)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.OKButton.setIcon(icon1)
self.OKButton.setObjectName("OKButton")
self.horizontalLayout.addWidget(self.OKButton)
self.CancelButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.CancelButton.sizePolicy().hasHeightForWidth())
self.CancelButton.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.CancelButton.setFont(font)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.CancelButton.setIcon(icon2)
self.CancelButton.setObjectName("CancelButton")
self.horizontalLayout.addWidget(self.CancelButton)
self.EditorFrame = QtWidgets.QFrame(MetaEditorDialog)
self.EditorFrame.setGeometry(QtCore.QRect(10, 10, 381, 211))
self.EditorFrame.setObjectName("EditorFrame")
self.formLayoutWidget = QtWidgets.QWidget(self.EditorFrame)
self.formLayoutWidget.setGeometry(QtCore.QRect(0, 0, 381, 211))
self.formLayoutWidget.setObjectName("formLayoutWidget")
self.formLayout = QtWidgets.QFormLayout(self.formLayoutWidget)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(self.formLayoutWidget)
self.label.setObjectName("label")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label)
self.SeriesLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.SeriesLine.setObjectName("SeriesLine")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.SeriesLine)
self.label_2 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_2)
self.VolumeLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.VolumeLine.setObjectName("VolumeLine")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.VolumeLine)
self.label_3 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_3.setObjectName("label_3")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_3)
self.NumberLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.NumberLine.setObjectName("NumberLine")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.NumberLine)
self.label_4 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_4.setObjectName("label_4")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_4)
self.WriterLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.WriterLine.setObjectName("WriterLine")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.WriterLine)
self.label_5 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_5.setObjectName("label_5")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_5)
self.PencillerLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.PencillerLine.setObjectName("PencillerLine")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.PencillerLine)
self.label_6 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_6.setObjectName("label_6")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_6)
self.InkerLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.InkerLine.setObjectName("InkerLine")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.InkerLine)
self.label_7 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_7.setObjectName("label_7")
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_7)
self.ColoristLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.ColoristLine.setObjectName("ColoristLine")
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.ColoristLine)
self.label_8 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_8.setTextFormat(QtCore.Qt.RichText)
self.label_8.setOpenExternalLinks(True)
self.label_8.setObjectName("label_8")
self.formLayout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.label_8)
self.MUidLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.MUidLine.setObjectName("MUidLine")
self.formLayout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.MUidLine)
self.retranslateUi(MetaEditorDialog)
QtCore.QMetaObject.connectSlotsByName(MetaEditorDialog)
def retranslateUi(self, MetaEditorDialog):
_translate = QtCore.QCoreApplication.translate
MetaEditorDialog.setWindowTitle(_translate("MetaEditorDialog", "Metadata editor"))
self.OKButton.setText(_translate("MetaEditorDialog", "Save"))
self.CancelButton.setText(_translate("MetaEditorDialog", "Cancel"))
self.label.setText(_translate("MetaEditorDialog", "Series:"))
self.label_2.setText(_translate("MetaEditorDialog", "Volume:"))
self.label_3.setText(_translate("MetaEditorDialog", "Number:"))
self.label_4.setText(_translate("MetaEditorDialog", "Writer:"))
self.label_5.setText(_translate("MetaEditorDialog", "Penciller:"))
self.label_6.setText(_translate("MetaEditorDialog", "Inker:"))
self.label_7.setText(_translate("MetaEditorDialog", "Colorist:"))
self.label_8.setText(_translate("MetaEditorDialog", "<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>"))
from . import KCC_rc

View File

@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'gui/MetaEditor.ui'
#
# Created: Sun Feb 8 03:24:23 2015
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MetaEditorDialog(object):
def setupUi(self, MetaEditorDialog):
MetaEditorDialog.setObjectName("MetaEditorDialog")
MetaEditorDialog.resize(400, 320)
MetaEditorDialog.setMinimumSize(QtCore.QSize(400, 320))
MetaEditorDialog.setMaximumSize(QtCore.QSize(400, 320))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
MetaEditorDialog.setWindowIcon(icon)
self.horizontalLayoutWidget = QtWidgets.QWidget(MetaEditorDialog)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 280, 381, 31))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.StatusLabel = QtWidgets.QLabel(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.StatusLabel.sizePolicy().hasHeightForWidth())
self.StatusLabel.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(True)
font.setWeight(75)
self.StatusLabel.setFont(font)
self.StatusLabel.setStyleSheet("color: rgb(255, 0, 0);")
self.StatusLabel.setObjectName("StatusLabel")
self.horizontalLayout.addWidget(self.StatusLabel)
self.OKButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.OKButton.sizePolicy().hasHeightForWidth())
self.OKButton.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.OKButton.setFont(font)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.OKButton.setIcon(icon1)
self.OKButton.setObjectName("OKButton")
self.horizontalLayout.addWidget(self.OKButton)
self.CancelButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.CancelButton.sizePolicy().hasHeightForWidth())
self.CancelButton.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.CancelButton.setFont(font)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.CancelButton.setIcon(icon2)
self.CancelButton.setObjectName("CancelButton")
self.horizontalLayout.addWidget(self.CancelButton)
self.EditorFrame = QtWidgets.QFrame(MetaEditorDialog)
self.EditorFrame.setGeometry(QtCore.QRect(10, 10, 381, 271))
self.EditorFrame.setObjectName("EditorFrame")
self.formLayoutWidget = QtWidgets.QWidget(self.EditorFrame)
self.formLayoutWidget.setGeometry(QtCore.QRect(0, 0, 381, 266))
self.formLayoutWidget.setObjectName("formLayoutWidget")
self.formLayout = QtWidgets.QFormLayout(self.formLayoutWidget)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(self.formLayoutWidget)
self.label.setObjectName("label")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label)
self.SeriesLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.SeriesLine.setObjectName("SeriesLine")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.SeriesLine)
self.label_2 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_2)
self.VolumeLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.VolumeLine.setObjectName("VolumeLine")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.VolumeLine)
self.label_3 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_3.setObjectName("label_3")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_3)
self.NumberLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.NumberLine.setObjectName("NumberLine")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.NumberLine)
self.label_4 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_4.setObjectName("label_4")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_4)
self.WriterLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.WriterLine.setObjectName("WriterLine")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.WriterLine)
self.label_5 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_5.setObjectName("label_5")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_5)
self.PencillerLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.PencillerLine.setObjectName("PencillerLine")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.PencillerLine)
self.label_6 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_6.setObjectName("label_6")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_6)
self.InkerLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.InkerLine.setObjectName("InkerLine")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.InkerLine)
self.label_7 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_7.setObjectName("label_7")
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_7)
self.ColoristLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.ColoristLine.setObjectName("ColoristLine")
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.ColoristLine)
self.label_8 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_8.setTextFormat(QtCore.Qt.RichText)
self.label_8.setOpenExternalLinks(True)
self.label_8.setObjectName("label_8")
self.formLayout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.label_8)
self.MUidLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.MUidLine.setObjectName("MUidLine")
self.formLayout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.MUidLine)
self.retranslateUi(MetaEditorDialog)
QtCore.QMetaObject.connectSlotsByName(MetaEditorDialog)
def retranslateUi(self, MetaEditorDialog):
_translate = QtCore.QCoreApplication.translate
MetaEditorDialog.setWindowTitle(_translate("MetaEditorDialog", "Metadata editor"))
self.OKButton.setText(_translate("MetaEditorDialog", "Save"))
self.CancelButton.setText(_translate("MetaEditorDialog", "Cancel"))
self.label.setText(_translate("MetaEditorDialog", "Series:"))
self.label_2.setText(_translate("MetaEditorDialog", "Volume:"))
self.label_3.setText(_translate("MetaEditorDialog", "Number:"))
self.label_4.setText(_translate("MetaEditorDialog", "Writer:"))
self.label_5.setText(_translate("MetaEditorDialog", "Penciller:"))
self.label_6.setText(_translate("MetaEditorDialog", "Inker:"))
self.label_7.setText(_translate("MetaEditorDialog", "Colorist:"))
self.label_8.setText(_translate("MetaEditorDialog", "<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>"))
from . import KCC_rc

View File

@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/pawelj/Documents/KCC/gui/MetaEditor.ui'
#
# Created: Sun Feb 8 12:47:09 2015
# by: PyQt5 UI code generator 5.4
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MetaEditorDialog(object):
def setupUi(self, MetaEditorDialog):
MetaEditorDialog.setObjectName("MetaEditorDialog")
MetaEditorDialog.resize(400, 295)
MetaEditorDialog.setMinimumSize(QtCore.QSize(400, 295))
MetaEditorDialog.setMaximumSize(QtCore.QSize(400, 295))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
MetaEditorDialog.setWindowIcon(icon)
self.horizontalLayoutWidget = QtWidgets.QWidget(MetaEditorDialog)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 260, 381, 32))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.StatusLabel = QtWidgets.QLabel(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.StatusLabel.sizePolicy().hasHeightForWidth())
self.StatusLabel.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setPointSize(10)
font.setBold(True)
font.setWeight(75)
self.StatusLabel.setFont(font)
self.StatusLabel.setStyleSheet("color: rgb(255, 0, 0);")
self.StatusLabel.setObjectName("StatusLabel")
self.horizontalLayout.addWidget(self.StatusLabel)
self.OKButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.OKButton.sizePolicy().hasHeightForWidth())
self.OKButton.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.OKButton.setFont(font)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.OKButton.setIcon(icon1)
self.OKButton.setObjectName("OKButton")
self.horizontalLayout.addWidget(self.OKButton)
self.CancelButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.CancelButton.sizePolicy().hasHeightForWidth())
self.CancelButton.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.CancelButton.setFont(font)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.CancelButton.setIcon(icon2)
self.CancelButton.setObjectName("CancelButton")
self.horizontalLayout.addWidget(self.CancelButton)
self.EditorFrame = QtWidgets.QFrame(MetaEditorDialog)
self.EditorFrame.setGeometry(QtCore.QRect(10, 10, 381, 251))
self.EditorFrame.setObjectName("EditorFrame")
self.formLayoutWidget = QtWidgets.QWidget(self.EditorFrame)
self.formLayoutWidget.setGeometry(QtCore.QRect(0, 0, 381, 250))
self.formLayoutWidget.setObjectName("formLayoutWidget")
self.formLayout = QtWidgets.QFormLayout(self.formLayoutWidget)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(self.formLayoutWidget)
self.label.setObjectName("label")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label)
self.SeriesLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.SeriesLine.setObjectName("SeriesLine")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.SeriesLine)
self.label_2 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_2)
self.VolumeLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.VolumeLine.setObjectName("VolumeLine")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.VolumeLine)
self.label_3 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_3.setObjectName("label_3")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_3)
self.NumberLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.NumberLine.setObjectName("NumberLine")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.NumberLine)
self.label_4 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_4.setObjectName("label_4")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_4)
self.WriterLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.WriterLine.setObjectName("WriterLine")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.WriterLine)
self.label_5 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_5.setObjectName("label_5")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_5)
self.PencillerLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.PencillerLine.setObjectName("PencillerLine")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.PencillerLine)
self.label_6 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_6.setObjectName("label_6")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_6)
self.InkerLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.InkerLine.setObjectName("InkerLine")
self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.InkerLine)
self.label_7 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_7.setObjectName("label_7")
self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_7)
self.ColoristLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.ColoristLine.setObjectName("ColoristLine")
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.ColoristLine)
self.label_8 = QtWidgets.QLabel(self.formLayoutWidget)
self.label_8.setTextFormat(QtCore.Qt.RichText)
self.label_8.setOpenExternalLinks(True)
self.label_8.setObjectName("label_8")
self.formLayout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.label_8)
self.MUidLine = QtWidgets.QLineEdit(self.formLayoutWidget)
self.MUidLine.setObjectName("MUidLine")
self.formLayout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.MUidLine)
self.retranslateUi(MetaEditorDialog)
QtCore.QMetaObject.connectSlotsByName(MetaEditorDialog)
def retranslateUi(self, MetaEditorDialog):
_translate = QtCore.QCoreApplication.translate
MetaEditorDialog.setWindowTitle(_translate("MetaEditorDialog", "Metadata editor"))
self.OKButton.setText(_translate("MetaEditorDialog", "Save"))
self.CancelButton.setText(_translate("MetaEditorDialog", "Cancel"))
self.label.setText(_translate("MetaEditorDialog", "Series:"))
self.label_2.setText(_translate("MetaEditorDialog", "Volume:"))
self.label_3.setText(_translate("MetaEditorDialog", "Number:"))
self.label_4.setText(_translate("MetaEditorDialog", "Writer:"))
self.label_5.setText(_translate("MetaEditorDialog", "Penciller:"))
self.label_6.setText(_translate("MetaEditorDialog", "Inker:"))
self.label_7.setText(_translate("MetaEditorDialog", "Colorist:"))
self.label_8.setText(_translate("MetaEditorDialog", "<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>"))
from . import KCC_rc

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -17,11 +17,6 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
__version__ = '4.3'
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import os import os
import sys import sys
from urllib.parse import unquote from urllib.parse import unquote
@@ -33,20 +28,86 @@ from shutil import move
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn from socketserver import ThreadingMixIn
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
from xml.dom.minidom import parse from xml.dom.minidom import parse
from html.parser import HTMLParser
from psutil import Popen, Process from psutil import Popen, Process
from copy import copy from copy import copy
from .shared import md5Checksum from distutils.version import StrictVersion
from xml.sax.saxutils import escape
from .shared import md5Checksum, HTMLStripper
from . import __version__
from . import comic2ebook from . import comic2ebook
from . import KCC_rc_web from . import KCC_rc_web
from . import metadata
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from . import KCC_ui_osx as KCC_ui from . import KCC_ui_osx as KCC_ui
elif sys.platform.startswith('linux'): elif sys.platform.startswith('linux'):
from . import KCC_ui_linux as KCC_ui from . import KCC_ui_linux as KCC_ui
else: else:
from . import KCC_ui from . import KCC_ui
if sys.platform.startswith('darwin'):
from . import KCC_MetaEditor_ui_osx as KCC_MetaEditor_ui
elif sys.platform.startswith('linux'):
from . import KCC_MetaEditor_ui_linux as KCC_MetaEditor_ui
else:
from . import KCC_MetaEditor_ui
class QApplicationMessaging(QtWidgets.QApplication):
messageFromOtherInstance = QtCore.pyqtSignal(bytes)
def __init__(self, argv):
QtWidgets.QApplication.__init__(self, argv)
self._key = 'KCC'
self._timeout = 1000
self._locked = False
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
if not socket.waitForConnected(self._timeout):
self._server = QtNetwork.QLocalServer(self)
# noinspection PyUnresolvedReferences
self._server.newConnection.connect(self.handleMessage)
self._server.listen(self._key)
else:
self._locked = True
socket.disconnectFromServer()
def __del__(self):
if not self._locked:
self._server.close()
def event(self, e):
if e.type() == QtCore.QEvent.FileOpen:
self.messageFromOtherInstance.emit(bytes(e.file(), 'UTF-8'))
return True
else:
return QtWidgets.QApplication.event(self, e)
def isRunning(self):
return self._locked
def handleMessage(self):
socket = self._server.nextPendingConnection()
if socket.waitForReadyRead(self._timeout):
self.messageFromOtherInstance.emit(socket.readAll().data())
def sendMessage(self, message):
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
socket.waitForConnected(self._timeout)
socket.write(bytes(message, 'UTF-8'))
socket.waitForBytesWritten(self._timeout)
socket.disconnectFromServer()
class QMainWindowKCC(QtWidgets.QMainWindow):
progressBarTick = QtCore.pyqtSignal(str)
modeConvert = QtCore.pyqtSignal(int)
addMessage = QtCore.pyqtSignal(str, str, bool)
addTrayMessage = QtCore.pyqtSignal(str, str)
showDialog = QtCore.pyqtSignal(str, str)
hideProgressBar = QtCore.pyqtSignal()
forceShutdown = QtCore.pyqtSignal()
class Icons: class Icons:
@@ -76,19 +137,6 @@ class Icons:
self.programIcon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.programIcon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
class HTMLStripper(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
class WebServerHandler(BaseHTTPRequestHandler): class WebServerHandler(BaseHTTPRequestHandler):
# noinspection PyAttributeOutsideInit, PyArgumentList # noinspection PyAttributeOutsideInit, PyArgumentList
def do_GET(self): def do_GET(self):
@@ -111,15 +159,15 @@ class WebServerHandler(BaseHTTPRequestHandler):
self.send_header('Content-type', 'text/html') self.send_header('Content-type', 'text/html')
self.end_headers() self.end_headers()
self.wfile.write(bytes('<!DOCTYPE html>\n' self.wfile.write(bytes('<!DOCTYPE html>\n'
'<html lang="en">\n' '<html lang="en">\n'
'<head><meta charset="utf-8">\n' '<head><meta charset="utf-8">\n'
'<link href="' + GUI.webContent.favicon + '" rel="icon" type="image/x-icon" />\n' '<link href="' + GUI.webContent.favicon + '" rel="icon" type="image/x-icon" />\n'
'<title>Kindle Comic Converter</title>\n' '<title>Kindle Comic Converter</title>\n'
'</head>\n' '</head>\n'
'<body>\n' '<body>\n'
'<div style="text-align: center; font-size:25px">\n' '<div style="text-align: center; font-size:25px">\n'
'<p style="font-size:50px">- <img style="vertical-align: middle" ' '<p style="font-size:50px">- <img style="vertical-align: middle" '
'alt="KCC Logo" src="' + GUI.webContent.logo + '" /> -</p>\n', 'UTF-8')) 'alt="KCC Logo" src="' + GUI.webContent.logo + '" /> -</p>\n', 'UTF-8'))
if len(GUI.completedWork) > 0 and not GUI.conversionAlive: if len(GUI.completedWork) > 0 and not GUI.conversionAlive:
for key in sorted(GUI.completedWork.keys()): for key in sorted(GUI.completedWork.keys()):
self.wfile.write(bytes('<p><a href="' + key + '">' + key.split('.')[0] + '</a></p>\n', 'UTF-8')) self.wfile.write(bytes('<p><a href="' + key + '">' + key.split('.')[0] + '</a></p>\n', 'UTF-8'))
@@ -127,8 +175,8 @@ class WebServerHandler(BaseHTTPRequestHandler):
self.wfile.write(bytes('<p style="font-weight: bold">No downloads are available.<br/>' self.wfile.write(bytes('<p style="font-weight: bold">No downloads are available.<br/>'
'Convert some files and refresh this page.</p>\n', 'UTF-8')) 'Convert some files and refresh this page.</p>\n', 'UTF-8'))
self.wfile.write(bytes('</div>\n' self.wfile.write(bytes('</div>\n'
'</body>\n' '</body>\n'
'</html>\n', 'UTF-8')) '</html>\n', 'UTF-8'))
elif sendReply: elif sendReply:
outputFile = GUI.completedWork[unquote(self.path[1:])] outputFile = GUI.completedWork[unquote(self.path[1:])]
fp = open(outputFile, 'rb') fp = open(outputFile, 'rb')
@@ -189,6 +237,8 @@ class VersionThread(QtCore.QThread):
QtCore.QThread.__init__(self) QtCore.QThread.__init__(self)
self.newVersion = '' self.newVersion = ''
self.md5 = '' self.md5 = ''
self.barProgress = 0
self.answer = None
def __del__(self): def __del__(self):
self.wait() self.wait()
@@ -200,22 +250,28 @@ class VersionThread(QtCore.QThread):
except Exception: except Exception:
return return
latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml() latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml()
if tuple(map(int, (latestVersion.split(".")))) > tuple(map(int, (__version__.split(".")))): if StrictVersion(latestVersion) > StrictVersion(__version__):
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
self.newVersion = latestVersion self.newVersion = latestVersion
self.md5 = XML.childNodes[0].getElementsByTagName('WindowsMD5')[0].childNodes[0].toxml() self.md5 = XML.childNodes[0].getElementsByTagName('WindowsMD5')[0].childNodes[0].toxml()
MW.showDialog.emit('<b>New version released!</b> <a href="https://github.com/ciromattia/kcc/releases/">' MW.showDialog.emit('<b>New version released!</b> <a href="https://github.com/ciromattia/kcc/releases/">'
'See changelog.</a><<br/><br/>Installed version: ' + __version__ + 'See changelog.</a><br/><br/>Installed version: ' + __version__ +
'<br/>Current version: ' + latestVersion + '<br/>Current version: ' + latestVersion +
'<br/><br/>Would you like to start automatic update?', 'question') '<br/><br/>Would you like to start automatic update?', 'question')
self.getNewVersion()
else: else:
MW.addMessage.emit('<a href="http://kcc.iosphe.re/">' MW.addMessage.emit('<a href="http://kcc.iosphe.re/">'
'<b>New version is available!</b></a> ' '<b>New version is available!</b></a> '
'(<a href="https://github.com/ciromattia/kcc/releases/">' '(<a href="https://github.com/ciromattia/kcc/releases/">'
'Changelog</a>)', 'warning', False) 'Changelog</a>)', 'warning', False)
def getNewVersion(self, dialogAnswer): def setAnswer(self, dialogAnswer):
if dialogAnswer == QtWidgets.QMessageBox.Yes: self.answer = dialogAnswer
def getNewVersion(self):
while self.answer is None:
sleep(1)
if self.answer == QtWidgets.QMessageBox.Yes:
try: try:
MW.modeConvert.emit(-1) MW.modeConvert.emit(-1)
MW.progressBarTick.emit('Downloading update') MW.progressBarTick.emit('Downloading update')
@@ -234,9 +290,12 @@ class VersionThread(QtCore.QThread):
MW.modeConvert.emit(1) MW.modeConvert.emit(1)
def getNewVersionTick(self, size, blockSize, totalSize): def getNewVersionTick(self, size, blockSize, totalSize):
progress = int((size / (totalSize // blockSize)) * 100)
if size == 0: if size == 0:
MW.progressBarTick.emit(str(int(totalSize / blockSize))) MW.progressBarTick.emit('100')
MW.progressBarTick.emit('tick') if progress > self.barProgress:
self.barProgress = progress
MW.progressBarTick.emit('tick')
class ProgressThread(QtCore.QThread): class ProgressThread(QtCore.QThread):
@@ -525,6 +584,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
dname = dname.replace('/', '\\') dname = dname.replace('/', '\\')
self.lastPath = os.path.abspath(os.path.join(dname, os.pardir)) self.lastPath = os.path.abspath(os.path.join(dname, os.pardir))
GUI.JobList.addItem(dname) GUI.JobList.addItem(dname)
GUI.JobList.scrollToBottom()
def selectFile(self): def selectFile(self):
if self.needClean: if self.needClean:
@@ -550,6 +610,35 @@ class KCCGUI(KCC_ui.Ui_KCC):
fname = fname.replace('/', '\\') fname = fname.replace('/', '\\')
self.lastPath = os.path.abspath(os.path.join(fname, os.pardir)) self.lastPath = os.path.abspath(os.path.join(fname, os.pardir))
GUI.JobList.addItem(fname) GUI.JobList.addItem(fname)
GUI.JobList.scrollToBottom()
def selectFileMetaEditor(self):
if self.UnRAR:
if self.sevenza:
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))
try:
self.editor.loadData(fname)
except:
self.showDialog('Failed to parse metadata!', 'error')
else:
self.editor.ui.exec_()
def clearJobs(self): def clearJobs(self):
GUI.JobList.clear() GUI.JobList.clear()
@@ -565,6 +654,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
MW.setMinimumSize(QtCore.QSize(420, 287)) MW.setMinimumSize(QtCore.QSize(420, 287))
MW.resize(420, 287) MW.resize(420, 287)
GUI.BasicModeButton.setEnabled(True) GUI.BasicModeButton.setEnabled(True)
GUI.EditorButton.setEnabled(True)
GUI.AdvModeButton.setEnabled(True) GUI.AdvModeButton.setEnabled(True)
GUI.BasicModeButton.setStyleSheet('font-weight:Bold;') GUI.BasicModeButton.setStyleSheet('font-weight:Bold;')
GUI.AdvModeButton.setStyleSheet('font-weight:Normal;') GUI.AdvModeButton.setStyleSheet('font-weight:Normal;')
@@ -594,6 +684,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
MW.setMinimumSize(QtCore.QSize(420, 365)) MW.setMinimumSize(QtCore.QSize(420, 365))
MW.resize(420, 365) MW.resize(420, 365)
GUI.BasicModeButton.setEnabled(True) GUI.BasicModeButton.setEnabled(True)
GUI.EditorButton.setEnabled(True)
GUI.AdvModeButton.setEnabled(True) GUI.AdvModeButton.setEnabled(True)
GUI.BasicModeButton.setStyleSheet('font-weight:Normal;') GUI.BasicModeButton.setStyleSheet('font-weight:Normal;')
GUI.AdvModeButton.setStyleSheet('font-weight:Bold;') GUI.AdvModeButton.setStyleSheet('font-weight:Bold;')
@@ -623,6 +714,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
MW.setMinimumSize(QtCore.QSize(420, 397)) MW.setMinimumSize(QtCore.QSize(420, 397))
MW.resize(420, 397) MW.resize(420, 397)
GUI.BasicModeButton.setEnabled(False) GUI.BasicModeButton.setEnabled(False)
GUI.EditorButton.setEnabled(True)
GUI.AdvModeButton.setEnabled(False) GUI.AdvModeButton.setEnabled(False)
GUI.BasicModeButton.setStyleSheet('font-weight:Normal;') GUI.BasicModeButton.setStyleSheet('font-weight:Normal;')
GUI.AdvModeButton.setStyleSheet('font-weight:Normal;') GUI.AdvModeButton.setStyleSheet('font-weight:Normal;')
@@ -653,6 +745,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
status = True status = True
if self.currentMode != 3: if self.currentMode != 3:
GUI.BasicModeButton.setEnabled(status) GUI.BasicModeButton.setEnabled(status)
GUI.EditorButton.setEnabled(status)
GUI.AdvModeButton.setEnabled(status) GUI.AdvModeButton.setEnabled(status)
if self.currentMode != 1: if self.currentMode != 1:
GUI.FormatBox.setEnabled(status) GUI.FormatBox.setEnabled(status)
@@ -838,9 +931,9 @@ class KCCGUI(KCC_ui.Ui_KCC):
if kind == 'error': if kind == 'error':
QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok)
elif kind == 'question': elif kind == 'question':
dialogResponse = QtWidgets.QMessageBox.question(MW, 'KCC - Question', message, GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message,
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.Yes,
MW.dialogAnswer.emit(dialogResponse) QtWidgets.QMessageBox.No))
def updateProgressbar(self, command): def updateProgressbar(self, command):
if command == 'tick': if command == 'tick':
@@ -848,6 +941,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
elif command.isdigit(): elif command.isdigit():
GUI.ProgressBar.setMaximum(int(command) - 1) GUI.ProgressBar.setMaximum(int(command) - 1)
GUI.BasicModeButton.hide() GUI.BasicModeButton.hide()
GUI.EditorButton.hide()
GUI.AdvModeButton.hide() GUI.AdvModeButton.hide()
GUI.ProgressBar.reset() GUI.ProgressBar.reset()
GUI.ProgressBar.show() GUI.ProgressBar.show()
@@ -885,20 +979,25 @@ class KCCGUI(KCC_ui.Ui_KCC):
self.addMessage('Target resolution is not set!', 'error') self.addMessage('Target resolution is not set!', 'error')
self.needClean = True self.needClean = True
return return
if str(GUI.FormatBox.currentText()) == 'MOBI' and not GUI.KindleGen: if str(GUI.FormatBox.currentText()) == 'MOBI' and not self.KindleGen:
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">' self.detectKindleGen()
'<b>KindleGen</b></a>! MOBI conversion is not possible!', 'error') if not self.KindleGen:
if sys.platform.startswith('win'): GUI.JobList.clear()
self.addMessage('Download it and place EXE in KCC directory.', 'error') self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html'
else: '?ie=UTF8&docId=1000765211"><b>KindleGen</b></a>!'
self.addMessage('Download it, and place executable in /usr/local/bin directory.', 'error') ' MOBI conversion is unavailable!', 'error')
self.needClean = True if sys.platform.startswith('win'):
return self.addMessage('Download it and place EXE in KCC directory.', 'error')
else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
self.needClean = True
return
self.worker.start() self.worker.start()
def hideProgressBar(self): def hideProgressBar(self):
GUI.ProgressBar.hide() GUI.ProgressBar.hide()
GUI.BasicModeButton.show() GUI.BasicModeButton.show()
GUI.EditorButton.show()
GUI.AdvModeButton.show() GUI.AdvModeButton.show()
def saveSettings(self, event): def saveSettings(self, event):
@@ -954,10 +1053,12 @@ class KCCGUI(KCC_ui.Ui_KCC):
formats = ['.cbz', '.zip', '.pdf'] formats = ['.cbz', '.zip', '.pdf']
if os.path.isdir(message): if os.path.isdir(message):
GUI.JobList.addItem(message) GUI.JobList.addItem(message)
GUI.JobList.scrollToBottom()
elif os.path.isfile(message): elif os.path.isfile(message):
extension = os.path.splitext(message) extension = os.path.splitext(message)
if extension[1].lower() in formats: if extension[1].lower() in formats:
GUI.JobList.addItem(message) GUI.JobList.addItem(message)
GUI.JobList.scrollToBottom()
else: else:
self.addMessage('This file type is unsupported!', 'error') self.addMessage('This file type is unsupported!', 'error')
@@ -979,6 +1080,34 @@ class KCCGUI(KCC_ui.Ui_KCC):
self.saveSettings(None) self.saveSettings(None)
sys.exit(0) sys.exit(0)
def detectKindleGen(self, startup=False):
if not sys.platform.startswith('win'):
try:
os.chmod('/usr/local/bin/kindlegen', 0o755)
except Exception:
pass
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
if kindleGenExitCode.wait() == 0:
self.KindleGen = True
versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
for line in versionCheck.stdout:
line = line.decode("utf-8")
if 'Amazon kindlegen' in line:
versionCheck = line.split('V')[1].split(' ')[0]
if StrictVersion(versionCheck) < StrictVersion('2.9'):
self.addMessage('Your <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
'1000765211">KindleGen</a> is outdated! MOBI conversion might fail.', 'warning')
break
else:
self.KindleGen = False
if startup:
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">'
'<b>KindleGen</b></a>! MOBI conversion will be unavailable!', 'error')
if sys.platform.startswith('win'):
self.addMessage('Download it and place EXE in KCC directory.', 'error')
else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
# noinspection PyArgumentList # noinspection PyArgumentList
def __init__(self, KCCAplication, KCCWindow): def __init__(self, KCCAplication, KCCWindow):
global APP, MW, GUI global APP, MW, GUI
@@ -986,16 +1115,11 @@ class KCCGUI(KCC_ui.Ui_KCC):
MW = KCCWindow MW = KCCWindow
GUI = self GUI = self
self.setupUi(MW) self.setupUi(MW)
# User settings will be reverted to default ones if were created in one of the following versions self.editor = KCCGUI_MetaEditor()
# Empty string cover all versions before this system was implemented
purgeSettingsVersions = ['']
self.icons = Icons() self.icons = Icons()
self.webContent = KCC_rc_web.WebContent() self.webContent = KCC_rc_web.WebContent()
self.settings = QtCore.QSettings('KindleComicConverter', 'KindleComicConverter') self.settings = QtCore.QSettings('KindleComicConverter', 'KindleComicConverter')
self.settingsVersion = self.settings.value('settingsVersion', '', type=str) self.settingsVersion = self.settings.value('settingsVersion', '', type=str)
if self.settingsVersion in purgeSettingsVersions:
QtCore.QSettings.clear(self.settings)
self.settingsVersion = self.settings.value('settingsVersion', '', type=str)
self.lastPath = self.settings.value('lastPath', '', type=str) self.lastPath = self.settings.value('lastPath', '', type=str)
self.lastDevice = self.settings.value('lastDevice', 0, type=int) self.lastDevice = self.settings.value('lastDevice', 0, type=int)
self.currentMode = self.settings.value('currentMode', 1, type=int) self.currentMode = self.settings.value('currentMode', 1, type=int)
@@ -1009,6 +1133,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
self.tray = SystemTrayIcon() self.tray = SystemTrayIcon()
self.conversionAlive = False self.conversionAlive = False
self.needClean = True self.needClean = True
self.KindleGen = False
self.GammaValue = 1.0 self.GammaValue = 1.0
self.completedWork = {} self.completedWork = {}
self.targetDirectory = '' self.targetDirectory = ''
@@ -1016,11 +1141,13 @@ class KCCGUI(KCC_ui.Ui_KCC):
self.listFontSize = 11 self.listFontSize = 11
self.statusBarFontSize = 10 self.statusBarFontSize = 10
self.statusBarStyle = 'QLabel{padding-top:2px;padding-bottom:3px;}' self.statusBarStyle = 'QLabel{padding-top:2px;padding-bottom:3px;}'
self.ProgressBar.setStyleSheet('QProgressBar{padding-top:5px;text-align:center;}') self.ProgressBar.setStyleSheet('QProgressBar{font-size:13px;text-align:center;'
'border:2px solid grey;border-radius:5px;}'
'QProgressBar::chunk{background-color:steelblue;width:20px;}')
elif sys.platform.startswith('linux'): elif sys.platform.startswith('linux'):
self.listFontSize = 8 self.listFontSize = 8
self.statusBarFontSize = 8 self.statusBarFontSize = 8
self.statusBarStyle = 'QLabel{padding-top:5px;padding-bottom:3px;}' self.statusBarStyle = 'QLabel{padding-top:3px;padding-bottom:3px;}'
self.statusBar.setStyleSheet('QStatusBar::item{border:0px;border-top:2px solid #C2C7CB;}') self.statusBar.setStyleSheet('QStatusBar::item{border:0px;border-top:2px solid #C2C7CB;}')
else: else:
self.listFontSize = 9 self.listFontSize = 9
@@ -1108,27 +1235,6 @@ class KCCGUI(KCC_ui.Ui_KCC):
self.addMessage('Since you are new user of <b>KCC</b> please see few ' self.addMessage('Since you are new user of <b>KCC</b> please see few '
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.', '<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
'info') 'info')
if not sys.platform.startswith('win'):
try:
os.chmod('/usr/local/bin/kindlegen', 0o755)
except Exception:
pass
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
if kindleGenExitCode.wait() == 0:
self.KindleGen = True
versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
for line in versionCheck.stdout:
line = line.decode("utf-8")
if 'Amazon kindlegen' in line:
versionCheck = line.split('V')[1].split(' ')[0]
if tuple(map(int, (versionCheck.split(".")))) < tuple(map(int, ('2.9'.split(".")))):
self.addMessage('Your <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
'1000765211">kindlegen</a> is outdated! Creating MOBI might fail.'
' Please update <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
'1000765211">kindlegen</a> from Amazon\'s website.', 'warning')
break
else:
self.KindleGen = False
rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, shell=True) rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, shell=True)
rarExitCode = rarExitCode.wait() rarExitCode = rarExitCode.wait()
if rarExitCode == 0 or rarExitCode == 7: if rarExitCode == 0 or rarExitCode == 7:
@@ -1145,6 +1251,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
self.sevenza = False self.sevenza = False
self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7za</a>!' self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7za</a>!'
' Processing of CB7/7Z files will be disabled.', 'warning') ' Processing of CB7/7Z files will be disabled.', 'warning')
self.detectKindleGen(True)
APP.messageFromOtherInstance.connect(self.handleMessage) APP.messageFromOtherInstance.connect(self.handleMessage)
GUI.BasicModeButton.clicked.connect(self.modeBasic) GUI.BasicModeButton.clicked.connect(self.modeBasic)
@@ -1152,6 +1259,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
GUI.DirectoryButton.clicked.connect(self.selectDir) GUI.DirectoryButton.clicked.connect(self.selectDir)
GUI.ClearButton.clicked.connect(self.clearJobs) GUI.ClearButton.clicked.connect(self.clearJobs)
GUI.FileButton.clicked.connect(self.selectFile) GUI.FileButton.clicked.connect(self.selectFile)
GUI.EditorButton.clicked.connect(self.selectFileMetaEditor)
GUI.ConvertButton.clicked.connect(self.convertStart) GUI.ConvertButton.clicked.connect(self.convertStart)
GUI.GammaSlider.valueChanged.connect(self.changeGamma) GUI.GammaSlider.valueChanged.connect(self.changeGamma)
GUI.NoRotateBox.stateChanged.connect(self.toggleNoSplitRotate) GUI.NoRotateBox.stateChanged.connect(self.toggleNoSplitRotate)
@@ -1166,7 +1274,6 @@ class KCCGUI(KCC_ui.Ui_KCC):
MW.showDialog.connect(self.showDialog) MW.showDialog.connect(self.showDialog)
MW.hideProgressBar.connect(self.hideProgressBar) MW.hideProgressBar.connect(self.hideProgressBar)
MW.forceShutdown.connect(self.forceShutdown) MW.forceShutdown.connect(self.forceShutdown)
MW.dialogAnswer.connect(self.versionCheck.getNewVersion)
MW.closeEvent = self.saveSettings MW.closeEvent = self.saveSettings
MW.addTrayMessage.connect(self.tray.addTrayMessage) MW.addTrayMessage.connect(self.tray.addTrayMessage)
@@ -1215,3 +1322,53 @@ class KCCGUI(KCC_ui.Ui_KCC):
MW.setWindowTitle("Kindle Comic Converter " + __version__) MW.setWindowTitle("Kindle Comic Converter " + __version__)
MW.show() MW.show()
MW.raise_() MW.raise_()
class KCCGUI_MetaEditor(KCC_MetaEditor_ui.Ui_MetaEditorDialog):
def loadData(self, file):
self.parser = metadata.MetadataParser(file)
if self.parser.compressor == 'rar':
self.EditorFrame.setEnabled(False)
self.OKButton.setEnabled(False)
self.StatusLabel.setText('CBR metadata are read-only.')
else:
self.EditorFrame.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):
field.setText(self.parser.data[field.objectName()[:-4]])
for field in (self.WriterLine, self.PencillerLine, self.InkerLine, self.ColoristLine):
field.setText(', '.join(self.parser.data[field.objectName()[:-4] + 's']))
def saveData(self):
for field in (self.VolumeLine, self.NumberLine, self.MUidLine):
if field.text().isnumeric() or self.cleanData(field.text()) == '':
self.parser.data[field.objectName()[:-4]] = self.cleanData(field.text())
else:
self.StatusLabel.setText(field.objectName()[:-4] + ' field must be a number.')
break
else:
self.parser.data['Series'] = self.cleanData(self.SeriesLine.text())
for field in (self.WriterLine, self.PencillerLine, self.InkerLine, self.ColoristLine):
values = self.cleanData(field.text()).split(',')
tmpData = []
for value in values:
if self.cleanData(value) != '':
tmpData.append(self.cleanData(value))
self.parser.data[field.objectName()[:-4] + 's'] = tmpData
try:
self.parser.saveXML()
except:
GUI.showDialog('Failed to save metadata!', 'error')
self.ui.close()
def cleanData(self, s):
return escape(s.strip())
def __init__(self):
self.ui = QtWidgets.QDialog()
self.parser = None
self.setupUi(self.ui)
self.ui.setWindowFlags(self.ui.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
self.OKButton.clicked.connect(self.saveData)
self.CancelButton.clicked.connect(self.ui.close)

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file 'KCC.ui' # Form implementation generated from reading ui file 'KCC.ui'
# #
# Created: Sun May 18 09:08:27 2014 # Created: Sun Feb 8 09:50:43 2015
# by: PyQt5 UI code generator 5.2.1 # by: PyQt5 UI code generator 5.4
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@@ -141,18 +141,16 @@ class Ui_KCC(object):
self.JobList.setStyleSheet("QListWidget#JobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}QScrollBar:vertical{border:1px solid #999;background:#FFF;width:5px;margin:0}QScrollBar::handle:vertical{background:DarkGray;min-height:0}QScrollBar::add-line:vertical{height:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:vertical{height:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}QScrollBar:horizontal{border:1px solid #999;background:#FFF;height:5px;margin:0}QScrollBar::handle:horizontal{background:DarkGray;min-width:0}QScrollBar::add-line:horizontal{width:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:horizontal{width:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}") self.JobList.setStyleSheet("QListWidget#JobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}QScrollBar:vertical{border:1px solid #999;background:#FFF;width:5px;margin:0}QScrollBar::handle:vertical{background:DarkGray;min-height:0}QScrollBar::add-line:vertical{height:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:vertical{height:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}QScrollBar:horizontal{border:1px solid #999;background:#FFF;height:5px;margin:0}QScrollBar::handle:horizontal{background:DarkGray;min-width:0}QScrollBar::add-line:horizontal{width:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:horizontal{width:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}")
self.JobList.setProperty("showDropIndicator", False) self.JobList.setProperty("showDropIndicator", False)
self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.JobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.JobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.JobList.setObjectName("JobList") self.JobList.setObjectName("JobList")
self.BasicModeButton = QtWidgets.QPushButton(self.Form) self.BasicModeButton = QtWidgets.QPushButton(self.Form)
self.BasicModeButton.setGeometry(QtCore.QRect(10, 10, 195, 32)) self.BasicModeButton.setGeometry(QtCore.QRect(10, 10, 141, 32))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.BasicModeButton.setFont(font) self.BasicModeButton.setFont(font)
self.BasicModeButton.setFocusPolicy(QtCore.Qt.NoFocus) self.BasicModeButton.setFocusPolicy(QtCore.Qt.NoFocus)
self.BasicModeButton.setObjectName("BasicModeButton") self.BasicModeButton.setObjectName("BasicModeButton")
self.AdvModeButton = QtWidgets.QPushButton(self.Form) self.AdvModeButton = QtWidgets.QPushButton(self.Form)
self.AdvModeButton.setGeometry(QtCore.QRect(217, 10, 195, 32)) self.AdvModeButton.setGeometry(QtCore.QRect(261, 10, 151, 32))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(9) font.setPointSize(9)
self.AdvModeButton.setFont(font) self.AdvModeButton.setFont(font)
@@ -231,6 +229,16 @@ class Ui_KCC(object):
self.customHeight.setMaxLength(4) self.customHeight.setMaxLength(4)
self.customHeight.setObjectName("customHeight") self.customHeight.setObjectName("customHeight")
self.gridLayout_2.addWidget(self.customHeight, 0, 3, 1, 1) self.gridLayout_2.addWidget(self.customHeight, 0, 3, 1, 1)
self.EditorButton = QtWidgets.QPushButton(self.Form)
self.EditorButton.setGeometry(QtCore.QRect(160, 10, 91, 32))
font = QtGui.QFont()
font.setPointSize(9)
self.EditorButton.setFont(font)
self.EditorButton.setFocusPolicy(QtCore.Qt.NoFocus)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.EditorButton.setIcon(icon5)
self.EditorButton.setObjectName("EditorButton")
KCC.setCentralWidget(self.Form) KCC.setCentralWidget(self.Form)
self.statusBar = QtWidgets.QStatusBar(KCC) self.statusBar = QtWidgets.QStatusBar(KCC)
font = QtGui.QFont() font = QtGui.QFont()
@@ -260,28 +268,30 @@ class Ui_KCC(object):
def retranslateUi(self, KCC): def retranslateUi(self, KCC):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter")) KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter"))
self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable image optimizations.<br/>Input images must be already resized.</p></body></html>")) self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable image optimizations.<br/><span style=\" font-weight:600;\">Input images must be already resized.</span></p></body></html>"))
self.ProcessingBox.setText(_translate("KCC", "No optimisation")) self.ProcessingBox.setText(_translate("KCC", "No optimisation"))
self.UpscaleBox.setToolTip(_translate("KCC", "<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.setToolTip(_translate("KCC", "<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("KCC", "Stretch/Upscale")) self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale"))
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\"white-space:pre\">Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>This mode is created for pages with a low width, high height and vertical panel flow.</p></body></html>")) self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for WebToons.</p></body></html>"))
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode")) self.WebtoonBox.setText(_translate("KCC", "Webtoon mode"))
self.NoDitheringBox.setToolTip(_translate("KCC", "<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.setToolTip(_translate("KCC", "<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("KCC", "PNG output")) self.NoDitheringBox.setText(_translate("KCC", "PNG output"))
self.BorderBox.setToolTip(_translate("KCC", "<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("KCC", "<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.setText(_translate("KCC", "W/B margins")) self.BorderBox.setText(_translate("KCC", "W/B margins"))
self.NoRotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting and rotation.</p></body></html>")) self.NoRotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable page splitting and rotation.</p></body></html>"))
self.NoRotateBox.setText(_translate("KCC", "No split/rotate")) self.NoRotateBox.setText(_translate("KCC", "No split/rotate"))
self.DeviceBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>")) self.DeviceBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>"))
self.FormatBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>")) self.FormatBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>"))
self.ConvertButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>")) self.ConvertButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>"))
self.ConvertButton.setText(_translate("KCC", "Convert")) self.ConvertButton.setText(_translate("KCC", "Convert"))
self.DirectoryButton.setToolTip(_translate("KCC", "<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("KCC", "Add directory")) self.DirectoryButton.setText(_translate("KCC", "Add directory"))
self.FileButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>"))
self.FileButton.setText(_translate("KCC", "Add file")) self.FileButton.setText(_translate("KCC", "Add file"))
self.ClearButton.setText(_translate("KCC", "Clear list")) self.ClearButton.setText(_translate("KCC", "Clear list"))
self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>")) self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
self.MangaBox.setText(_translate("KCC", "Manga mode")) self.MangaBox.setText(_translate("KCC", "Manga mode"))
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\"white-space:pre\"><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode<br/></span>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p style=\"white-space:pre\"><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode<br/></span>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.</p><p style=\"white-space:pre\"><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode<br/></span>Highest possible quality. Output files will be big.</p></body></html>")) self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Quality of Panel View/zoom. Highly impact size of output file.<br/><span style=\" font-weight:600;\">This option control only quality of magnification!</span></p></body></html>"))
self.QualityBox.setText(_translate("KCC", "High/Ultra quality")) self.QualityBox.setText(_translate("KCC", "High/Ultra quality"))
self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>")) self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>"))
self.RotateBox.setText(_translate("KCC", "Horizontal mode")) self.RotateBox.setText(_translate("KCC", "Horizontal mode"))
@@ -298,6 +308,7 @@ class Ui_KCC(object):
self.hLabel.setText(_translate("KCC", "Custom height: ")) self.hLabel.setText(_translate("KCC", "Custom height: "))
self.customHeight.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>")) self.customHeight.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
self.customHeight.setInputMask(_translate("KCC", "0000")) self.customHeight.setInputMask(_translate("KCC", "0000"))
self.EditorButton.setText(_translate("KCC", "Editor"))
self.ActionBasic.setText(_translate("KCC", "Basic")) self.ActionBasic.setText(_translate("KCC", "Basic"))
self.ActionAdvanced.setText(_translate("KCC", "Advanced")) self.ActionAdvanced.setText(_translate("KCC", "Advanced"))

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'KCC-Linux.ui' # Form implementation generated from reading ui file 'gui/KCC-Linux.ui'
# #
# Created: Sun May 18 09:08:37 2014 # Created: Sun Feb 8 03:10:09 2015
# by: PyQt5 UI code generator 5.2.1 # by: PyQt5 UI code generator 5.2.1
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@@ -182,11 +182,9 @@ class Ui_KCC(object):
self.JobList.setProperty("showDropIndicator", False) self.JobList.setProperty("showDropIndicator", False)
self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.JobList.setIconSize(QtCore.QSize(18, 18)) self.JobList.setIconSize(QtCore.QSize(18, 18))
self.JobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.JobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.JobList.setObjectName("JobList") self.JobList.setObjectName("JobList")
self.BasicModeButton = QtWidgets.QPushButton(self.Form) self.BasicModeButton = QtWidgets.QPushButton(self.Form)
self.BasicModeButton.setGeometry(QtCore.QRect(10, 10, 195, 32)) self.BasicModeButton.setGeometry(QtCore.QRect(10, 10, 141, 32))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("DejaVu Sans") font.setFamily("DejaVu Sans")
font.setPointSize(9) font.setPointSize(9)
@@ -194,7 +192,7 @@ class Ui_KCC(object):
self.BasicModeButton.setFocusPolicy(QtCore.Qt.NoFocus) self.BasicModeButton.setFocusPolicy(QtCore.Qt.NoFocus)
self.BasicModeButton.setObjectName("BasicModeButton") self.BasicModeButton.setObjectName("BasicModeButton")
self.AdvModeButton = QtWidgets.QPushButton(self.Form) self.AdvModeButton = QtWidgets.QPushButton(self.Form)
self.AdvModeButton.setGeometry(QtCore.QRect(217, 10, 195, 32)) self.AdvModeButton.setGeometry(QtCore.QRect(260, 10, 151, 32))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("DejaVu Sans") font.setFamily("DejaVu Sans")
font.setPointSize(9) font.setPointSize(9)
@@ -301,6 +299,17 @@ class Ui_KCC(object):
self.customHeight.setMaxLength(4) self.customHeight.setMaxLength(4)
self.customHeight.setObjectName("customHeight") self.customHeight.setObjectName("customHeight")
self.gridLayout_2.addWidget(self.customHeight, 0, 3, 1, 1) self.gridLayout_2.addWidget(self.customHeight, 0, 3, 1, 1)
self.EditorButton = QtWidgets.QPushButton(self.Form)
self.EditorButton.setGeometry(QtCore.QRect(160, 10, 91, 32))
font = QtGui.QFont()
font.setFamily("DejaVu Sans")
font.setPointSize(9)
self.EditorButton.setFont(font)
self.EditorButton.setFocusPolicy(QtCore.Qt.NoFocus)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.EditorButton.setIcon(icon5)
self.EditorButton.setObjectName("EditorButton")
KCC.setCentralWidget(self.Form) KCC.setCentralWidget(self.Form)
self.statusBar = QtWidgets.QStatusBar(KCC) self.statusBar = QtWidgets.QStatusBar(KCC)
font = QtGui.QFont() font = QtGui.QFont()
@@ -329,28 +338,30 @@ class Ui_KCC(object):
def retranslateUi(self, KCC): def retranslateUi(self, KCC):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter")) KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter"))
self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable image optimizations.<br/>Input images must be already resized.</p></body></html>")) self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable image optimizations.<br/><span style=\" font-weight:600;\">Input images must be already resized.</span></p></body></html>"))
self.ProcessingBox.setText(_translate("KCC", "No optimisation")) self.ProcessingBox.setText(_translate("KCC", "No optimisation"))
self.UpscaleBox.setToolTip(_translate("KCC", "<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.setToolTip(_translate("KCC", "<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("KCC", "Stretch/Upscale")) self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale"))
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>This mode was created for pages with a low width, high height and vertical panel flow.</p></body></html>")) self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for WebToons.</p></body></html>"))
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode")) self.WebtoonBox.setText(_translate("KCC", "Webtoon mode"))
self.NoDitheringBox.setToolTip(_translate("KCC", "<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.setToolTip(_translate("KCC", "<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("KCC", "PNG output")) self.NoDitheringBox.setText(_translate("KCC", "PNG output"))
self.BorderBox.setToolTip(_translate("KCC", "<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("KCC", "<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.setText(_translate("KCC", "W/B margins")) self.BorderBox.setText(_translate("KCC", "W/B margins"))
self.NoRotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting and rotation.</p></body></html>")) self.NoRotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable page splitting and rotation.</p></body></html>"))
self.NoRotateBox.setText(_translate("KCC", "No split/rotate")) self.NoRotateBox.setText(_translate("KCC", "No split/rotate"))
self.DeviceBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>")) self.DeviceBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>"))
self.FormatBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>")) self.FormatBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>"))
self.ConvertButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>")) self.ConvertButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>"))
self.ConvertButton.setText(_translate("KCC", "Convert")) self.ConvertButton.setText(_translate("KCC", "Convert"))
self.DirectoryButton.setToolTip(_translate("KCC", "<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("KCC", "Add directory")) self.DirectoryButton.setText(_translate("KCC", "Add directory"))
self.FileButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>"))
self.FileButton.setText(_translate("KCC", "Add file")) self.FileButton.setText(_translate("KCC", "Add file"))
self.ClearButton.setText(_translate("KCC", "Clear list")) self.ClearButton.setText(_translate("KCC", "Clear list"))
self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>")) self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
self.MangaBox.setText(_translate("KCC", "Manga mode")) self.MangaBox.setText(_translate("KCC", "Manga mode"))
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode</span><br/>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode</span><br/>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.<br/><br/><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode</span><br/>Highest possible quality. Output files will be big.</p></body></html>")) self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Quality of Panel View/zoom. Highly impact size of output file.<br/><span style=\" font-weight:600;\">This option control only quality of magnification!</span></p></body></html>"))
self.QualityBox.setText(_translate("KCC", "High/Ultra quality")) self.QualityBox.setText(_translate("KCC", "High/Ultra quality"))
self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>")) self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>"))
self.RotateBox.setText(_translate("KCC", "Horizontal mode")) self.RotateBox.setText(_translate("KCC", "Horizontal mode"))
@@ -359,14 +370,15 @@ class Ui_KCC(object):
self.GammaLabel.setText(_translate("KCC", "Gamma: Auto")) self.GammaLabel.setText(_translate("KCC", "Gamma: Auto"))
self.ColorBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Don\'t convert images to grayscale.</p></body></html>")) self.ColorBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Don\'t convert images to grayscale.</p></body></html>"))
self.ColorBox.setText(_translate("KCC", "Color mode")) self.ColorBox.setText(_translate("KCC", "Color mode"))
self.wLabel.setToolTip(_translate("KCC", "<html><head/><body><p>Resolution of target device.</p></body></html>")) self.wLabel.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
self.wLabel.setText(_translate("KCC", "Custom width: ")) self.wLabel.setText(_translate("KCC", "Custom width: "))
self.customWidth.setToolTip(_translate("KCC", "<html><head/><body><p>Resolution of target device.</p></body></html>")) self.customWidth.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
self.customWidth.setInputMask(_translate("KCC", "0000")) self.customWidth.setInputMask(_translate("KCC", "0000"))
self.hLabel.setToolTip(_translate("KCC", "<html><head/><body><p>Resolution of target device.</p></body></html>")) self.hLabel.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
self.hLabel.setText(_translate("KCC", "Custom height: ")) self.hLabel.setText(_translate("KCC", "Custom height: "))
self.customHeight.setToolTip(_translate("KCC", "<html><head/><body><p>Resolution of target device.</p></body></html>")) self.customHeight.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
self.customHeight.setInputMask(_translate("KCC", "0000")) self.customHeight.setInputMask(_translate("KCC", "0000"))
self.EditorButton.setText(_translate("KCC", "Editor"))
self.ActionBasic.setText(_translate("KCC", "Basic")) self.ActionBasic.setText(_translate("KCC", "Basic"))
self.ActionAdvanced.setText(_translate("KCC", "Advanced")) self.ActionAdvanced.setText(_translate("KCC", "Advanced"))

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'KCC-OSX.ui' # Form implementation generated from reading ui file '/Users/pawelj/Documents/KCC/gui/KCC-OSX.ui'
# #
# Created: Sun May 18 09:08:44 2014 # Created: Sun Feb 8 12:37:33 2015
# by: PyQt5 UI code generator 5.2.1 # by: PyQt5 UI code generator 5.4
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@@ -188,11 +188,9 @@ class Ui_KCC(object):
self.JobList.setStyleSheet("QListWidget#JobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}QScrollBar:vertical{border:1px solid #999;background:#FFF;width:5px;margin:0}QScrollBar::handle:vertical{background:DarkGray;min-height:0}QScrollBar::add-line:vertical{height:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:vertical{height:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}QScrollBar:horizontal{border:1px solid #999;background:#FFF;height:5px;margin:0}QScrollBar::handle:horizontal{background:DarkGray;min-width:0}QScrollBar::add-line:horizontal{width:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:horizontal{width:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}") self.JobList.setStyleSheet("QListWidget#JobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}QScrollBar:vertical{border:1px solid #999;background:#FFF;width:5px;margin:0}QScrollBar::handle:vertical{background:DarkGray;min-height:0}QScrollBar::add-line:vertical{height:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:vertical{height:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}QScrollBar:horizontal{border:1px solid #999;background:#FFF;height:5px;margin:0}QScrollBar::handle:horizontal{background:DarkGray;min-width:0}QScrollBar::add-line:horizontal{width:0;background:DarkGray;subcontrol-position:bottom;subcontrol-origin:margin}QScrollBar::sub-line:horizontal{width:0;background:DarkGray;subcontrol-position:top;subcontrol-origin:margin}")
self.JobList.setProperty("showDropIndicator", False) self.JobList.setProperty("showDropIndicator", False)
self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.JobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.JobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.JobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.JobList.setObjectName("JobList") self.JobList.setObjectName("JobList")
self.BasicModeButton = QtWidgets.QPushButton(self.Form) self.BasicModeButton = QtWidgets.QPushButton(self.Form)
self.BasicModeButton.setGeometry(QtCore.QRect(5, 10, 210, 41)) self.BasicModeButton.setGeometry(QtCore.QRect(5, 10, 156, 41))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Lucida Grande") font.setFamily("Lucida Grande")
font.setPointSize(12) font.setPointSize(12)
@@ -202,7 +200,7 @@ class Ui_KCC(object):
self.BasicModeButton.setFocusPolicy(QtCore.Qt.NoFocus) self.BasicModeButton.setFocusPolicy(QtCore.Qt.NoFocus)
self.BasicModeButton.setObjectName("BasicModeButton") self.BasicModeButton.setObjectName("BasicModeButton")
self.AdvModeButton = QtWidgets.QPushButton(self.Form) self.AdvModeButton = QtWidgets.QPushButton(self.Form)
self.AdvModeButton.setGeometry(QtCore.QRect(207, 10, 210, 41)) self.AdvModeButton.setGeometry(QtCore.QRect(260, 10, 156, 41))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Lucida Grande") font.setFamily("Lucida Grande")
font.setPointSize(12) font.setPointSize(12)
@@ -323,6 +321,17 @@ class Ui_KCC(object):
self.customHeight.setMaxLength(4) self.customHeight.setMaxLength(4)
self.customHeight.setObjectName("customHeight") self.customHeight.setObjectName("customHeight")
self.gridLayout_2.addWidget(self.customHeight, 0, 3, 1, 1) self.gridLayout_2.addWidget(self.customHeight, 0, 3, 1, 1)
self.EditorButton = QtWidgets.QPushButton(self.Form)
self.EditorButton.setGeometry(QtCore.QRect(160, 10, 101, 41))
font = QtGui.QFont()
font.setFamily("Lucida Grande")
font.setPointSize(12)
self.EditorButton.setFont(font)
self.EditorButton.setFocusPolicy(QtCore.Qt.NoFocus)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.EditorButton.setIcon(icon5)
self.EditorButton.setObjectName("EditorButton")
KCC.setCentralWidget(self.Form) KCC.setCentralWidget(self.Form)
self.statusBar = QtWidgets.QStatusBar(KCC) self.statusBar = QtWidgets.QStatusBar(KCC)
font = QtGui.QFont() font = QtGui.QFont()
@@ -352,28 +361,30 @@ class Ui_KCC(object):
def retranslateUi(self, KCC): def retranslateUi(self, KCC):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter")) KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter"))
self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable image optimizations.<br/>Input images must be already resized.</p></body></html>")) self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable image optimizations.<br/><span style=\" font-weight:600;\">Input images must be already resized.</span></p></body></html>"))
self.ProcessingBox.setText(_translate("KCC", "No optimisation")) self.ProcessingBox.setText(_translate("KCC", "No optimisation"))
self.UpscaleBox.setToolTip(_translate("KCC", "<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.setToolTip(_translate("KCC", "<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("KCC", "Stretch/Upscale")) self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale"))
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God </span>or <span style=\" font-style:italic;\">Noblesse</span>.<br/>This mode was created for pages with a low width, high height and vertical panel flow.</p><p><br/></p></body></html>")) self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for WebToons.</p></body></html>"))
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode")) self.WebtoonBox.setText(_translate("KCC", "Webtoon mode"))
self.NoDitheringBox.setToolTip(_translate("KCC", "<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.setToolTip(_translate("KCC", "<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("KCC", "PNG output")) self.NoDitheringBox.setText(_translate("KCC", "PNG output"))
self.BorderBox.setToolTip(_translate("KCC", "<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("KCC", "<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.setText(_translate("KCC", "W/B margins")) self.BorderBox.setText(_translate("KCC", "W/B margins"))
self.NoRotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting and rotation.</p></body></html>")) self.NoRotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable page splitting and rotation.</p></body></html>"))
self.NoRotateBox.setText(_translate("KCC", "No split/rotate")) self.NoRotateBox.setText(_translate("KCC", "No split/rotate"))
self.DeviceBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>")) self.DeviceBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>"))
self.FormatBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>")) self.FormatBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>"))
self.ConvertButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>")) self.ConvertButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>"))
self.ConvertButton.setText(_translate("KCC", "Convert")) self.ConvertButton.setText(_translate("KCC", "Convert"))
self.DirectoryButton.setToolTip(_translate("KCC", "<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("KCC", "Add directory")) self.DirectoryButton.setText(_translate("KCC", "Add directory"))
self.FileButton.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>"))
self.FileButton.setText(_translate("KCC", "Add file")) self.FileButton.setText(_translate("KCC", "Add file"))
self.ClearButton.setText(_translate("KCC", "Clear list")) self.ClearButton.setText(_translate("KCC", "Clear list"))
self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>")) self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
self.MangaBox.setText(_translate("KCC", "Manga mode")) self.MangaBox.setText(_translate("KCC", "Manga mode"))
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode</span><br/>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode</span><br/>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode</span><br/>Highest possible quality. Output files will be big.</p></body></html>")) self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Quality of Panel View/zoom. Highly impact size of output file.<br/><span style=\" font-weight:600;\">This option control only quality of magnification!</span></p></body></html>"))
self.QualityBox.setText(_translate("KCC", "High/Ultra quality")) self.QualityBox.setText(_translate("KCC", "High/Ultra quality"))
self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>")) self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>"))
self.RotateBox.setText(_translate("KCC", "Horizontal mode")) self.RotateBox.setText(_translate("KCC", "Horizontal mode"))
@@ -390,6 +401,7 @@ class Ui_KCC(object):
self.hLabel.setText(_translate("KCC", "Custom height: ")) self.hLabel.setText(_translate("KCC", "Custom height: "))
self.customHeight.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>")) self.customHeight.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
self.customHeight.setInputMask(_translate("KCC", "0000")) self.customHeight.setInputMask(_translate("KCC", "0000"))
self.EditorButton.setText(_translate("KCC", "Editor"))
self.ActionBasic.setText(_translate("KCC", "Basic")) self.ActionBasic.setText(_translate("KCC", "Basic"))
self.ActionAdvanced.setText(_translate("KCC", "Advanced")) self.ActionAdvanced.setText(_translate("KCC", "Advanced"))

View File

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

View File

@@ -1,5 +1,5 @@
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -16,10 +16,6 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import sys import sys
import os import os
from zipfile import is_zipfile, ZipFile from zipfile import is_zipfile, ZipFile
@@ -27,6 +23,7 @@ from subprocess import STDOUT, PIPE
from psutil import Popen from psutil import Popen
from shutil import move, copy from shutil import move, copy
from . import rarfile from . import rarfile
from .shared import check7ZFile as is_7zfile, saferReplace
class CBxArchive: class CBxArchive:
@@ -36,7 +33,7 @@ class CBxArchive:
self.compressor = 'zip' self.compressor = 'zip'
elif rarfile.is_rarfile(origFileName): elif rarfile.is_rarfile(origFileName):
self.compressor = 'rar' self.compressor = 'rar'
elif origFileName.endswith('.7z') or origFileName.endswith('.cb7'): elif is_7zfile(origFileName):
self.compressor = '7z' self.compressor = '7z'
else: else:
self.compressor = None self.compressor = None
@@ -104,7 +101,7 @@ class CBxArchive:
for f in os.listdir(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 directory names contain UTF-8 chars shutil.move can't clean up the mess alone
if os.path.isdir(os.path.join(targetdir, f)): if os.path.isdir(os.path.join(targetdir, f)):
os.replace(os.path.join(targetdir, adir[0], f), os.path.join(targetdir, adir[0], f + '-A')) saferReplace(os.path.join(targetdir, adir[0], f), os.path.join(targetdir, adir[0], f + '-A'))
f += '-A' f += '-A'
move(os.path.join(targetdir, adir[0], f), targetdir) move(os.path.join(targetdir, adir[0], f), targetdir)
os.rmdir(os.path.join(targetdir, adir[0])) os.rmdir(os.path.join(targetdir, adir[0]))

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -18,55 +18,64 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
__version__ = '4.3'
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import os import os
import sys import sys
from copy import copy
from glob import glob
from json import loads from json import loads
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from re import split, sub, compile from re import sub
from stat import S_IWRITE, S_IREAD, S_IEXEC from stat import S_IWRITE, S_IREAD, S_IEXEC
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from tempfile import mkdtemp from tempfile import mkdtemp
from shutil import move, copytree, rmtree from shutil import move, copytree, rmtree
from optparse import OptionParser, OptionGroup from optparse import OptionParser, OptionGroup
from multiprocessing import Pool from multiprocessing import Pool
from xml.dom.minidom import parse
from uuid import uuid4 from uuid import uuid4
from slugify import slugify as slugifyExt from slugify import slugify as slugifyExt
from PIL import Image from PIL import Image
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from psutil import Popen, virtual_memory from psutil import Popen, virtual_memory
from scandir import walk
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
QtCore = None QtCore = None
from .shared import md5Checksum, getImageFileName, walkLevel from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, saferReplace
from . import comic2panel from . import comic2panel
from . import image from . import image
from . import cbxarchive from . import cbxarchive
from . import pdfjpgextract from . import pdfjpgextract
from . import dualmetafix from . import dualmetafix
from . import metadata
from . import __version__
def main(argv=None): def main(argv=None):
global options global options
parser = makeParser() parser = makeParser()
options, args = parser.parse_args(argv) optionstemplate, args = parser.parse_args(argv)
checkOptions() if len(args) == 0:
if len(args) != 1:
parser.print_help() parser.print_help()
return return
outputPath = makeBook(args[0]) sources = set([source for arg in args for source in glob(arg)])
outputPath = []
if len(sources) == 0:
print('No matching files found.')
return
for source in sources:
options = copy(optionstemplate)
checkOptions()
if len(sources) > 1:
print('\nWorking on ' + source)
outputPath = makeBook(source)
return outputPath return outputPath
def buildHTML(path, imgfile, imgfilepath): def buildHTML(path, imgfile, imgfilepath, forcePV=False):
imgfilepath = md5Checksum(imgfilepath) imgfilepath = md5Checksum(imgfilepath)
filename = getImageFileName(imgfile) filename = getImageFileName(imgfile)
additionalStyle = ''
if options.imgproc: if options.imgproc:
if "Rotated" in options.imgIndex[imgfilepath]: if "Rotated" in options.imgIndex[imgfilepath]:
rotatedPage = True rotatedPage = True
@@ -84,11 +93,17 @@ def buildHTML(path, imgfile, imgfilepath):
noVerticalPV = True noVerticalPV = True
else: else:
noVerticalPV = False noVerticalPV = False
if "BlackFill" in options.imgIndex[imgfilepath]:
additionalStyle = ' style="background-color:#000000" '
else: else:
rotatedPage = False rotatedPage = False
noPV = False noPV = False
noHorizontalPV = False noHorizontalPV = False
noVerticalPV = False noVerticalPV = False
if forcePV and noPV:
noPV = False
noHorizontalPV = True
noVerticalPV = True
htmlpath = '' htmlpath = ''
postfix = '' postfix = ''
backref = 1 backref = 1
@@ -113,12 +128,13 @@ def buildHTML(path, imgfile, imgfilepath):
"<link href=\"", "../" * (backref - 1), "<link href=\"", "../" * (backref - 1),
"style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n", "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"</head>\n", "</head>\n",
"<body>\n", "<body" + additionalStyle + ">\n",
"<div class=\"fs\">\n", "<div class=\"fs\">\n",
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"", "<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
imgfile, "\" class=\"singlePage\"/></div>\n" imgfile, "\" class=\"singlePage\"/></div>\n"
]) ])
if options.panelview and not noPV: if (options.panelview or forcePV) and not noPV:
options.panelviewused = True
if not noHorizontalPV and not noVerticalPV: if not noHorizontalPV and not noVerticalPV:
if rotatedPage: if rotatedPage:
if options.righttoleft: if options.righttoleft:
@@ -156,8 +172,8 @@ def buildHTML(path, imgfile, imgfilepath):
f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=", f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=",
"'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]), "'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]),
"}'></a></div>\n"]) "}'></a></div>\n"])
if options.quality == 2: if options.quality == 2 and not forcePV:
imgfilepv = str.split(imgfile, ".") imgfilepv = imgfile.split(".")
imgfilepv[0] += "-hq" imgfilepv[0] += "-hq"
imgfilepv = ".".join(imgfilepv) imgfilepv = ".".join(imgfilepv)
else: else:
@@ -204,10 +220,14 @@ def buildNCX(dstdir, title, chapters, chapterNames):
]) ])
for chapter in chapters: for chapter in chapters:
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
if os.path.basename(folder) != "Text":
title = chapterNames[os.path.basename(folder)]
filename = getImageFileName(os.path.join(folder, chapter[1])) filename = getImageFileName(os.path.join(folder, chapter[1]))
f.write("<navPoint id=\"" + folder.replace('/', '_').replace('\\', '_') + "\"><navLabel><text>" navID = folder.replace('/', '_').replace('\\', '_')
if options.chapters:
title = chapterNames[chapter[1]]
navID = filename[0].replace('/', '_').replace('\\', '_')
elif os.path.basename(folder) != "Text":
title = chapterNames[os.path.basename(folder)]
f.write("<navPoint id=\"" + navID + "\"><navLabel><text>"
+ title + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/") + title + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/")
+ ".html\"/></navPoint>\n") + ".html\"/></navPoint>\n")
f.write("</navMap>\n</ncx>") f.write("</navMap>\n</ncx>")
@@ -293,6 +313,7 @@ def buildEPUB(path, chapterNames, tomeNumber):
filelist = [] filelist = []
chapterlist = [] chapterlist = []
cover = None cover = None
lastfile = None
_, deviceres, _, _, panelviewsize = options.profileData _, deviceres, _, _, panelviewsize = options.profileData
os.mkdir(os.path.join(path, 'OEBPS', 'Text')) os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w', encoding='UTF-8') f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w', encoding='UTF-8')
@@ -405,12 +426,14 @@ def buildEPUB(path, chapterNames, tomeNumber):
"}", "}",
]) ])
f.close() f.close()
for (dirpath, dirnames, filenames) in os.walk(os.path.join(path, 'OEBPS', 'Images')): for (dirpath, dirnames, filenames) in walk(os.path.join(path, 'OEBPS', 'Images')):
chapter = False chapter = False
dirnames, filenames = walkSort(dirnames, filenames)
for afile in filenames: for afile in filenames:
filename = getImageFileName(afile) filename = getImageFileName(afile)
if not '-kcc-hq' in filename[0]: if '-kcc-hq' not in filename[0]:
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile))) filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
lastfile = (dirpath, afile, os.path.join(dirpath, afile))
if not chapter: if not chapter:
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1])) chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
chapter = True chapter = True
@@ -418,32 +441,40 @@ def buildEPUB(path, chapterNames, tomeNumber):
cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'), cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
'cover' + getImageFileName(filelist[-1][1])[1]) 'cover' + getImageFileName(filelist[-1][1])[1])
image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options, tomeNumber) image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options, tomeNumber)
# Hack that force Panel View on at last one page
if lastfile and not options.panelviewused and 'Ko' not in options.profile \
and options.profile not in ['K1', 'K2', 'KDX', 'OTHER']:
filelist[-1] = buildHTML(lastfile[0], lastfile[1], lastfile[2], True)
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
if not chapterNames and options.chapters:
chapterlist = []
globaldiff = 0
for aChapter in options.chapters:
pageid = aChapter[0]
for x in range(0, pageid + globaldiff + 1):
if '-aaa-kcc' in filelist[x][1]:
pageid += 1
if '-bbb-kcc' in filelist[pageid][1]:
pageid -= 1
filename = filelist[pageid][1]
chapterlist.append((filelist[pageid][0].replace('Images', 'Text'), filename))
chapterNames[filename] = aChapter[1]
globaldiff = pageid - (aChapter[0] + globaldiff)
buildNCX(path, options.title, chapterlist, chapterNames) buildNCX(path, options.title, chapterlist, chapterNames)
# Ensure we're sorting files alphabetically
convert = lambda text: int(text) if text.isdigit() else text
alphanum_key = lambda key: [convert(c) for c in split('([0-9]+)', key)]
filelist.sort(key=lambda name: (alphanum_key(name[0].lower()), alphanum_key(name[1].lower())))
buildOPF(path, options.title, filelist, cover) buildOPF(path, options.title, filelist, cover)
def imgOptimization(img, opt, hqImage=None): def imgOptimization(img, opt):
if not img.fill: if not img.fill:
img.getImageFill() img.getImageFill()
if not opt.webtoon: if not opt.webtoon:
img.cropWhiteSpace() img.cropWhiteSpace()
if opt.cutpagenumbers and not opt.webtoon: if opt.cutpagenumbers and not opt.webtoon:
img.cutPageNumber() img.cutPageNumber()
img.optimizeImage(opt.gamma) img.autocontrastImage()
if hqImage: img.resizeImage()
img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, 0) if not img.second and opt.panelview:
img.calculateBorder(hqImage, True) img.calculateBorder()
else:
img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, opt.quality)
if opt.panelview:
if opt.quality == 0:
img.calculateBorder(img)
elif opt.quality == 1:
img.calculateBorder(img, True)
if opt.forcepng and not opt.forcecolor: if opt.forcepng and not opt.forcecolor:
img.quantizeImage() img.quantizeImage()
@@ -456,7 +487,7 @@ def imgDirectoryProcessing(path):
options.imgPurgeIndex = [] options.imgPurgeIndex = []
work = [] work = []
pagenumber = 0 pagenumber = 0
for (dirpath, dirnames, filenames) in os.walk(path): for (dirpath, dirnames, filenames) in walk(path):
for afile in filenames: for afile in filenames:
pagenumber += 1 pagenumber += 1
work.append([afile, dirpath, options]) work.append([afile, dirpath, options])
@@ -504,48 +535,38 @@ def imgFileProcessing(work):
dirpath = work[1] dirpath = work[1]
opt = work[2] opt = work[2]
output = [] output = []
img = image.ComicPage(os.path.join(dirpath, afile), opt.profileData) img = image.ComicPage(os.path.join(dirpath, afile), opt)
if opt.quality == 2:
wipe = False
else:
wipe = True
if opt.nosplitrotate: if opt.nosplitrotate:
splitter = None splitter = None
else: else:
splitter = img.splitPage(dirpath, opt.righttoleft, opt.rotate) splitter = img.splitPage(dirpath)
if splitter is not None: if splitter is not None:
img0 = image.ComicPage(splitter[0], opt.profileData) img0 = image.ComicPage(splitter[0], opt)
imgOptimization(img0, opt) imgOptimization(img0, opt)
output.append(img0.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) if not img0.noHQ:
img1 = image.ComicPage(splitter[1], opt.profileData) output.append(img0.saveToDir(dirpath))
img1 = image.ComicPage(splitter[1], opt)
imgOptimization(img1, opt) imgOptimization(img1, opt)
output.append(img1.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) if not img1.noHQ:
if wipe: output.append(img1.saveToDir(dirpath))
output.append(img0.origFileName) output.extend([img.origFileName, img0.origFileName, img1.origFileName])
output.append(img1.origFileName)
if opt.quality == 2: if opt.quality == 2:
img0b = image.ComicPage(splitter[0], opt.profileData, img0.fill) output.extend([img0.origFileName, img1.origFileName])
imgOptimization(img0b, opt, img0) img0b = image.ComicPage(splitter[0], opt, img0)
output.append(img0b.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) imgOptimization(img0b, opt)
img1b = image.ComicPage(splitter[1], opt.profileData, img1.fill) output.append(img0b.saveToDir(dirpath))
imgOptimization(img1b, opt, img1) img1b = image.ComicPage(splitter[1], opt, img1)
output.append(img1b.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) imgOptimization(img1b, opt)
output.append(img0.origFileName) output.append(img1b.saveToDir(dirpath))
output.append(img1.origFileName)
output.append(img.origFileName)
else: else:
output.append(img.origFileName)
imgOptimization(img, opt) imgOptimization(img, opt)
output.append(img.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) if not img.noHQ:
if wipe: output.append(img.saveToDir(dirpath))
output.append(img.origFileName)
if opt.quality == 2: if opt.quality == 2:
img2 = image.ComicPage(os.path.join(dirpath, afile), opt.profileData, img.fill) img2 = image.ComicPage(os.path.join(dirpath, afile), opt, img)
if img.rotated: imgOptimization(img2, opt)
img2.image = img2.image.rotate(90) output.append(img2.saveToDir(dirpath))
img2.rotated = True
imgOptimization(img2, opt, img)
output.append(img2.saveToDir(dirpath, opt.forcepng, opt.forcecolor))
output.append(img.origFileName)
return output return output
except Exception: except Exception:
return str(sys.exc_info()[1]) return str(sys.exc_info()[1])
@@ -557,7 +578,7 @@ def getWorkFolder(afile):
if os.path.isdir(afile): if os.path.isdir(afile):
workdir = mkdtemp('', 'KCC-TMP-') workdir = mkdtemp('', 'KCC-TMP-')
try: try:
os.rmdir(workdir) # needed for copytree() fails if dst already exists os.rmdir(workdir)
fullPath = os.path.join(workdir, 'OEBPS', 'Images') fullPath = os.path.join(workdir, 'OEBPS', 'Images')
if len(fullPath) > 240: if len(fullPath) > 240:
raise UserWarning("Path is too long.") raise UserWarning("Path is too long.")
@@ -622,6 +643,7 @@ def getComicInfo(path, originalPath):
xmlPath = os.path.join(path, 'ComicInfo.xml') xmlPath = os.path.join(path, 'ComicInfo.xml')
options.authors = ['KCC'] options.authors = ['KCC']
options.remoteCovers = {} options.remoteCovers = {}
options.chapters = []
titleSuffix = '' titleSuffix = ''
if options.title == 'defaulttitle': if options.title == 'defaulttitle':
defaultTitle = True defaultTitle = True
@@ -633,56 +655,43 @@ def getComicInfo(path, originalPath):
defaultTitle = False defaultTitle = False
if os.path.exists(xmlPath): if os.path.exists(xmlPath):
try: try:
xml = parse(xmlPath) xml = metadata.MetadataParser(xmlPath)
except Exception: except Exception:
os.remove(xmlPath) os.remove(xmlPath)
return return
options.authors = [] options.authors = []
if defaultTitle: if defaultTitle:
if len(xml.getElementsByTagName('Series')) != 0: if xml.data['Series']:
options.title = xml.getElementsByTagName('Series')[0].firstChild.nodeValue options.title = xml.data['Series']
if len(xml.getElementsByTagName('Volume')) != 0: if xml.data['Volume']:
titleSuffix += ' V' + xml.getElementsByTagName('Volume')[0].firstChild.nodeValue titleSuffix += ' V' + xml.data['Volume']
if len(xml.getElementsByTagName('Number')) != 0: if xml.data['Number']:
titleSuffix += ' #' + xml.getElementsByTagName('Number')[0].firstChild.nodeValue titleSuffix += ' #' + xml.data['Number']
options.title += titleSuffix options.title += titleSuffix
if len(xml.getElementsByTagName('Writer')) != 0: for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']:
authorsTemp = str.split(xml.getElementsByTagName('Writer')[0].firstChild.nodeValue, ', ') for person in xml.data[field]:
for author in authorsTemp: options.authors.append(person)
options.authors.append(author)
if len(xml.getElementsByTagName('Penciller')) != 0:
authorsTemp = str.split(xml.getElementsByTagName('Penciller')[0].firstChild.nodeValue, ', ')
for author in authorsTemp:
options.authors.append(author)
if len(xml.getElementsByTagName('Inker')) != 0:
authorsTemp = str.split(xml.getElementsByTagName('Inker')[0].firstChild.nodeValue, ', ')
for author in authorsTemp:
options.authors.append(author)
if len(xml.getElementsByTagName('Colorist')) != 0:
authorsTemp = str.split(xml.getElementsByTagName('Colorist')[0].firstChild.nodeValue, ', ')
for author in authorsTemp:
options.authors.append(author)
if len(options.authors) > 0: if len(options.authors) > 0:
options.authors = list(set(options.authors)) options.authors = list(set(options.authors))
options.authors.sort() options.authors.sort()
else: else:
options.authors = ['KCC'] options.authors = ['KCC']
if len(xml.getElementsByTagName('ScanInformation')) != 0: if xml.data['MUid']:
coverId = xml.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue options.remoteCovers = getCoversFromMCB(xml.data['MUid'])
coverId = compile('(MCD\\()(\\d+)(\\))').search(coverId) if xml.data['Bookmarks']:
if coverId: options.chapters = xml.data['Bookmarks']
options.remoteCovers = getCoversFromMCB(coverId.group(2))
os.remove(xmlPath) os.remove(xmlPath)
def getCoversFromMCB(mangaID): def getCoversFromMCB(mangaID):
covers = {} covers = {}
try: try:
jsonRaw = urlopen(Request('http://manga.joentjuh.nl/json/series/' + mangaID + '/', jsonRaw = urlopen(Request('http://mcd.iosphe.re/api/v1/series/' + mangaID + '/',
headers={'User-Agent': 'KindleComicConverter/' + __version__})) headers={'User-Agent': 'KindleComicConverter/' + __version__}))
jsonData = loads(jsonRaw.readall().decode('utf-8')) jsonData = loads(jsonRaw.readall().decode('utf-8'))
for volume in jsonData['volumes']: for volume in jsonData['Covers']['a']:
covers[int(volume['volume'])] = volume['releases'][0]['files']['front']['url'] if volume['Side'] == 'front':
covers[int(volume['Volume'])] = volume['Raw']
except Exception: except Exception:
return {} return {}
return covers return covers
@@ -690,7 +699,7 @@ def getCoversFromMCB(mangaID):
def getDirectorySize(start_path='.'): def getDirectorySize(start_path='.'):
total_size = 0 total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path): for dirpath, dirnames, filenames in walk(start_path):
for f in filenames: for f in filenames:
fp = os.path.join(dirpath, f) fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp) total_size += os.path.getsize(fp)
@@ -699,7 +708,7 @@ def getDirectorySize(start_path='.'):
def sanitizeTree(filetree): def sanitizeTree(filetree):
chapterNames = {} chapterNames = {}
for root, dirs, files in os.walk(filetree, False): for root, dirs, files in walk(filetree, False):
for name in files: for name in files:
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
slugified = slugify(splitname[0]) slugified = slugify(splitname[0])
@@ -709,7 +718,7 @@ def sanitizeTree(filetree):
newKey = os.path.join(root, slugified + splitname[1]) newKey = os.path.join(root, slugified + splitname[1])
key = os.path.join(root, name) key = os.path.join(root, name)
if key != newKey: if key != newKey:
os.replace(key, newKey) saferReplace(key, newKey)
for name in dirs: for name in dirs:
tmpName = name tmpName = name
slugified = slugify(name) slugified = slugify(name)
@@ -719,15 +728,14 @@ def sanitizeTree(filetree):
newKey = os.path.join(root, slugified) newKey = os.path.join(root, slugified)
key = os.path.join(root, name) key = os.path.join(root, name)
if key != newKey: if key != newKey:
os.replace(key, newKey) saferReplace(key, newKey)
return chapterNames return chapterNames
def sanitizeTreeKobo(filetree): def sanitizeTreeKobo(filetree):
pageNumber = 0 pageNumber = 0
for root, dirs, files in os.walk(filetree): for root, dirs, files in walk(filetree):
files.sort() dirs, files = walkSort(dirs, files)
dirs.sort()
for name in files: for name in files:
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
slugified = str(pageNumber).zfill(5) slugified = str(pageNumber).zfill(5)
@@ -738,11 +746,11 @@ def sanitizeTreeKobo(filetree):
newKey = os.path.join(root, slugified + splitname[1]) newKey = os.path.join(root, slugified + splitname[1])
key = os.path.join(root, name) key = os.path.join(root, name)
if key != newKey: if key != newKey:
os.replace(key, newKey) saferReplace(key, newKey)
def sanitizePermissions(filetree): def sanitizePermissions(filetree):
for root, dirs, files in os.walk(filetree, False): for root, dirs, files in walk(filetree, False):
for name in files: for name in files:
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD) os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
for name in dirs: for name in dirs:
@@ -812,11 +820,15 @@ def splitProcess(path, mode):
output = [] output = []
currentSize = 0 currentSize = 0
currentTarget = path currentTarget = path
if options.webtoon:
targetSize = 104857600
else:
targetSize = 419430400
if mode == 0: if mode == 0:
for root, dirs, files in walkLevel(path, 0): for root, dirs, files in walkLevel(path, 0):
for name in files: for name in files:
size = os.path.getsize(os.path.join(root, name)) size = os.path.getsize(os.path.join(root, name))
if currentSize + size > 419430400: if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome() currentTarget, pathRoot = createNewTome()
output.append(pathRoot) output.append(pathRoot)
currentSize = size currentSize = size
@@ -828,7 +840,7 @@ def splitProcess(path, mode):
for root, dirs, files in walkLevel(path, 0): for root, dirs, files in walkLevel(path, 0):
for name in dirs: for name in dirs:
size = getDirectorySize(os.path.join(root, name)) size = getDirectorySize(os.path.join(root, name))
if currentSize + size > 419430400: if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome() currentTarget, pathRoot = createNewTome()
output.append(pathRoot) output.append(pathRoot)
currentSize = size currentSize = size
@@ -842,7 +854,7 @@ def splitProcess(path, mode):
for name in dirs: for name in dirs:
size = getDirectorySize(os.path.join(root, name)) size = getDirectorySize(os.path.join(root, name))
currentSize = 0 currentSize = 0
if size > 419430400: if size > targetSize:
if not firstTome: if not firstTome:
currentTarget, pathRoot = createNewTome() currentTarget, pathRoot = createNewTome()
output.append(pathRoot) output.append(pathRoot)
@@ -851,7 +863,7 @@ def splitProcess(path, mode):
for rootInside, dirsInside, filesInside in walkLevel(os.path.join(root, name), 0): for rootInside, dirsInside, filesInside in walkLevel(os.path.join(root, name), 0):
for nameInside in dirsInside: for nameInside in dirsInside:
size = getDirectorySize(os.path.join(rootInside, nameInside)) size = getDirectorySize(os.path.join(rootInside, nameInside))
if currentSize + size > 419430400: if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome() currentTarget, pathRoot = createNewTome()
output.append(pathRoot) output.append(pathRoot)
currentSize = size currentSize = size
@@ -870,7 +882,7 @@ def splitProcess(path, mode):
def detectCorruption(tmpPath, orgPath): def detectCorruption(tmpPath, orgPath):
for root, dirs, files in os.walk(tmpPath, False): for root, dirs, files in walk(tmpPath, False):
for name in files: for name in files:
if getImageFileName(name) is not None: if getImageFileName(name) is not None:
path = os.path.join(root, name) path = os.path.join(root, name)
@@ -899,20 +911,20 @@ def detectMargins(path):
yu = flag[2] yu = flag[2]
xr = flag[3] xr = flag[3]
yd = flag[4] yd = flag[4]
if xl != "0": if xl != "0.0":
xl = "-" + str(float(xl)/100) + "%" xl = "-" + xl + "%"
else: else:
xl = "0%" xl = "0%"
if xr != "0": if xr != "0.0":
xr = "-" + str(float(xr)/100) + "%" xr = "-" + xr + "%"
else: else:
xr = "0%" xr = "0%"
if yu != "0": if yu != "0.0":
yu = "-" + str(float(yu)/100) + "%" yu = "-" + yu + "%"
else: else:
yu = "0%" yu = "0%"
if yd != "0": if yd != "0.0":
yd = "-" + str(float(yd)/100) + "%" yd = "-" + yd + "%"
else: else:
yd = "0%" yd = "0%"
return xl, yu, xr, yd return xl, yu, xr, yd
@@ -937,7 +949,7 @@ def makeZIP(zipFilename, baseDir, isEPUB=False):
zipOutput = ZipFile(zipFilename, 'w', ZIP_DEFLATED) zipOutput = ZipFile(zipFilename, 'w', ZIP_DEFLATED)
if isEPUB: if isEPUB:
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED) zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
for dirpath, dirnames, filenames in os.walk(baseDir): for dirpath, dirnames, filenames in walk(baseDir):
for name in filenames: for name in filenames:
path = os.path.normpath(os.path.join(dirpath, name)) path = os.path.normpath(os.path.join(dirpath, name))
aPath = os.path.normpath(os.path.join(dirpath.replace(baseDir, ''), name)) aPath = os.path.normpath(os.path.join(dirpath.replace(baseDir, ''), name))
@@ -1018,6 +1030,7 @@ def makeParser():
def checkOptions(): def checkOptions():
global options global options
options.panelview = True options.panelview = True
options.panelviewused = False
options.bordersColor = None options.bordersColor = None
if options.format == 'Auto': if options.format == 'Auto':
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'KFHD', 'KFHDX', 'KFHDX8', 'KFA']: if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'KFHD', 'KFHDX', 'KFHDX8', 'KFA']:
@@ -1115,10 +1128,7 @@ def makeBook(source, qtGUI=None):
getComicInfo(os.path.join(path, "OEBPS", "Images"), source) getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
detectCorruption(os.path.join(path, "OEBPS", "Images"), source) detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
if options.webtoon: if options.webtoon:
if options.customheight > 0: comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', '-m', path], qtGUI)
comic2panel.main(['-y ' + str(options.customheight), '-i', '-m', path], qtGUI)
else:
comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', '-m', path], qtGUI)
if options.imgproc: if options.imgproc:
print("\nProcessing images...") print("\nProcessing images...")
if GUI: if GUI:
@@ -1144,7 +1154,10 @@ def makeBook(source, qtGUI=None):
GUI.progressBarTick.emit('tick') GUI.progressBarTick.emit('tick')
options.baseTitle = options.title options.baseTitle = options.title
for tome in tomes: for tome in tomes:
if len(tomes) > 1: if len(tomes) > 9:
tomeNumber += 1
options.title = options.baseTitle + ' [' + str(tomeNumber).zfill(2) + '/' + str(len(tomes)).zfill(2) + ']'
elif len(tomes) > 1:
tomeNumber += 1 tomeNumber += 1
options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']' options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']'
if options.format == 'CBZ': if options.format == 'CBZ':
@@ -1254,4 +1267,4 @@ def makeMOBI(work, qtGUI=None):
makeMOBIWorkerPool.apply_async(func=makeMOBIWorker, args=(i, ), callback=makeMOBIWorkerTick) makeMOBIWorkerPool.apply_async(func=makeMOBIWorker, args=(i, ), callback=makeMOBIWorkerTick)
makeMOBIWorkerPool.close() makeMOBIWorkerPool.close()
makeMOBIWorkerPool.join() makeMOBIWorkerPool.join()
return makeMOBIWorkerOutput return makeMOBIWorkerOutput

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -18,18 +18,14 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
__version__ = '4.3'
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import os import os
import sys import sys
from shutil import rmtree, copytree, move from shutil import rmtree, copytree, move
from optparse import OptionParser, OptionGroup from optparse import OptionParser, OptionGroup
from multiprocessing import Pool from multiprocessing import Pool
from PIL import Image, ImageStat from PIL import Image, ImageStat, ImageOps
from .shared import getImageFileName, walkLevel from scandir import walk
from .shared import getImageFileName, walkLevel, walkSort
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
@@ -50,9 +46,9 @@ def mergeDirectory(work):
try: try:
directory = work[0] directory = work[0]
images = [] images = []
imagesClear = [] imagesValid = []
sizes = [] sizes = []
h = 0 targetHeight = 0
for root, dirs, files in walkLevel(directory, 0): for root, dirs, files in walkLevel(directory, 0):
for name in files: for name in files:
if getImageFileName(name) is not None: if getImageFileName(name) is not None:
@@ -60,23 +56,26 @@ def mergeDirectory(work):
images.append([os.path.join(root, name), i.size[0], i.size[1]]) images.append([os.path.join(root, name), i.size[0], i.size[1]])
sizes.append(i.size[0]) sizes.append(i.size[0])
if len(images) > 0: if len(images) > 0:
mw = max(set(sizes), key=sizes.count) targetWidth = max(set(sizes), key=sizes.count)
for i in images: for i in images:
if i[1] == mw: if i[1] <= targetWidth:
h += i[2] targetHeight += i[2]
imagesClear.append(i[0]) imagesValid.append(i[0])
# Silently drop directories that contain too many images # Silently drop directories that contain too many images
if h > 262144: # 131072 = GIMP_MAX_IMAGE_SIZE / 4
if targetHeight > 131072:
return None return None
result = Image.new('RGB', (mw, h)) result = Image.new('RGB', (targetWidth, targetHeight))
y = 0 y = 0
for i in imagesClear: for i in imagesValid:
img = Image.open(i) img = Image.open(i)
img = img.convert('RGB') img = img.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)) result.paste(img, (0, y))
y += img.size[1] y += img.size[1]
os.remove(i) os.remove(i)
savePath = os.path.split(imagesClear[0]) savePath = os.path.split(imagesValid[0])
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG') result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
except Exception: except Exception:
return str(sys.exc_info()[1]) return str(sys.exc_info()[1])
@@ -84,7 +83,7 @@ def mergeDirectory(work):
def sanitizePanelSize(panel, opt): def sanitizePanelSize(panel, opt):
newPanels = [] newPanels = []
if panel[2] > 8 * opt.height: if panel[2] > 6 * opt.height:
diff = int(panel[2] / 8) diff = int(panel[2] / 8)
newPanels.append([panel[0], panel[1] - diff*7, diff]) newPanels.append([panel[0], panel[1] - diff*7, diff])
newPanels.append([panel[1] - diff*7, panel[1] - diff*6, diff]) newPanels.append([panel[1] - diff*7, panel[1] - diff*6, diff])
@@ -94,13 +93,13 @@ def sanitizePanelSize(panel, opt):
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, 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*2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff]) newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 4 * opt.height: elif panel[2] > 3 * opt.height:
diff = int(panel[2] / 4) diff = int(panel[2] / 4)
newPanels.append([panel[0], panel[1] - diff*3, diff]) newPanels.append([panel[0], panel[1] - diff*3, diff])
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff]) newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff])
newPanels.append([panel[1] - diff*2, panel[1] - diff, diff]) newPanels.append([panel[1] - diff*2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff]) newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 2 * opt.height: elif panel[2] > 1.5 * opt.height:
newPanels.append([panel[0], panel[1] - int(panel[2] / 2), int(panel[2] / 2)]) 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)]) newPanels.append([panel[1] - int(panel[2] / 2), panel[1], int(panel[2] / 2)])
else: else:
@@ -199,8 +198,7 @@ def splitImage(work):
panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]]) panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]])
newPage.paste(panelImg, (0, targetHeight)) newPage.paste(panelImg, (0, targetHeight))
targetHeight += panels[panel][2] targetHeight += panels[panel][2]
newPage.save(os.path.join(path, fileExpanded[0] + '-' + newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG')
str(pageNumber) + '.png'), 'PNG')
pageNumber += 1 pageNumber += 1
os.remove(filePath) os.remove(filePath)
except Exception: except Exception:
@@ -249,7 +247,8 @@ def main(argv=None, qtGUI=None):
mergeWorkerOutput = [] mergeWorkerOutput = []
mergeWorkerPool = Pool() mergeWorkerPool = Pool()
mergeWork.append([options.targetDir]) mergeWork.append([options.targetDir])
for root, dirs, files in os.walk(options.targetDir, False): for root, dirs, files in walk(options.targetDir, False):
dirs, files = walkSort(dirs, files)
for directory in dirs: for directory in dirs:
directoryNumer += 1 directoryNumer += 1
mergeWork.append([os.path.join(root, directory)]) mergeWork.append([os.path.join(root, directory)])
@@ -267,7 +266,7 @@ def main(argv=None, qtGUI=None):
rmtree(options.targetDir, True) rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0]) raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0])
print("\nSplitting images...") print("\nSplitting images...")
for root, dirs, files in os.walk(options.targetDir, False): for root, dirs, files in walk(options.targetDir, False):
for name in files: for name in files:
if getImageFileName(name) is not None: if getImageFileName(name) is not None:
pagenumber += 1 pagenumber += 1

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks # Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks
# Changes for KCC Copyright (C) 2014 Pawel Jastrzebski <pawelj@iosphe.re> # Changes for KCC Copyright (C) 2014-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2010 Alex Yatskov # Copyright (C) 2010 Alex Yatskov
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com> # Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@@ -16,11 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
__version__ = '4.3'
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import os import os
from io import BytesIO from io import BytesIO
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
@@ -28,6 +23,7 @@ from urllib.parse import quote
from functools import reduce from functools import reduce
from PIL import Image, ImageOps, ImageStat, ImageChops from PIL import Image, ImageOps, ImageStat, ImageChops
from .shared import md5Checksum from .shared import md5Checksum
from . import __version__
class ProfileData: class ProfileData:
@@ -87,7 +83,7 @@ class ProfileData:
'K345': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)), 'K345': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)), 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)),
'KPW': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)), 'KPW': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)),
'KV': ("Kindle Voyage", (1080, 1440), Palette16, 1.8, (1620, 2160)), 'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.8, (1608, 2172)),
'KFHD': ("K. Fire HD", (800, 1280), PalleteNull, 1.0, (1200, 1920)), 'KFHD': ("K. Fire HD", (800, 1280), PalleteNull, 1.0, (1200, 1920)),
'KFHDX': ("K. Fire HDX", (1200, 1920), PalleteNull, 1.0, (1800, 2880)), 'KFHDX': ("K. Fire HDX", (1200, 1920), PalleteNull, 1.0, (1800, 2880)),
'KFHDX8': ("K. Fire HDX 8.9", (1600, 2560), PalleteNull, 1.0, (2400, 3840)), 'KFHDX8': ("K. Fire HDX 8.9", (1600, 2560), PalleteNull, 1.0, (2400, 3840)),
@@ -102,63 +98,77 @@ class ProfileData:
class ComicPage: class ComicPage:
def __init__(self, source, device, fill=None): def __init__(self, source, options, original=None):
try: try:
self.profile_label, self.size, self.palette, self.gamma, self.panelviewsize = device self.profile_label, self.size, self.palette, self.gamma, self.panelviewsize = options.profileData
except KeyError: except KeyError:
raise RuntimeError('Unexpected output device %s' % device) raise RuntimeError('Unexpected output device %s' % options.profileData)
self.origFileName = source self.origFileName = source
self.filename = os.path.basename(self.origFileName) self.filename = os.path.basename(self.origFileName)
self.image = Image.open(source) self.image = Image.open(source)
self.image = self.image.convert('RGB') self.image = self.image.convert('RGB')
self.color = self.isImageColor() self.opt = options
self.rotated = None if original:
self.border = None self.second = True
self.noHPV = None self.rotated = original.rotated
self.noVPV = None self.border = original.border
self.noPV = None self.noHPV = original.noHPV
self.purge = False self.noVPV = original.noVPV
self.hq = False self.noPV = original.noPV
if fill: self.noHQ = original.noHQ
self.fill = fill self.fill = original.fill
self.color = original.color
if self.rotated:
self.image = self.image.rotate(90, Image.BICUBIC, True)
self.opt.quality = 0
else: else:
self.second = False
self.rotated = None
self.border = None
self.noHPV = None
self.noVPV = None
self.noPV = None
self.fill = None self.fill = None
self.noHQ = False
def saveToDir(self, targetdir, forcepng, color): if options.webtoon:
try: self.color = True
if not self.purge:
flags = []
filename = os.path.join(targetdir, os.path.splitext(self.filename)[0]) + '-KCC'
if not color and not forcepng:
self.image = self.image.convert('L')
if self.rotated:
flags.append('Rotated')
if self.hq:
flags.append('HighQuality')
filename += '-HQ'
if self.noPV:
flags.append('NoPanelView')
else:
if self.noHPV:
flags.append('NoHorizontalPanelView')
if self.noVPV:
flags.append('NoVerticalPanelView')
if self.border:
flags.append("Margins-" + str(self.border[0]) + "-" + str(self.border[1]) + "-"
+ str(self.border[2]) + "-" + str(self.border[3]))
if forcepng:
filename += ".png"
self.image.save(filename, "PNG", optimize=1)
else:
filename += ".jpg"
self.image.save(filename, "JPEG", optimize=1)
return [md5Checksum(filename), flags]
else: else:
return None self.color = self.isImageColor()
def saveToDir(self, targetdir):
try:
flags = []
filename = os.path.join(targetdir, os.path.splitext(self.filename)[0]) + '-KCC'
if not self.opt.forcecolor and not self.opt.forcepng:
self.image = self.image.convert('L')
if self.rotated:
flags.append('Rotated')
if self.noPV:
flags.append('NoPanelView')
else:
if self.noHPV:
flags.append('NoHorizontalPanelView')
if self.noVPV:
flags.append('NoVerticalPanelView')
if self.border:
flags.append('Margins-' + str(self.border[0]) + '-' + str(self.border[1]) + '-'
+ str(self.border[2]) + '-' + str(self.border[3]))
if self.fill != 'white':
flags.append('BlackFill')
if self.opt.quality == 2:
filename += '-HQ'
if self.opt.forcepng:
filename += '.png'
self.image.save(filename, 'PNG', optimize=1)
else:
filename += '.jpg'
self.image.save(filename, 'JPEG', optimize=1, quality=80)
return [md5Checksum(filename), flags]
except IOError as e: except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e)) raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
def optimizeImage(self, gamma): def autocontrastImage(self):
gamma = self.opt.gamma
if gamma < 0.1: if gamma < 0.1:
gamma = self.gamma gamma = self.gamma
if self.gamma != 1.0 and self.color: if self.gamma != 1.0 and self.color:
@@ -179,75 +189,65 @@ class ComicPage:
# Quantize is deprecated but new function call it internally anyway... # Quantize is deprecated but new function call it internally anyway...
self.image = self.image.quantize(palette=palImg) self.image = self.image.quantize(palette=palImg)
def calculateBorderPercent(self, x, img, isWidth): def calculateBorder(self):
if isWidth: if self.noPV:
return int(round(float(x)/float(img.image.size[0]), 4) * 10000 * 1.5) self.border = [0.0, 0.0, 0.0, 0.0]
else:
return int(round(float(x)/float(img.image.size[1]), 4) * 10000 * 1.5)
def calculateBorder(self, sourceImage, isHQ=False):
if (isHQ and sourceImage.purge) or self.noPV:
self.border = [0, 0, 0, 0]
self.noPV = True
return return
if self.fill == 'white': if self.fill == 'white':
# Only already saved files can have P mode. So we can break color quantization. border = ImageChops.invert(self.image).getbbox()
if sourceImage.image.mode == 'P':
sourceImage.image = sourceImage.image.convert('RGB')
border = ImageChops.invert(sourceImage.image).getbbox()
else: else:
border = sourceImage.image.getbbox() border = self.image.getbbox()
if self.opt.quality == 2:
multiplier = 1.0
else:
multiplier = 1.5
if border is not None: if border is not None:
if isHQ: self.border = [round(float(border[0])/float(self.image.size[0])*150, 3),
multiplier = 1.0 round(float(border[1])/float(self.image.size[1])*150, 3),
else: round(float(self.image.size[0]-border[2])/float(self.image.size[0])*150, 3),
multiplier = 1.5 round(float(self.image.size[1]-border[3])/float(self.image.size[1])*150, 3)]
self.border = [self.calculateBorderPercent(border[0], sourceImage, True), if int((border[2] - border[0]) * multiplier) < self.size[0] + 10:
self.calculateBorderPercent(border[1], sourceImage, False),
self.calculateBorderPercent((sourceImage.image.size[0] - border[2]), sourceImage, True),
self.calculateBorderPercent((sourceImage.image.size[1] - border[3]), sourceImage, False)]
if int((border[2] - border[0]) * multiplier) < self.size[0]:
self.noHPV = True self.noHPV = True
if int((border[3] - border[1]) * multiplier) < self.size[1]: if int((border[3] - border[1]) * multiplier) < self.size[1] + 10:
self.noVPV = True self.noVPV = True
else: else:
self.border = [0, 0, 0, 0] self.border = [0.0, 0.0, 0.0, 0.0]
self.noHPV = True self.noHPV = True
self.noVPV = True self.noVPV = True
def resizeImage(self, upscale=False, stretch=False, bordersColor=None, qualityMode=0): def resizeImage(self):
if bordersColor: if self.opt.bordersColor:
fill = bordersColor fill = self.opt.bordersColor
else: else:
fill = self.fill fill = self.fill
# Set target size # Set target size
if qualityMode == 0: if self.opt.quality == 0:
size = (self.size[0], self.size[1]) size = (self.size[0], self.size[1])
elif qualityMode == 1 and not stretch and not upscale and self.image.size[0] <=\ elif self.opt.quality == 1 and not self.opt.stretch and not self.opt.upscale and self.image.size[0] <=\
self.size[0] and self.image.size[1] <= self.size[1]: self.size[0] and self.image.size[1] <= self.size[1]:
size = (self.size[0], self.size[1]) size = (self.size[0], self.size[1])
elif qualityMode == 1: elif self.opt.quality == 1:
# Forcing upscale to make sure that margins will be not too big # Forcing upscale to make sure that margins will be not too big
if not stretch: if not self.opt.stretch:
upscale = True self.opt.upscale = True
size = (self.panelviewsize[0], self.panelviewsize[1]) size = (self.panelviewsize[0], self.panelviewsize[1])
elif qualityMode == 2 and not stretch and not upscale and self.image.size[0] <=\ elif self.opt.quality == 2 and not self.opt.stretch and not self.opt.upscale and self.image.size[0] <=\
self.size[0] and self.image.size[1] <= self.size[1]: self.size[0] and self.image.size[1] <= self.size[1]:
self.purge = True # HQ version will not be needed
return self.image self.noHQ = True
return
else: else:
self.hq = True
size = (self.panelviewsize[0], self.panelviewsize[1]) size = (self.panelviewsize[0], self.panelviewsize[1])
# If stretching is on - Resize without other considerations # If stretching is on - Resize without other considerations
if stretch: if self.opt.stretch:
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]: if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
method = Image.BICUBIC method = Image.BICUBIC
else: else:
method = Image.ANTIALIAS method = Image.LANCZOS
self.image = self.image.resize(size, method) self.image = self.image.resize(size, method)
return self.image return
# If image is smaller than target resolution and upscale is off - Just expand it by adding margins # If image is smaller than target resolution and upscale is off - Just expand it by adding margins
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not upscale: if self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale:
borderw = int((size[0] - self.image.size[0]) / 2) borderw = int((size[0] - self.image.size[0]) / 2)
borderh = int((size[1] - self.image.size[1]) / 2) borderh = int((size[1] - self.image.size[1]) / 2)
# PV is disabled when source image is smaller than device screen and upscale is off # PV is disabled when source image is smaller than device screen and upscale is off
@@ -257,7 +257,7 @@ class ComicPage:
# Border can't be float so sometimes image might be 1px too small/large # Border can't be float so sometimes image might be 1px too small/large
if self.image.size[0] != size[0] or self.image.size[1] != size[1]: 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)) self.image = ImageOps.fit(self.image, size, method=Image.BICUBIC, centering=(0.5, 0.5))
return self.image return
# Otherwise - Upscale/Downscale # Otherwise - Upscale/Downscale
ratioDev = float(size[0]) / float(size[1]) ratioDev = float(size[0]) / float(size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
@@ -269,17 +269,17 @@ class ComicPage:
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]: if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
method = Image.BICUBIC method = Image.BICUBIC
else: else:
method = Image.ANTIALIAS method = Image.LANCZOS
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5)) self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
return self.image return
def splitPage(self, targetdir, righttoleft=False, rotate=False): def splitPage(self, targetdir):
width, height = self.image.size width, height = self.image.size
dstwidth, dstheight = self.size dstwidth, dstheight = self.size
# Only split if origin is not oriented the same as target # Only split if origin is not oriented the same as target
if (width > height) != (dstwidth > dstheight): if (width > height) != (dstwidth > dstheight):
if rotate: if self.opt.rotate:
self.image = self.image.rotate(90) self.image = self.image.rotate(90, Image.BICUBIC, True)
self.rotated = True self.rotated = True
return None return None
else: else:
@@ -292,18 +292,18 @@ class ComicPage:
# Source is portrait and target is landscape, so split by the height # Source is portrait and target is landscape, so split by the height
leftbox = (0, 0, width, int(height / 2)) leftbox = (0, 0, width, int(height / 2))
rightbox = (0, int(height / 2), width, height) rightbox = (0, int(height / 2), width, height)
filename = os.path.splitext(self.filename) filename = os.path.splitext(self.filename)[0]
fileone = targetdir + '/' + filename[0] + '-A' + filename[1] fileone = targetdir + '/' + filename + '-AAA.png'
filetwo = targetdir + '/' + filename[0] + '-B' + filename[1] filetwo = targetdir + '/' + filename + '-BBB.png'
try: try:
if righttoleft: if self.opt.righttoleft:
pageone = self.image.crop(rightbox) pageone = self.image.crop(rightbox)
pagetwo = self.image.crop(leftbox) pagetwo = self.image.crop(leftbox)
else: else:
pageone = self.image.crop(leftbox) pageone = self.image.crop(leftbox)
pagetwo = self.image.crop(rightbox) pagetwo = self.image.crop(rightbox)
pageone.save(fileone) pageone.save(fileone, 'PNG', optimize=1)
pagetwo.save(filetwo) pagetwo.save(filetwo, 'PNG', optimize=1)
except IOError as e: except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e)) raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
return fileone, filetwo return fileone, filetwo
@@ -364,7 +364,6 @@ class ComicPage:
else: else:
diff = pageNumberCut1 diff = pageNumberCut1
self.image = self.image.crop((0, 0, widthImg, heightImg - diff)) self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
return self.image
def cropWhiteSpace(self): def cropWhiteSpace(self):
if ImageChops.invert(self.image).getbbox() is not None: if ImageChops.invert(self.image).getbbox() is not None:
@@ -400,7 +399,6 @@ class ComicPage:
diff += delta diff += delta
diff -= delta diff -= delta
self.image = self.image.crop((0, 0, widthImg - diff, heightImg)) self.image = self.image.crop((0, 0, widthImg - diff, heightImg))
return self.image
def getImageHistogram(self, image): def getImageHistogram(self, image):
histogram = image.histogram() histogram = image.histogram()
@@ -417,13 +415,16 @@ class ComicPage:
imageBoxB = ImageChops.invert(bw).getbbox() imageBoxB = ImageChops.invert(bw).getbbox()
if imageBoxA is None or imageBoxB is None: if imageBoxA is None or imageBoxB is None:
surfaceB, surfaceW = 0, 0 surfaceB, surfaceW = 0, 0
diff = 0
else: else:
surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1]) surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1])
surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1]) surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1])
if surfaceW < surfaceB: diff = ((max(surfaceB, surfaceW) - min(surfaceB, surfaceW)) / min(surfaceB, surfaceW)) * 100
self.fill = 'white' if diff > 0.5:
elif surfaceW > surfaceB: if surfaceW < surfaceB:
self.fill = 'black' self.fill = 'white'
elif surfaceW > surfaceB:
self.fill = 'black'
else: else:
fill = 0 fill = 0
startY = 0 startY = 0
@@ -498,8 +499,8 @@ class Cover:
def processExternal(self): def processExternal(self):
self.image = self.image.convert('RGB') self.image = self.image.convert('RGB')
self.image.thumbnail(self.options.profileData[1], Image.ANTIALIAS) self.image.thumbnail(self.options.profileData[1], Image.LANCZOS)
self.save(True) self.save()
def trim(self): def trim(self):
bg = Image.new(self.image.mode, self.image.size, self.image.getpixel((0, 0))) bg = Image.new(self.image.mode, self.image.size, self.image.getpixel((0, 0)))
@@ -511,15 +512,8 @@ class Cover:
else: else:
return self.image return self.image
def save(self, external=False): def save(self):
if external:
source = self.options.remoteCovers[self.tomeNumber].split('/')[-1]
else:
source = self.source
try: try:
if os.path.splitext(source)[1].lower() == '.png': self.image.save(self.target, "JPEG", optimize=1, quality=80)
self.image.save(self.target, "PNG", optimize=1)
else:
self.image.save(self.target, "JPEG", optimize=1)
except IOError: except IOError:
raise RuntimeError('Failed to save cover') raise RuntimeError('Failed to save cover')

169
kcc/metadata.py Normal file
View File

@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2013-2015 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 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
class MetadataParser:
def __init__(self, source):
self.source = source
self.data = {'Series': '',
'Volume': '',
'Number': '',
'Writers': [],
'Pencillers': [],
'Inkers': [],
'Colorists': [],
'MUid': '',
'Bookmarks': []}
self.rawdata = None
self.compressor = None
if self.source.endswith('.xml'):
self.rawdata = parse(self.source)
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-TMP-')
tmpXML = os.path.join(workdir, 'ComicInfo.xml')
output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"',
stdout=PIPE, stderr=STDOUT, 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
if os.path.isfile(tmpXML):
self.rawdata = parse(tmpXML)
rmtree(workdir)
else:
raise OSError
if self.rawdata:
self.parseXML()
def parseXML(self):
if len(self.rawdata.getElementsByTagName('Series')) != 0:
self.data['Series'] = self.rawdata.getElementsByTagName('Series')[0].firstChild.nodeValue
if len(self.rawdata.getElementsByTagName('Volume')) != 0:
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
if len(self.rawdata.getElementsByTagName('Number')) != 0:
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
if len(self.rawdata.getElementsByTagName(field)) != 0:
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
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:
self.data['Bookmarks'].append((int(page.attributes['Image'].value),
page.attributes['Bookmark'].value))
def saveXML(self):
if self.rawdata:
root = self.rawdata.getElementsByTagName('ComicInfo')[0]
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'])],
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
if self.rawdata.getElementsByTagName(row[0]):
node = self.rawdata.getElementsByTagName(row[0])[0]
if row[1]:
node.firstChild.replaceWholeText(row[1])
else:
root.removeChild(node)
elif row[1]:
main = self.rawdata.createElement(row[0])
root.appendChild(main)
text = self.rawdata.createTextNode(row[1])
main.appendChild(text)
else:
doc = Document()
root = doc.createElement('ComicInfo')
root.setAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema')
root.setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
doc.appendChild(root)
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'])],
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
if row[1]:
main = doc.createElement(row[0])
root.appendChild(main)
text = doc.createTextNode(row[1])
main.appendChild(text)
self.rawdata = doc
if self.source.endswith('.xml'):
with open(self.source, 'w', encoding='utf-8') as f:
self.rawdata.writexml(f, encoding='utf-8')
else:
workdir = mkdtemp('', 'KCC-TMP-')
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, 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
rmtree(workdir)

View File

@@ -1,5 +1,5 @@
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Based upon the code snippet by Ned Batchelder # Based upon the code snippet by Ned Batchelder
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html) # (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
@@ -19,10 +19,6 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import os import os
from random import choice from random import choice
from string import ascii_uppercase, digits from string import ascii_uppercase, digits

View File

@@ -1,6 +1,6 @@
# rarfile.py # rarfile.py
# #
# Copyright (c) 2005-2013 Marko Kreen <markokr@gmail.com> # Copyright (c) 2005-2014 Marko Kreen <markokr@gmail.com>
# #
# Permission to use, copy, modify, and/or distribute this software for any # Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above # purpose with or without fee is hereby granted, provided that the above
@@ -74,7 +74,7 @@ For more details, refer to source.
""" """
__version__ = '2.6' __version__ = '2.7-kcc'
# export only interesting items # export only interesting items
__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile'] __all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
@@ -178,8 +178,25 @@ EXTRACT_ARGS = ('x', '-y', '-idq')
#: args for testrar() #: args for testrar()
TEST_ARGS = ('t', '-idq') TEST_ARGS = ('t', '-idq')
#
# Allow use of tool that is not compatible with unrar.
#
# By default use 'bsdtar' which is 'tar' program that
# sits on top of libarchive.
#
# Problems with libarchive RAR backend:
# - Does not support solid archives.
# - Does not support password-protected archives.
#
ALT_TOOL = 'bsdtar'
ALT_OPEN_ARGS = ('-x', '--to-stdout', '-f')
ALT_EXTRACT_ARGS = ('-x', '-f')
ALT_TEST_ARGS = ('-t', '-f')
ALT_CHECK_ARGS = ('--help',)
#: whether to speed up decompression by using tmp archive #: whether to speed up decompression by using tmp archive
USE_EXTRACT_HACK = 1 USE_EXTRACT_HACK = 0
#: limit the filesize for tmp archive usage #: limit the filesize for tmp archive usage
HACK_SIZE_LIMIT = 20*1024*1024 HACK_SIZE_LIMIT = 20*1024*1024
@@ -278,6 +295,7 @@ RAR_M5 = 0x35
## ##
RAR_ID = bytes("Rar!\x1a\x07\x00", 'ascii') RAR_ID = bytes("Rar!\x1a\x07\x00", 'ascii')
RAR5_ID = bytes("Rar!\x1a\x07\x01", 'ascii')
ZERO = bytes("\0", 'ascii') ZERO = bytes("\0", 'ascii')
EMPTY = bytes("", 'ascii') EMPTY = bytes("", 'ascii')
@@ -336,6 +354,8 @@ class RarUnknownError(RarExecError):
"""Unknown exit code""" """Unknown exit code"""
class RarSignalExit(RarExecError): class RarSignalExit(RarExecError):
"""Unrar exited with signal""" """Unrar exited with signal"""
class RarCannotExec(RarExecError):
"""Executable not found."""
def is_rarfile(xfile): def is_rarfile(xfile):
@@ -343,7 +363,10 @@ def is_rarfile(xfile):
fd = XFile(xfile) fd = XFile(xfile)
buf = fd.read(len(RAR_ID)) buf = fd.read(len(RAR_ID))
fd.close() fd.close()
return buf == RAR_ID if buf == RAR_ID or buf == RAR5_ID:
return True
else:
return False
class RarInfo(object): class RarInfo(object):
@@ -693,10 +716,7 @@ class RarFile(object):
"""Let 'unrar' test the archive. """Let 'unrar' test the archive.
""" """
cmd = [UNRAR_TOOL] + list(TEST_ARGS) cmd = [UNRAR_TOOL] + list(TEST_ARGS)
if self._password is not None: add_password_arg(cmd, self._password)
cmd.append('-p' + self._password)
else:
cmd.append('-p-')
cmd.append(self.rarfile) cmd.append(self.rarfile)
p = custom_popen(cmd) p = custom_popen(cmd)
output = p.communicate()[0] output = p.communicate()[0]
@@ -769,7 +789,7 @@ class RarFile(object):
fd = XFile(self.rarfile) fd = XFile(self.rarfile)
self._fd = fd self._fd = fd
id = fd.read(len(RAR_ID)) id = fd.read(len(RAR_ID))
if id != RAR_ID: if id != RAR_ID and id != RAR5_ID:
raise NotRarFile("Not a Rar archive: "+self.rarfile) raise NotRarFile("Not a Rar archive: "+self.rarfile)
volume = 0 # first vol (.rar) is 0 volume = 0 # first vol (.rar) is 0
@@ -1172,8 +1192,7 @@ class RarFile(object):
if is_filelike(rarfile): if is_filelike(rarfile):
raise ValueError("Cannot use unrar directly on memory buffer") raise ValueError("Cannot use unrar directly on memory buffer")
cmd = [UNRAR_TOOL] + list(OPEN_ARGS) cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
if psw is not None: add_password_arg(cmd, psw)
cmd.append("-p" + psw)
cmd.append(rarfile) cmd.append(rarfile)
# not giving filename avoids encoding related problems # not giving filename avoids encoding related problems
@@ -1205,10 +1224,7 @@ class RarFile(object):
# pasoword # pasoword
psw = psw or self._password psw = psw or self._password
if psw is not None: add_password_arg(cmd, psw)
cmd.append('-p' + psw)
else:
cmd.append('-p-')
# rar file # rar file
cmd.append(self.rarfile) cmd.append(self.rarfile)
@@ -1830,10 +1846,7 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No
tmpf.close() tmpf.close()
cmd = [UNRAR_TOOL] + list(OPEN_ARGS) cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
if psw is not None and (flags & RAR_FILE_PASSWORD): add_password_arg(cmd, psw, (flags & RAR_FILE_PASSWORD))
cmd.append("-p" + psw)
else:
cmd.append("-p-")
cmd.append(tmpname) cmd.append(tmpname)
p = custom_popen(cmd) p = custom_popen(cmd)
@@ -1902,10 +1915,27 @@ def custom_popen(cmd):
except OSError: except OSError:
ex = sys.exc_info()[1] ex = sys.exc_info()[1]
if ex.errno == errno.ENOENT: if ex.errno == errno.ENOENT:
raise RarExecError("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL) raise RarCannotExec("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
raise raise
return p return p
def custom_check(cmd, ignore_retcode=False):
"""Run command, collect output, raise error if needed."""
p = custom_popen(cmd)
out, err = p.communicate()
if p.returncode and not ignore_retcode:
raise RarExecError("Check-run failed")
return out
def add_password_arg(cmd, psw, required=False):
"""Append password switch to commandline."""
if UNRAR_TOOL == ALT_TOOL:
return
if psw is not None:
cmd.append('-p' + psw)
else:
cmd.append('-p-')
def check_returncode(p, out): def check_returncode(p, out):
"""Raise exception according to unrar exit code""" """Raise exception according to unrar exit code"""
@@ -1920,6 +1950,8 @@ def check_returncode(p, out):
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError, RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
RarWriteError, RarOpenError, RarUserError, RarMemoryError, RarWriteError, RarOpenError, RarUserError, RarMemoryError,
RarCreateError, RarNoFilesError] # codes from rar.txt RarCreateError, RarNoFilesError] # codes from rar.txt
if UNRAR_TOOL == ALT_TOOL:
errmap = [None]
if code > 0 and code < len(errmap): if code > 0 and code < len(errmap):
exc = errmap[code] exc = errmap[code]
elif code == 255: elif code == 255:
@@ -1936,3 +1968,24 @@ def check_returncode(p, out):
msg = "%s [%d]" % (exc.__doc__, p.returncode) msg = "%s [%d]" % (exc.__doc__, p.returncode)
raise exc(msg) raise exc(msg)
#
# Check if unrar works
#
try:
# does UNRAR_TOOL work?
custom_check([UNRAR_TOOL], True)
except RarCannotExec:
try:
# does ALT_TOOL work?
custom_check([ALT_TOOL] + list(ALT_CHECK_ARGS), True)
# replace config
UNRAR_TOOL = ALT_TOOL
OPEN_ARGS = ALT_OPEN_ARGS
EXTRACT_ARGS = ALT_EXTRACT_ARGS
TEST_ARGS = ALT_TEST_ARGS
except RarCannotExec:
# no usable tool, only uncompressed archives work
pass

View File

@@ -1,5 +1,5 @@
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2015 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -16,33 +16,58 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
__license__ = 'ISC'
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en'
import os import os
from hashlib import md5 from hashlib import md5
from html.parser import HTMLParser
from distutils.version import StrictVersion
from time import sleep
from shutil import rmtree, move
from tempfile import mkdtemp
from zipfile import ZipFile, ZIP_DEFLATED
from re import split
try:
from scandir import walk
except ImportError:
walk = None
class HTMLStripper(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.reset()
self.strict = False
self.convert_charrefs = True
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
def getImageFileName(imgfile): def getImageFileName(imgfile):
filename = os.path.splitext(imgfile) name, ext = os.path.splitext(imgfile)
if filename[0].startswith('.') or\ ext = ext.lower()
(filename[1].lower() != '.png' and if name.startswith('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg' and ext != '.gif'):
filename[1].lower() != '.jpg' and
filename[1].lower() != '.gif' and
filename[1].lower() != '.tif' and
filename[1].lower() != '.tiff' and
filename[1].lower() != '.bmp' and
filename[1].lower() != '.jpeg'):
return None return None
return filename return [name, ext]
def walkSort(dirnames, filenames):
convert = lambda text: int(text) if text.isdigit() else text
alphanum_key = lambda key: [convert(c) for c in split('([0-9]+)', key)]
dirnames.sort(key=lambda name: alphanum_key(name.lower()))
filenames.sort(key=lambda name: alphanum_key(name.lower()))
return dirnames, filenames
def walkLevel(some_dir, level=1): def walkLevel(some_dir, level=1):
some_dir = some_dir.rstrip(os.path.sep) some_dir = some_dir.rstrip(os.path.sep)
assert os.path.isdir(some_dir) assert os.path.isdir(some_dir)
num_sep = some_dir.count(os.path.sep) num_sep = some_dir.count(os.path.sep)
for root, dirs, files in os.walk(some_dir): for root, dirs, files in walk(some_dir):
dirs, files = walkSort(dirs, files)
yield root, dirs, files yield root, dirs, files
num_sep_this = root.count(os.path.sep) num_sep_this = root.count(os.path.sep)
if num_sep + level <= num_sep_this: if num_sep + level <= num_sep_this:
@@ -58,3 +83,75 @@ def md5Checksum(filePath):
break break
m.update(data) m.update(data)
return m.hexdigest() return m.hexdigest()
def check7ZFile(filePath):
with open(filePath, 'rb') as fh:
header = fh.read(6)
return header == b"7z\xbc\xaf'\x1c"
def saferReplace(old, new):
for x in range(5):
try:
os.replace(old, new)
except PermissionError:
sleep(5)
else:
break
else:
raise PermissionError
def removeFromZIP(zipfname, *filenames):
tempdir = mkdtemp('', 'KCC-TMP-')
try:
tempname = os.path.join(tempdir, 'KCC-TMP.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))
move(tempname, zipfname)
finally:
rmtree(tempdir)
# noinspection PyUnresolvedReferences
def dependencyCheck(level):
missing = []
if level > 2:
try:
from PyQt5.QtCore import qVersion as qtVersion
if StrictVersion('5.2.0') > StrictVersion(qtVersion()):
missing.append('PyQt 5.2.0+')
except ImportError:
missing.append('PyQt 5.2.0+')
if level > 1:
try:
from psutil import __version__ as psutilVersion
if StrictVersion('2.0.0') > StrictVersion(psutilVersion):
missing.append('psutil 2.0.0+')
except ImportError:
missing.append('psutil 2.0.0+')
try:
from slugify import __version__ as slugifyVersion
if StrictVersion('0.1.0') > StrictVersion(slugifyVersion):
missing.append('python-slugify 0.1.0+')
except ImportError:
missing.append('python-slugify 0.1.0+')
try:
from PIL import PILLOW_VERSION as pillowVersion
if StrictVersion('2.7.0') > StrictVersion(pillowVersion):
missing.append('Pillow 2.7.0+')
except ImportError:
missing.append('Pillow 2.7.0+')
try:
from scandir import __version__ as scandirVersion
if StrictVersion('0.9') > StrictVersion(scandirVersion):
missing.append('scandir 0.9+')
except ImportError:
missing.append('scandir 0.9+')
if len(missing) > 0:
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1)

View File

@@ -9,12 +9,13 @@ Usage (Mac OS X):
python setup.py py2app python setup.py py2app
""" """
from sys import platform, version_info from sys import platform, version_info
from kcc import __version__
if version_info[0] != 3: if version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
NAME = "KindleComicConverter" NAME = "KindleComicConverter"
VERSION = "4.3" VERSION = __version__
MAIN = "kcc.py" MAIN = "kcc.py"
if platform == "darwin": if platform == "darwin":
@@ -26,17 +27,14 @@ if platform == "darwin":
py2app=dict( py2app=dict(
argv_emulation=True, argv_emulation=True,
iconfile='icons/comic2ebook.icns', iconfile='icons/comic2ebook.icns',
includes=['PIL', 'sip', 'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtNetwork', 'PyQt5.QtWidgets', includes=['sip', 'PyQt5.QtPrintSupport'],
'PyQt5.QtPrintSupport'],
resources=['LICENSE.txt', 'other/qt.conf', 'other/Additional-LICENSE.txt', 'other/unrar', 'other/7za'], resources=['LICENSE.txt', 'other/qt.conf', 'other/Additional-LICENSE.txt', 'other/unrar', 'other/7za'],
plist=dict( plist=dict(
CFBundleName=NAME, CFBundleName=NAME,
CFBundleShortVersionString=VERSION, CFBundleShortVersionString=VERSION,
CFBundleGetInfoString=NAME + " " + VERSION + CFBundleGetInfoString=NAME + " " + VERSION +
", written 2012-2014 by Ciro Mattia Gonano and Pawel Jastrzebski", ", written 2012-2015 by Ciro Mattia Gonano and Pawel Jastrzebski",
CFBundleExecutable=NAME, CFBundleExecutable=NAME,
CFBundleIdentifier='com.github.ciromattia.kcc',
CFBundleSignature='dplt',
CFBundleDocumentTypes=[ CFBundleDocumentTypes=[
dict( dict(
CFBundleTypeExtensions=['cbz', 'cbr', 'cb7', 'zip', 'rar', '7z', 'pdf'], CFBundleTypeExtensions=['cbz', 'cbr', 'cb7', 'zip', 'rar', '7z', 'pdf'],
@@ -47,7 +45,7 @@ if platform == "darwin":
], ],
LSMinimumSystemVersion='10.8.0', LSMinimumSystemVersion='10.8.0',
LSEnvironment=dict( LSEnvironment=dict(
PATH='/usr/local/bin:/usr/bin:/bin' PATH='./../Resources:/usr/local/bin:/usr/bin:/bin'
), ),
NSHumanReadableCopyright='ISC License (ISCL)' NSHumanReadableCopyright='ISC License (ISCL)'
) )
@@ -57,9 +55,9 @@ if platform == "darwin":
elif platform == "win32": elif platform == "win32":
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import py2exe import py2exe
import platform as arch import platform
from distutils.core import setup from distutils.core import setup
if arch.architecture()[0] == '64bit': if platform.architecture()[0] == '64bit':
suffix = '_64' suffix = '_64'
else: else:
suffix = '' suffix = ''
@@ -69,10 +67,10 @@ elif platform == "win32":
'other\\7za.exe', 'other\\7za.exe',
'other\\UnRAR.exe', 'other\\UnRAR.exe',
'other\\Additional-LICENSE.txt', 'other\\Additional-LICENSE.txt',
'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libGLESv2.dll',
'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libEGL.dll'])] 'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libEGL.dll'])]
extra_options = dict( extra_options = dict(
options={'py2exe': {"bundle_files": 1, options={'py2exe': {"bundle_files": 1,
"dll_excludes": ["tcl85.dll", "tk85.dll"],
"dist_dir": "dist" + suffix, "dist_dir": "dist" + suffix,
"compressed": True, "compressed": True,
"includes": ["sip"], "includes": ["sip"],
@@ -81,7 +79,7 @@ elif platform == "win32":
windows=[{"script": MAIN, windows=[{"script": MAIN,
"dest_base": "KCC", "dest_base": "KCC",
"version": VERSION, "version": VERSION,
"copyright": "Ciro Mattia Gonano, Pawel Jastrzebski © 2014", "copyright": "Ciro Mattia Gonano, Pawel Jastrzebski © 2012-2015",
"legal_copyright": "ISC License (ISCL)", "legal_copyright": "ISC License (ISCL)",
"product_version": VERSION, "product_version": VERSION,
"product_name": "Kindle Comic Converter", "product_name": "Kindle Comic Converter",

View File

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