1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-18 15:08:48 +00:00

Compare commits

...

57 Commits
5.1 ... 5.4

Author SHA1 Message Date
Paweł Jastrzębski
ac2934aba2 Fix setup.py 2017-04-09 15:41:12 +02:00
Paweł Jastrzębski
a5064a0c0a Merge pull request #233 from ciromattia/dev
5.4
2017-04-09 15:35:41 +02:00
Paweł Jastrzębski
2d712e796d Updated README + version bump 2017-04-09 15:34:59 +02:00
Paweł Jastrzębski
cc3da40fd7 Fixed page splitter 2017-04-06 15:24:03 +02:00
Paweł Jastrzębski
a53c272bd0 Tweaked webtoon splitter 2017-03-25 09:16:29 +01:00
Paweł Jastrzębski
6526b139fd Code cleanup 2017-03-25 08:05:28 +01:00
Paweł Jastrzębski
283d6101cd Reimplemented HQ Panel View (close #223) 2017-03-22 10:46:58 +01:00
Paweł Jastrzębski
02dab3c6ee Overhauled webtoon splitter 2017-03-22 07:56:23 +01:00
Paweł Jastrzębski
1895aa127d Decrease memory usage 2017-03-19 07:48:39 +01:00
Paweł Jastrzębski
c01ff83fce Merge pull request #231 from ciromattia/dev
5.3.1
2017-03-17 11:11:00 +01:00
Paweł Jastrzębski
4b670f3754 Update README.md 2017-03-17 11:09:29 +01:00
Paweł Jastrzębski
23b1560fa2 Updated README + version bump 2017-03-17 11:02:59 +01:00
Paweł Jastrzębski
62350608dc Added some additional checks 2017-03-17 10:58:48 +01:00
Paweł Jastrzębski
8048b91fa8 Overhauled startup functions for PyPI packaging 2017-03-17 10:55:56 +01:00
Paweł Jastrzębski
2e9b3389e4 Code cleanup 2017-03-15 18:30:32 +01:00
Paweł Jastrzębski
40e1ab4cf3 Updated build environment 2017-03-12 15:28:54 +01:00
Paweł Jastrzębski
d2c12c89e6 Updated dependencies 2017-03-12 13:29:10 +01:00
Paweł Jastrzębski
4647fd1f1d Merge pull request #224 from ciromattia/dev
5.3.0
2017-02-12 09:13:12 +01:00
Paweł Jastrzębski
010ad3c88c Updated README + version bump 2017-02-12 09:11:18 +01:00
Paweł Jastrzębski
4b0a94a8a0 Revert "Force admin rights for Windows version"
This reverts commit e1470cca15.
2017-02-06 19:21:33 +01:00
Paweł Jastrzębski
807a2d1dff Tweaked cover parsing 2017-02-05 08:53:09 +01:00
Paweł Jastrzębski
e1470cca15 Force admin rights for Windows version 2017-02-05 08:35:34 +01:00
Paweł Jastrzębski
02b9081e37 Improved compatibility with non-Kindle devices 2017-02-04 19:05:31 +01:00
Paweł Jastrzębski
495db88a9e Re-enabled Panel View support for Kindle Keyboard 2017-02-01 17:32:48 +01:00
Paweł Jastrzębski
2bea546a9d Re-enabled OS X file association mechanism 2017-01-21 22:34:39 +01:00
Paweł Jastrzębski
ee042ef98d Update build environment 2017-01-21 22:21:58 +01:00
bakatrouble
aea7c0fafb Fix unreadable text with dark qt themes
Fix unreadable text with dark qt themes #2 (file list)

Tweaks
2017-01-20 09:54:32 +01:00
Paweł Jastrzębski
45c1afcad4 Update build environment 2017-01-20 09:44:21 +01:00
Paweł Jastrzębski
b8e314f6ca Improved processing of credit pages 2016-12-08 10:36:05 +01:00
Paweł Jastrzębski
d76eea9f43 Merge pull request #216 from ciromattia/dev
5.2.1
2016-11-26 18:13:08 +01:00
Paweł Jastrzębski
2e55f22355 Updated README + version bump 2016-11-26 18:12:29 +01:00
Paweł Jastrzębski
30b8770e34 Improved error reporting 2016-11-26 17:59:40 +01:00
Paweł Jastrzębski
9ad161489f Decreased ferocity of margin cropping 2016-11-26 14:56:51 +01:00
Paweł Jastrzębski
bdb459cfab Code cleanup 2016-11-26 09:23:39 +01:00
Paweł Jastrzębski
2e85556543 GUI update 2016-11-25 18:57:42 +01:00
Paweł Jastrzębski
93ebbbd0af Refactored and improved output splitting 2016-11-25 18:05:05 +01:00
Paweł Jastrzębski
dd5c907bad Merge pull request #215 from ciromattia/dev
5.2
2016-11-22 08:53:56 +01:00
Paweł Jastrzębski
64fb4a9eca Updated README + version bump 2016-11-22 08:33:02 +01:00
Paweł Jastrzębski
284c577894 Fixed some file lock anomalies 2016-11-21 17:24:58 +01:00
Paweł Jastrzębski
c68c9892e4 GUI update 2016-11-21 16:36:46 +01:00
Paweł Jastrzębski
aa00ea3aa2 Expanded autoscale option 2016-11-21 15:59:14 +01:00
Paweł Jastrzębski
88f005824c Merge branch 'dev' of https://github.com/ciromattia/kcc into dev
# Conflicts:
#	kcc/comic2ebook.py
2016-11-21 14:06:04 +01:00
Paweł Jastrzębski
2a2bfae112 Dropped HQ PV option 2016-11-21 13:55:12 +01:00
Paweł Jastrzębski
583eec787f Merge pull request #214 from houcheng/autoscale
Add autoscale option
2016-11-21 13:51:51 +01:00
Houcheng Lin
9ce691aecb add autoscale option
Instead of fixed 1.5 scale ratio, the autoscale feature uses current page's
image width, and dynamically determine the needed scale ratio. The rendering
effects looks okay and speed is fine in my KPW1.

The generated panel view will have two view ports: (top and bottom).
2016-11-20 17:38:06 -05:00
Paweł Jastrzębski
d1a07d7ffa Improved cropping mechanism 2016-11-19 17:57:16 +01:00
Paweł Jastrzębski
b545f7ad48 Small tweaks 2016-11-18 08:39:16 +01:00
Paweł Jastrzębski
9e01797d28 Merge pull request #207 from ciromattia/dev
5.1.3
2016-09-17 08:24:22 +02:00
Paweł Jastrzębski
c68c5f25bf Updated README + version bump 2016-09-17 08:23:32 +02:00
Paweł Jastrzębski
a04bf5262f Added Kobo Aura ONE profile 2016-08-20 08:40:38 +02:00
Paweł Jastrzębski
b09b2527d9 Small bugfix 2016-08-20 08:14:39 +02:00
Paweł Jastrzębski
94b372f47d Tweaked glob (close #205) 2016-08-19 08:55:38 +02:00
Paweł Jastrzębski
b978adcc7c Updated README + version bump 2016-05-11 16:55:37 +02:00
Paweł Jastrzębski
9dee4432ad Updated Docker recipe 2016-05-11 12:15:56 +02:00
Paweł Jastrzębski
15055c6c0c Added missing Docker recipe 2016-05-01 09:42:12 +02:00
Paweł Jastrzębski
3f948a10b0 Updated README + version bump 2016-05-01 08:39:53 +02:00
Paweł Jastrzębski
1c942d81db Fixed multiple GUI bugs 2016-05-01 08:34:59 +02:00
31 changed files with 1045 additions and 1040 deletions

6
.gitignore vendored
View File

@@ -4,7 +4,6 @@
.idea .idea
.DS_Store .DS_Store
Thumbs.db Thumbs.db
build
dist dist
Output Output
test test
@@ -13,4 +12,7 @@ kindlegen*
*.spec *.spec
setup.bat setup.bat
setup.sh setup.sh
kcc/sentry.py kindlecomicconverter/sentry.py
build/
.python-version
KindleComicConverter.egg-info/

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-2016 Paweł Jastrzębski <pawelj@iosphe.re> Copyright (c) 2013-2017 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

1
MANIFEST.in Normal file
View File

@@ -0,0 +1 @@
exclude kindlecomicconverter/sentry.py

114
README.md
View File

@@ -1,4 +1,6 @@
# KCC # KCC
[![GitHub release](https://img.shields.io/github/release/ciromattia/kcc.svg)](https://github.com/ciromattia/kcc/releases) [![PyPI](https://img.shields.io/pypi/v/KindleComicConverter.svg)](https://pypi.python.org/pypi/KindleComicConverter) [![AUR](https://img.shields.io/aur/version/kcc.svg)](https://aur.archlinux.org/packages/kcc/)
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ. **Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
@@ -17,11 +19,11 @@ If you can fix an open issue, fork & make a pull request.
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:
- [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2) - [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
- [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub) - [![Donate Flattr](https://img.shields.io/badge/Donate-Flattr-green.svg)](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
- Paweł Jastrzębski: - Paweł Jastrzębski:
- [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS) - [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
- Bitcoin: 1W15wwqsfd7wbaZ6wvSJf1LW1bz6q5L8b - [![Donate Bitcoin](https://img.shields.io/badge/Donate-Bitcoin-green.svg)](https://jastrzeb.ski/donate/)
## BINARY RELEASES ## BINARY RELEASES
You can find the latest released binary at the following links: You can find the latest released binary at the following links:
@@ -29,20 +31,25 @@ You can find the latest released binary at the following links:
- **Linux (Glibc 2.19+):** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/) - **Linux (Glibc 2.19+):** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/)
- **OS X (10.9+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/) - **OS X (10.9+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/)
## PYPI
**KCC** is also available on PyPI.
```
pip install KindleComicConverter
```
## DEPENDENCIES ## DEPENDENCIES
Following software is required to run Linux version of **KCC** and/or bare sources: Following software is required to run Linux version of **KCC** and/or bare sources:
- Python 3.3+ - Python 3.3+
- [PyQt](https://pypi.python.org/pypi/PyQt5) 5.6.0+ - [PyQt5](https://pypi.python.org/pypi/PyQt5) 5.6.0+
- [Pillow](https://pypi.python.org/pypi/Pillow/) 3.2.0+ - [Pillow](https://pypi.python.org/pypi/Pillow/) 4.0.0+
- [psutil](https://pypi.python.org/pypi/psutil) 4.1.0+ - [psutil](https://pypi.python.org/pypi/psutil) 5.0.0+
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.0+ - [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.1+
- [raven](https://pypi.python.org/pypi/raven) 5.13.0+ - [raven](https://pypi.python.org/pypi/raven) 6.0.0+
- [scandir](https://pypi.python.org/pypi/scandir) 1.2.0+ _(needed only when using Python 3.3 or 3.4)_
On Debian based distributions these two commands should install all needed dependencies: On Debian based distributions these two commands should install all needed dependencies:
``` ```
sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full unrar sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full unrar
sudo pip3 install --upgrade pillow python-slugify psutil scandir raven pyqt5 sudo pip3 install --upgrade pillow python-slugify psutil pyqt5 raven
``` ```
### Optional dependencies ### Optional dependencies
@@ -65,7 +72,7 @@ After completed conversion you should find ready file alongside the original inp
Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details. Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details.
CLI version of **KCC** is intended for power users. It is not idiot-proof like GUI :-) CLI version of **KCC** is intended for power users. It allow to use options that might not be compatible and decrease quality of output.
### Standalone `kcc-c2e.py` usage: ### Standalone `kcc-c2e.py` usage:
@@ -75,9 +82,12 @@ Usage: kcc-c2e [options] comic_file|comic_folder
Options: Options:
MAIN: MAIN:
-p PROFILE, --profile=PROFILE -p PROFILE, --profile=PROFILE
Device profile (Available options: K1, K2, K3, K45, KDX, Device profile (Available options: K1, K2, K3, K45,
KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O) [Default=KV] KDX, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
KoAO) [Default=KV]
-m, --manga-style Manga style (right-to-left reading and splitting) -m, --manga-style Manga style (right-to-left reading and splitting)
-q, --hq Try to increase the quality of magnification
-2, --two-panel Display two not four panels in Panel View mode
-w, --webtoon Webtoon processing mode -w, --webtoon Webtoon processing mode
OUTPUT SETTINGS: OUTPUT SETTINGS:
@@ -86,29 +96,31 @@ Options:
-t TITLE, --title=TITLE -t TITLE, --title=TITLE
Comic title [Default=filename or directory name] Comic title [Default=filename or directory name]
-f FORMAT, --format=FORMAT -f FORMAT, --format=FORMAT
Output format (Available options: Auto, MOBI, EPUB, CBZ) Output format (Available options: Auto, MOBI, EPUB,
[Default=Auto] CBZ) [Default=Auto]
-b, --batchsplit Split output into multiple files -b BATCHSPLIT, --batchsplit=BATCHSPLIT
Split output into multiple files. 0: Don't split 1:
Automatic mode 2: Consider every subdirectory as
separate volume [Default=0]
PROCESSING: PROCESSING:
-u, --upscale Resize images smaller than device's resolution -u, --upscale Resize images smaller than device's resolution
-s, --stretch Stretch images to device's resolution -s, --stretch Stretch images to device's resolution
-r SPLITTER, --splitter=SPLITTER -r SPLITTER, --splitter=SPLITTER
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0] Double page parsing mode. 0: Split 1: Rotate 2: Both
[Default=0]
-g GAMMA, --gamma=GAMMA -g GAMMA, --gamma=GAMMA
Apply gamma correction to linearize the image [Default=Auto] Apply gamma correction to linearize the image
--hq Enable high quality Panel View [Default=Auto]
-c CROPPING, --cropping=CROPPING
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
page numbers [Default=2]
--cp=CROPPINGP, --croppingpower=CROPPINGP
Set cropping power [Default=1.0]
--blackborders Disable autodetection and force black borders --blackborders Disable autodetection and force black borders
--whiteborders Disable autodetection and force white borders --whiteborders Disable autodetection and force white borders
--forcecolor Don't convert images to grayscale --forcecolor Don't convert images to grayscale
--forcepng Create PNG files instead JPEG --forcepng Create PNG files instead JPEG
--cropping=CROPPING
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
page numbers [Default=2]
--croppingpower=CROPPINGP
Set margin cropping threshold [Default=0.1]
--croppingpowerpage=CROPPINGPN
Set page number cropping threshold [Default=5.0]
CUSTOM PROFILE: CUSTOM PROFILE:
--customwidth=CUSTOMWIDTH --customwidth=CUSTOMWIDTH
@@ -153,16 +165,50 @@ The app relies and includes the following scripts:
* [Kindle Paperwhite 3 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi) * [Kindle Paperwhite 3 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi) * [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
* [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K45.mobi) * [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K45.mobi)
* [Kindle Keyboard](http://kcc.iosphe.re/Samples/Ubunchu!-K3.mobi)
* [Kindle DX/DXG](http://kcc.iosphe.re/Samples/Ubunchu!-KDX.cbz)
* [Kobo Mini/Touch](http://kcc.iosphe.re/Samples/Ubunchu-KoMT.kepub.epub)
* [Kobo Glo](http://kcc.iosphe.re/Samples/Ubunchu-KoG.kepub.epub)
* [Kobo Glo HD](http://kcc.iosphe.re/Samples/Ubunchu-KoGHD.kepub.epub)
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub) * [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub)
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub) * [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub)
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub) * [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub)
* [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub)
## CHANGELOG ## CHANGELOG
#### 5.4:
* Reimplemented high quality Panel View option
* Improved webtoon splitter
* Fixed page splitter
#### 5.3.1:
* Small increase of output quality
* Improved error reporting
* Internal changes and tweaks
#### 5.3:
* Vastly improved output compatibility for non-Kindle devices
* Enabled old pinch zoom for Kindle devices
* Re-enabled Panel View support for Kindle Keyboard
* Partially re-enabled OS X file association mechanism
* Fixed multiple smaller issues
#### 5.2.1:
* Improved directory parsing
* Tweaked margin detection algorithm
* Improved error reporting
#### 5.2:
* Added new Panel View options
* Implemented new margin detection algorithm
* Removed HQ Panel View mode
* Fixed multiple smaller issues
#### 5.1.3:
* Added Kobo Aura ONE profile
* Fixed few small bugs
#### 5.1.2:
* Fixed error reporting
#### 5.1.1:
* Fixed multiple GUI bugs
#### 5.1: #### 5.1:
* GUI now can be resized and high DPI support was somewhat improved * GUI now can be resized and high DPI support was somewhat improved
* Added profile for Kindle Oasis * Added profile for Kindle Oasis
@@ -474,5 +520,5 @@ The app relies and includes the following scripts:
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-2016 Ciro Mattia Gonano and Paweł Jastrzębski. Copyright (c) 2012-2017 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.

18
docker/Build Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
pip3 install --upgrade pip setuptools wheel
pip3 install pillow python-slugify psutil pyinstaller raven pyqt5 certifi
gem install fpm
cd /app
pyinstaller -F -s kcc.py
mkdir -p dist/usr/bin dist/usr/share/applications dist/usr/share/doc/kindlecomicconverter dist/usr/share/kindlecomicconverter dist/usr/share/lintian/overrides
mv dist/kcc dist/usr/bin
cp icons/comic2ebook.png dist/usr/share/kindlecomicconverter
cp LICENSE.txt dist/usr/share/doc/kindlecomicconverter/copyright
cp other/linux/kindlecomicconverter.desktop dist/usr/share/applications
cp other/linux/kindlecomicconverter dist/usr/share/lintian/overrides
cd /app/dist
fpm -f -s dir -t deb -n kindlecomicconverter -v $KCCVER -m "Paweł Jastrzębski <pawelj@iosphe.re>" --license "ISC" --description "$(printf "Comic and Manga converter for e-book readers.\nThis app allows you to transform your PNG, JPG, GIF, CBZ, CBR and CB7 files\ninto EPUB or MOBI format e-books.")" --url "https://kcc.iosphe.re/" --deb-priority "optional" --vendor "" --category "graphics" -d "unrar | unrar-free" -d "p7zip-full" -d "libc6" usr

12
docker/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
# acidweb/kcc
FROM debian:stretch
MAINTAINER Paweł Jastrzębski <pawelj@iosphe.re>
ADD ./Build /Build
RUN apt-get update && apt-get -y dist-upgrade
RUN apt-get -y install build-essential curl ruby ruby-dev libpng-dev libjpeg-dev python3 python3-dev python3-pyqt5
RUN curl https://bootstrap.pypa.io/get-pip.py | python3
RUN apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
CMD /Build

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>450</width> <width>450</width>
<height>450</height> <height>400</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -22,64 +22,7 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>5</number> <number>5</number>
</property> </property>
<item row="3" column="0"> <item row="1" column="0" colspan="2">
<widget class="QPushButton" name="directoryButton">
<property name="minimumSize">
<size>
<width>130</width>
<height>30</height>
</size>
</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">
<string>Add directory</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/folder_new.png</normaloff>:/Other/icons/folder_new.png</iconset>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="clearButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Clear list</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="fileButton">
<property name="minimumSize">
<size>
<width>130</width>
<height>30</height>
</size>
</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">
<string>Add file</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/document_new.png</normaloff>:/Other/icons/document_new.png</iconset>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QProgressBar" name="progressBar"> <widget class="QProgressBar" name="progressBar">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
@@ -101,62 +44,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="2" column="0" colspan="2">
<widget class="QComboBox" name="deviceBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>28</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="convertButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<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>
</property>
<property name="text">
<string>Convert</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/convert.png</normaloff>:/Other/icons/convert.png</iconset>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QComboBox" name="formatBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>28</height>
</size>
</property>
<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>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QListWidget" name="jobList"> <widget class="QListWidget" name="jobList">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}</string> <string notr="true">QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}</string>
</property> </property>
<property name="selectionMode"> <property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum> <enum>QAbstractItemView::NoSelection</enum>
@@ -169,7 +60,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="3"> <item row="6" column="0" colspan="2">
<widget class="QWidget" name="customWidget" native="true"> <widget class="QWidget" name="customWidget" native="true">
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
@@ -242,7 +133,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="3"> <item row="4" column="0" colspan="2">
<widget class="QWidget" name="optionWidget" native="true"> <widget class="QWidget" name="optionWidget" native="true">
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin"> <property name="leftMargin">
@@ -275,15 +166,21 @@
<property name="text"> <property name="text">
<string>Spread splitter</string> <string>Spread splitter</string>
</property> </property>
<property name="tristate">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QCheckBox" name="qualityBox"> <widget class="QCheckBox" name="qualityBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;High quality Panel View.&lt;br/&gt;Require source files with bigger resolution than target device.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Highly impact size of output file!&lt;/span&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;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - 4 panels&lt;br/&gt;&lt;/span&gt;Zoom each corner separately.&lt;/p&gt;&lt;p style='white-space:pre'&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - 2 panels&lt;br/&gt;&lt;/span&gt;Zoom only the top and bottom of the page.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - 4 high quality panels&lt;br/&gt;&lt;/span&gt;Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>HQ zoom</string> <string>Panel View 4/2/HQ</string>
</property>
<property name="tristate">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@@ -305,6 +202,9 @@
<property name="text"> <property name="text">
<string>Stretch/Upscale</string> <string>Stretch/Upscale</string>
</property> </property>
<property name="tristate">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="1" column="2">
@@ -325,15 +225,18 @@
<property name="text"> <property name="text">
<string>W/B margins</string> <string>W/B margins</string>
</property> </property>
<property name="tristate">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QCheckBox" name="noDitheringBox"> <widget class="QCheckBox" name="outputSplit">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Create PNG files instead JPEG.&lt;br/&gt;Quality increase is not noticeable on most of devices.&lt;br/&gt;Output files &lt;span style=&quot; font-weight:600;&quot;&gt;might&lt;/span&gt; be smaller.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;MOBI conversion will be much slower.&lt;/span&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;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - Automatic mode&lt;br/&gt;&lt;/span&gt;Output will be splitted automatically.&lt;/p&gt;&lt;p style='white-space:pre'&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Volume mode&lt;br/&gt;&lt;/span&gt;Every subdirectory will be considered as separate volume.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>PNG output</string> <string>Output split</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -350,7 +253,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="6" column="0" colspan="3"> <item row="5" column="0" colspan="2">
<widget class="QWidget" name="gammaWidget" native="true"> <widget class="QWidget" name="gammaWidget" native="true">
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
@@ -391,7 +294,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="0" column="0" colspan="3"> <item row="0" column="0" colspan="2">
<widget class="QWidget" name="toolWidget" native="true"> <widget class="QWidget" name="toolWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin"> <property name="leftMargin">
@@ -443,6 +346,151 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2">
<widget class="QWidget" name="buttonWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="directoryButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</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">
<string>Add directory</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/folder_new.png</normaloff>:/Other/icons/folder_new.png</iconset>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="fileButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</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">
<string>Add file</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/document_new.png</normaloff>:/Other/icons/document_new.png</iconset>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="deviceBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>28</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="formatBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>28</height>
</size>
</property>
<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>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="convertButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<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>
</property>
<property name="text">
<string>Convert</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/convert.png</normaloff>:/Other/icons/convert.png</iconset>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="clearButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Clear list</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
</property>
</widget>
</item>
</layout>
<zorder>directoryButton</zorder>
<zorder>clearButton</zorder>
<zorder>fileButton</zorder>
<zorder>deviceBox</zorder>
<zorder>convertButton</zorder>
<zorder>formatBox</zorder>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QStatusBar" name="statusBar"> <widget class="QStatusBar" name="statusBar">
@@ -451,6 +499,29 @@
</property> </property>
</widget> </widget>
</widget> </widget>
<tabstops>
<tabstop>convertButton</tabstop>
<tabstop>clearButton</tabstop>
<tabstop>directoryButton</tabstop>
<tabstop>fileButton</tabstop>
<tabstop>deviceBox</tabstop>
<tabstop>formatBox</tabstop>
<tabstop>mangaBox</tabstop>
<tabstop>rotateBox</tabstop>
<tabstop>qualityBox</tabstop>
<tabstop>webtoonBox</tabstop>
<tabstop>upscaleBox</tabstop>
<tabstop>gammaBox</tabstop>
<tabstop>borderBox</tabstop>
<tabstop>outputSplit</tabstop>
<tabstop>colorBox</tabstop>
<tabstop>editorButton</tabstop>
<tabstop>wikiButton</tabstop>
<tabstop>jobList</tabstop>
<tabstop>gammaSlider</tabstop>
<tabstop>widthBox</tabstop>
<tabstop>heightBox</tabstop>
</tabstops>
<resources> <resources>
<include location="KCC.qrc"/> <include location="KCC.qrc"/>
</resources> </resources>

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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -23,14 +23,9 @@ if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
from kcc.shared import dependencyCheck
dependencyCheck(2)
from multiprocessing import freeze_support from multiprocessing import freeze_support
from kcc import __version__ from kindlecomicconverter.startup import startC2E
from kcc.comic2ebook import main
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() freeze_support()
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.') startC2E()
sys.exit(main(sys.argv[1:]))

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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -23,14 +23,9 @@ if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') print('ERROR: This is Python 3 script!')
exit(1) exit(1)
from kcc.shared import dependencyCheck
dependencyCheck(1)
from multiprocessing import freeze_support from multiprocessing import freeze_support
from kcc import __version__ from kindlecomicconverter.startup import startC2P
from kcc.comic2panel import main
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() freeze_support()
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.') startC2P()
sys.exit(main(sys.argv[1:]))

View File

@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter" #define MyAppName "Kindle Comic Converter"
#define MyAppVersion "5.1" #define MyAppVersion "5.4"
#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-2016 Ciro Mattia Gonano and Paweł Jastrzębski AppCopyright=Copyright (C) 2012-2017 Ciro Mattia Gonano and Paweł Jastrzębski
ArchitecturesAllowed=x64 ArchitecturesAllowed=x64
DefaultDirName={pf}\{#MyAppName} DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}

26
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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 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
@@ -39,7 +39,7 @@ elif sys.platform.startswith('win'):
class _Popen(forking.Popen): class _Popen(forking.Popen):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
if hasattr(sys, 'frozen'): if hasattr(sys, 'frozen'):
# noinspection PyProtectedMember # noinspection PyUnresolvedReferences,PyProtectedMember
os.putenv('_MEIPASS2', sys._MEIPASS) os.putenv('_MEIPASS2', sys._MEIPASS)
try: try:
super(_Popen, self).__init__(*args, **kw) super(_Popen, self).__init__(*args, **kw)
@@ -59,28 +59,14 @@ elif sys.platform.startswith('win'):
# Load additional Sentry configuration # Load additional Sentry configuration
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
try: try:
import kcc.sentry import kindlecomicconverter.sentry
except: except:
pass pass
from kcc.shared import dependencyCheck
dependencyCheck(3)
from multiprocessing import freeze_support from multiprocessing import freeze_support
from kcc import KCC_gui from kindlecomicconverter.startup import start
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() freeze_support()
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1" start()
KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
if KCCAplication.isRunning():
if len(sys.argv) > 1:
KCCAplication.sendMessage(sys.argv[1])
else:
KCCAplication.sendMessage('ARISE')
else:
KCCWindow = KCC_gui.QMainWindowKCC()
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
if len(sys.argv) > 1:
KCCUI.handleMessage(sys.argv[1])
sys.exit(KCCAplication.exec_())

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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 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
@@ -21,17 +21,15 @@ import os
import sys import sys
from urllib.parse import unquote from urllib.parse import unquote
from urllib.request import urlopen, urlretrieve, Request from urllib.request import urlopen, urlretrieve, Request
from time import sleep, time from time import sleep
from datetime import datetime
from shutil import move from shutil import move
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
from xml.dom.minidom import parse, Document from xml.dom.minidom import parse
from xml.sax.saxutils import escape
from psutil import Popen, Process from psutil import Popen, Process
from copy import copy from copy import copy
from distutils.version import StrictVersion from distutils.version import StrictVersion
from xml.sax.saxutils import escape
from platform import platform
from raven import Client from raven import Client
from .shared import md5Checksum, HTMLStripper, sanitizeTrace from .shared import md5Checksum, HTMLStripper, sanitizeTrace
from . import __version__ from . import __version__
@@ -256,8 +254,10 @@ class WorkerThread(QtCore.QThread):
options.splitter = 2 options.splitter = 2
elif GUI.rotateBox.checkState() == 2: elif GUI.rotateBox.checkState() == 2:
options.splitter = 1 options.splitter = 1
if GUI.qualityBox.isChecked(): if GUI.qualityBox.checkState() == 1:
options.hqmode = True options.autoscale = True
elif GUI.qualityBox.checkState() == 2:
options.hq = True
if GUI.webtoonBox.isChecked(): if GUI.webtoonBox.isChecked():
options.webtoon = True options.webtoon = True
if GUI.upscaleBox.checkState() == 1: if GUI.upscaleBox.checkState() == 1:
@@ -270,8 +270,8 @@ class WorkerThread(QtCore.QThread):
options.white_borders = True options.white_borders = True
elif GUI.borderBox.checkState() == 2: elif GUI.borderBox.checkState() == 2:
options.black_borders = True options.black_borders = True
if GUI.noDitheringBox.isChecked(): if GUI.outputSplit.isChecked():
options.forcepng = True options.batchsplit = 2
if GUI.colorBox.isChecked(): if GUI.colorBox.isChecked():
options.forcecolor = True options.forcecolor = True
if GUI.currentMode > 2: if GUI.currentMode > 2:
@@ -319,15 +319,21 @@ class WorkerThread(QtCore.QThread):
GUI.progress.content = '' GUI.progress.content = ''
self.errors = True self.errors = True
_, _, traceback = sys.exc_info() _, _, traceback = sys.exc_info()
if ' is corrupted.' not in str(err): if len(err.args) == 1:
GUI.sentry.captureException()
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s" MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error') % (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
else:
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
% (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
GUI.sentry.extra_context({'realTraceback': err.args[1]})
if ' is corrupted.' not in str(err):
GUI.sentry.captureException()
MW.addMessage.emit('Error during conversion! Please consult ' MW.addMessage.emit('Error during conversion! Please consult '
'<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> ' '<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> '
'for more details.', 'error', False) 'for more details.', 'error', False)
MW.addTrayMessage.emit('Error during conversion!', 'Critical') MW.addTrayMessage.emit('Error during conversion!', 'Critical')
if not self.conversionAlive: if not self.conversionAlive:
if 'outputPath' in locals():
for item in outputPath: for item in outputPath:
if os.path.exists(item): if os.path.exists(item):
os.remove(item) os.remove(item)
@@ -392,7 +398,7 @@ class WorkerThread(QtCore.QThread):
for item in outputPath: for item in outputPath:
comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle( comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle(
k, comic2ebook.options.covers[outputPath.index(item)][1]) k, comic2ebook.options.covers[outputPath.index(item)][1])
MW.addMessage.emit('Kindle detected. Uploading covers...', 'info', False) MW.addMessage.emit('Kindle detected. Uploading covers... <b>Done!</b>', 'info', False)
else: else:
GUI.progress.content = '' GUI.progress.content = ''
for item in outputPath: for item in outputPath:
@@ -527,8 +533,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def clearJobs(self): def clearJobs(self):
GUI.jobList.clear() GUI.jobList.clear()
# noinspection PyCallByClass,PyTypeChecker,PyArgumentList
def openWiki(self): def openWiki(self):
# noinspection PyCallByClass
QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki')) QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki'))
def modeChange(self, mode): def modeChange(self, mode):
@@ -602,11 +608,25 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.upscaleBox.setEnabled(False) GUI.upscaleBox.setEnabled(False)
GUI.upscaleBox.setChecked(True) GUI.upscaleBox.setChecked(True)
else: else:
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if profile['PVOptions']:
GUI.qualityBox.setEnabled(True) GUI.qualityBox.setEnabled(True)
GUI.mangaBox.setEnabled(True) GUI.mangaBox.setEnabled(True)
GUI.rotateBox.setEnabled(True) GUI.rotateBox.setEnabled(True)
GUI.upscaleBox.setEnabled(True) GUI.upscaleBox.setEnabled(True)
def togglequalityBox(self, value):
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if value == 2:
if profile['Label'] in ['KV']:
self.addMessage('This option is intended for older Kindle models.', 'warning')
self.addMessage('It might not provide any quality increase in this case.', 'warning')
GUI.upscaleBox.setEnabled(False)
GUI.upscaleBox.setChecked(True)
else:
GUI.upscaleBox.setEnabled(True)
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
def changeGamma(self, value): def changeGamma(self, value):
valueRaw = int(5 * round(float(value) / 5)) valueRaw = int(5 * round(float(value) / 5))
value = '%.2f' % (float(valueRaw) / 100) value = '%.2f' % (float(valueRaw) / 100)
@@ -628,9 +648,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.changeFormat() self.changeFormat()
GUI.gammaSlider.setValue(0) GUI.gammaSlider.setValue(0)
self.changeGamma(0) self.changeGamma(0)
GUI.qualityBox.setEnabled(profile['Quality']) if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions'])
GUI.upscaleBox.setChecked(profile['DefaultUpscale']) GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
if not profile['Quality']: if not profile['PVOptions']:
GUI.qualityBox.setChecked(False) GUI.qualityBox.setChecked(False)
if str(GUI.deviceBox.currentText()) == 'Other': if str(GUI.deviceBox.currentText()) == 'Other':
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">' self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
@@ -642,7 +663,13 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.formatBox.setCurrentIndex(outputFormat) GUI.formatBox.setCurrentIndex(outputFormat)
else: else:
GUI.formatBox.setCurrentIndex(profile['DefaultFormat']) GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
GUI.qualityBox.setEnabled(profile['Quality']) if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions'])
if str(GUI.formatBox.currentText()) == 'MOBI/AZW3':
GUI.outputSplit.setEnabled(True)
else:
GUI.outputSplit.setEnabled(False)
GUI.outputSplit.setChecked(False)
def stripTags(self, html): def stripTags(self, html):
s = HTMLStripper() s = HTMLStripper()
@@ -661,7 +688,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
# We still fill original text field with transparent content to trigger creation of horizontal scrollbar # We still fill original text field with transparent content to trigger creation of horizontal scrollbar
item.setForeground(QtGui.QColor('transparent')) item.setForeground(QtGui.QColor('transparent'))
label = QtWidgets.QLabel(message) label = QtWidgets.QLabel(message)
label.setStyleSheet('background-image:url('');background-color:rgba(0,0,0,0);') label.setStyleSheet('background-image:url('');background-color:rgba(0,0,0,0);color:rgb(0,0,0);')
label.setOpenExternalLinks(True) label.setOpenExternalLinks(True)
GUI.jobList.addItem(item) GUI.jobList.addItem(item)
GUI.jobList.setItemWidget(item, label) GUI.jobList.setItemWidget(item, label)
@@ -697,7 +724,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.conversionAlive = False self.conversionAlive = False
self.worker.sync() self.worker.sync()
else: else:
# noinspection PyArgumentList
if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath) dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
if dname != '': if dname != '':
@@ -759,7 +785,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'upscaleBox': GUI.upscaleBox.checkState(), 'upscaleBox': GUI.upscaleBox.checkState(),
'borderBox': GUI.borderBox.checkState(), 'borderBox': GUI.borderBox.checkState(),
'webtoonBox': GUI.webtoonBox.checkState(), 'webtoonBox': GUI.webtoonBox.checkState(),
'noDitheringBox': GUI.noDitheringBox.checkState(), 'outputSplit': GUI.outputSplit.checkState(),
'colorBox': GUI.colorBox.checkState(), 'colorBox': GUI.colorBox.checkState(),
'widthBox': GUI.widthBox.value(), 'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(), 'heightBox': GUI.heightBox.value(),
@@ -845,7 +871,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
else: else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error') self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
# noinspection PyArgumentList
def __init__(self, KCCAplication, KCCWindow): def __init__(self, KCCAplication, KCCWindow):
global APP, MW, GUI global APP, MW, GUI
APP = KCCAplication APP = KCCAplication
@@ -883,47 +908,49 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if self.windowSize == '0x0': if self.windowSize == '0x0':
MW.resize(500, 500) MW.resize(500, 500)
elif sys.platform.startswith('darwin'): elif sys.platform.startswith('darwin'):
GUI.deviceBox.setMinimumSize(QtCore.QSize(0, 0)) for element in ['editorButton', 'wikiButton', 'directoryButton', 'clearButton', 'fileButton', 'deviceBox',
GUI.formatBox.setMinimumSize(QtCore.QSize(0, 0)) 'convertButton', 'formatBox']:
GUI.directoryButton.setMinimumSize(QtCore.QSize(190, 0)) eval('GUI.' + element).setMinimumSize(QtCore.QSize(0, 0))
GUI.fileButton.setMinimumSize(QtCore.QSize(190, 0))
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1) GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
GUI.toolWidget.setMinimumSize(QtCore.QSize(0, 0)) for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']:
eval('GUI.' + element).setContentsMargins(-1, 0, -1, 0)
if self.windowSize == '0x0': if self.windowSize == '0x0':
MW.resize(500, 500) MW.resize(500, 500)
self.profiles = { self.profiles = {
"Kindle Oasis": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Oasis": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle PW 3": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle PW 1/2": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'KPW'}, 'DefaultUpscale': False, 'Label': 'KPW'},
"Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K45'}, 'DefaultUpscale': False, 'Label': 'K45'},
"Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 2, "Kindle DX/DXG": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 2,
'DefaultUpscale': False, 'Label': 'KDX'}, 'DefaultUpscale': False, 'Label': 'KDX'},
"Kobo Mini/Touch": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Mini/Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoMT'}, 'DefaultUpscale': False, 'Label': 'KoMT'},
"Kobo Glo": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Glo": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoG'}, 'DefaultUpscale': False, 'Label': 'KoG'},
"Kobo Glo HD": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Glo HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoGHD'}, 'DefaultUpscale': False, 'Label': 'KoGHD'},
"Kobo Aura": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoA'}, 'DefaultUpscale': False, 'Label': 'KoA'},
"Kobo Aura HD": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoAHD'}, 'DefaultUpscale': True, 'Label': 'KoAHD'},
"Kobo Aura H2O": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura H2O": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoAH2O'}, 'DefaultUpscale': True, 'Label': 'KoAH2O'},
"Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1, "Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoAO'},
"Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'OTHER'}, 'DefaultUpscale': False, 'Label': 'OTHER'},
"Kindle 1": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K1'}, 'DefaultUpscale': False, 'Label': 'K1'},
"Kindle 2": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K2'}, 'DefaultUpscale': False, 'Label': 'K2'},
"Kindle 3": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K3'}, 'DefaultUpscale': False, 'Label': 'K3'},
} }
profilesGUI = [ profilesGUI = [
@@ -933,6 +960,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"Kindle PW 1/2", "Kindle PW 1/2",
"Kindle", "Kindle",
"Separator", "Separator",
"Kobo Aura ONE",
"Kobo Aura H2O", "Kobo Aura H2O",
"Kobo Aura HD", "Kobo Aura HD",
"Kobo Aura", "Kobo Aura",
@@ -990,6 +1018,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.gammaSlider.valueChanged.connect(self.changeGamma) GUI.gammaSlider.valueChanged.connect(self.changeGamma)
GUI.gammaBox.stateChanged.connect(self.togglegammaBox) GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox) GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
GUI.deviceBox.activated.connect(self.changeDevice) GUI.deviceBox.activated.connect(self.changeDevice)
GUI.formatBox.activated.connect(self.changeFormat) GUI.formatBox.activated.connect(self.changeFormat)
MW.progressBarTick.connect(self.updateProgressbar) MW.progressBarTick.connect(self.updateProgressbar)

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'gui\KCC.ui' # Form implementation generated from reading ui file 'gui\KCC.ui'
# #
# Created by: PyQt5 UI code generator 5.6 # Created by: PyQt5 UI code generator 5.8.1
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_mainWindow(object): class Ui_mainWindow(object):
def setupUi(self, mainWindow): def setupUi(self, mainWindow):
mainWindow.setObjectName("mainWindow") mainWindow.setObjectName("mainWindow")
mainWindow.resize(450, 450) mainWindow.resize(450, 400)
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
mainWindow.setWindowIcon(icon) mainWindow.setWindowIcon(icon)
@@ -20,27 +20,6 @@ class Ui_mainWindow(object):
self.gridLayout = QtWidgets.QGridLayout(self.centralWidget) self.gridLayout = QtWidgets.QGridLayout(self.centralWidget)
self.gridLayout.setContentsMargins(-1, -1, -1, 5) self.gridLayout.setContentsMargins(-1, -1, -1, 5)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.directoryButton = QtWidgets.QPushButton(self.centralWidget)
self.directoryButton.setMinimumSize(QtCore.QSize(130, 30))
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/folder_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.directoryButton.setIcon(icon1)
self.directoryButton.setObjectName("directoryButton")
self.gridLayout.addWidget(self.directoryButton, 3, 0, 1, 1)
self.clearButton = QtWidgets.QPushButton(self.centralWidget)
self.clearButton.setMinimumSize(QtCore.QSize(0, 30))
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.clearButton.setIcon(icon2)
self.clearButton.setObjectName("clearButton")
self.gridLayout.addWidget(self.clearButton, 3, 1, 1, 1)
self.fileButton = QtWidgets.QPushButton(self.centralWidget)
self.fileButton.setMinimumSize(QtCore.QSize(130, 30))
icon3 = QtGui.QIcon()
icon3.addPixmap(QtGui.QPixmap(":/Other/icons/document_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.fileButton.setIcon(icon3)
self.fileButton.setObjectName("fileButton")
self.gridLayout.addWidget(self.fileButton, 3, 2, 1, 1)
self.progressBar = QtWidgets.QProgressBar(self.centralWidget) self.progressBar = QtWidgets.QProgressBar(self.centralWidget)
self.progressBar.setMinimumSize(QtCore.QSize(0, 30)) self.progressBar.setMinimumSize(QtCore.QSize(0, 30))
font = QtGui.QFont() font = QtGui.QFont()
@@ -50,33 +29,14 @@ class Ui_mainWindow(object):
self.progressBar.setVisible(False) self.progressBar.setVisible(False)
self.progressBar.setAlignment(QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) self.progressBar.setAlignment(QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter)
self.progressBar.setObjectName("progressBar") self.progressBar.setObjectName("progressBar")
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 3) self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
self.deviceBox = QtWidgets.QComboBox(self.centralWidget)
self.deviceBox.setMinimumSize(QtCore.QSize(0, 28))
self.deviceBox.setObjectName("deviceBox")
self.gridLayout.addWidget(self.deviceBox, 4, 0, 1, 1)
self.convertButton = QtWidgets.QPushButton(self.centralWidget)
self.convertButton.setMinimumSize(QtCore.QSize(0, 30))
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.convertButton.setFont(font)
icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.convertButton.setIcon(icon4)
self.convertButton.setObjectName("convertButton")
self.gridLayout.addWidget(self.convertButton, 4, 1, 1, 1)
self.formatBox = QtWidgets.QComboBox(self.centralWidget)
self.formatBox.setMinimumSize(QtCore.QSize(0, 28))
self.formatBox.setObjectName("formatBox")
self.gridLayout.addWidget(self.formatBox, 4, 2, 1, 1)
self.jobList = QtWidgets.QListWidget(self.centralWidget) self.jobList = QtWidgets.QListWidget(self.centralWidget)
self.jobList.setStyleSheet("QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;}") self.jobList.setStyleSheet("QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}")
self.jobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.jobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.jobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.jobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.jobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.jobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.jobList.setObjectName("jobList") self.jobList.setObjectName("jobList")
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 3) self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
self.customWidget = QtWidgets.QWidget(self.centralWidget) self.customWidget = QtWidgets.QWidget(self.centralWidget)
self.customWidget.setVisible(False) self.customWidget.setVisible(False)
self.customWidget.setObjectName("customWidget") self.customWidget.setObjectName("customWidget")
@@ -107,7 +67,7 @@ class Ui_mainWindow(object):
self.heightBox.setMaximum(3840) self.heightBox.setMaximum(3840)
self.heightBox.setObjectName("heightBox") self.heightBox.setObjectName("heightBox")
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1) self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
self.gridLayout.addWidget(self.customWidget, 7, 0, 1, 3) self.gridLayout.addWidget(self.customWidget, 6, 0, 1, 2)
self.optionWidget = QtWidgets.QWidget(self.centralWidget) self.optionWidget = QtWidgets.QWidget(self.centralWidget)
self.optionWidget.setObjectName("optionWidget") self.optionWidget.setObjectName("optionWidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.optionWidget) self.gridLayout_2 = QtWidgets.QGridLayout(self.optionWidget)
@@ -117,30 +77,34 @@ class Ui_mainWindow(object):
self.mangaBox.setObjectName("mangaBox") self.mangaBox.setObjectName("mangaBox")
self.gridLayout_2.addWidget(self.mangaBox, 0, 0, 1, 1) self.gridLayout_2.addWidget(self.mangaBox, 0, 0, 1, 1)
self.rotateBox = QtWidgets.QCheckBox(self.optionWidget) self.rotateBox = QtWidgets.QCheckBox(self.optionWidget)
self.rotateBox.setTristate(True)
self.rotateBox.setObjectName("rotateBox") self.rotateBox.setObjectName("rotateBox")
self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1) self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1)
self.qualityBox = QtWidgets.QCheckBox(self.optionWidget) self.qualityBox = QtWidgets.QCheckBox(self.optionWidget)
self.qualityBox.setTristate(True)
self.qualityBox.setObjectName("qualityBox") self.qualityBox.setObjectName("qualityBox")
self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1) self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1)
self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget) self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget)
self.webtoonBox.setObjectName("webtoonBox") self.webtoonBox.setObjectName("webtoonBox")
self.gridLayout_2.addWidget(self.webtoonBox, 1, 0, 1, 1) self.gridLayout_2.addWidget(self.webtoonBox, 1, 0, 1, 1)
self.upscaleBox = QtWidgets.QCheckBox(self.optionWidget) self.upscaleBox = QtWidgets.QCheckBox(self.optionWidget)
self.upscaleBox.setTristate(True)
self.upscaleBox.setObjectName("upscaleBox") self.upscaleBox.setObjectName("upscaleBox")
self.gridLayout_2.addWidget(self.upscaleBox, 1, 1, 1, 1) self.gridLayout_2.addWidget(self.upscaleBox, 1, 1, 1, 1)
self.gammaBox = QtWidgets.QCheckBox(self.optionWidget) self.gammaBox = QtWidgets.QCheckBox(self.optionWidget)
self.gammaBox.setObjectName("gammaBox") self.gammaBox.setObjectName("gammaBox")
self.gridLayout_2.addWidget(self.gammaBox, 1, 2, 1, 1) self.gridLayout_2.addWidget(self.gammaBox, 1, 2, 1, 1)
self.borderBox = QtWidgets.QCheckBox(self.optionWidget) self.borderBox = QtWidgets.QCheckBox(self.optionWidget)
self.borderBox.setTristate(True)
self.borderBox.setObjectName("borderBox") self.borderBox.setObjectName("borderBox")
self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1) self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1)
self.noDitheringBox = QtWidgets.QCheckBox(self.optionWidget) self.outputSplit = QtWidgets.QCheckBox(self.optionWidget)
self.noDitheringBox.setObjectName("noDitheringBox") self.outputSplit.setObjectName("outputSplit")
self.gridLayout_2.addWidget(self.noDitheringBox, 2, 1, 1, 1) self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
self.colorBox = QtWidgets.QCheckBox(self.optionWidget) self.colorBox = QtWidgets.QCheckBox(self.optionWidget)
self.colorBox.setObjectName("colorBox") self.colorBox.setObjectName("colorBox")
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1) self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 3) self.gridLayout.addWidget(self.optionWidget, 4, 0, 1, 2)
self.gammaWidget = QtWidgets.QWidget(self.centralWidget) self.gammaWidget = QtWidgets.QWidget(self.centralWidget)
self.gammaWidget.setVisible(False) self.gammaWidget.setVisible(False)
self.gammaWidget.setObjectName("gammaWidget") self.gammaWidget.setObjectName("gammaWidget")
@@ -156,7 +120,7 @@ class Ui_mainWindow(object):
self.gammaSlider.setOrientation(QtCore.Qt.Horizontal) self.gammaSlider.setOrientation(QtCore.Qt.Horizontal)
self.gammaSlider.setObjectName("gammaSlider") self.gammaSlider.setObjectName("gammaSlider")
self.horizontalLayout_2.addWidget(self.gammaSlider) self.horizontalLayout_2.addWidget(self.gammaSlider)
self.gridLayout.addWidget(self.gammaWidget, 6, 0, 1, 3) self.gridLayout.addWidget(self.gammaWidget, 5, 0, 1, 2)
self.toolWidget = QtWidgets.QWidget(self.centralWidget) self.toolWidget = QtWidgets.QWidget(self.centralWidget)
self.toolWidget.setObjectName("toolWidget") self.toolWidget.setObjectName("toolWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.toolWidget) self.horizontalLayout = QtWidgets.QHBoxLayout(self.toolWidget)
@@ -164,19 +128,78 @@ class Ui_mainWindow(object):
self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setObjectName("horizontalLayout")
self.editorButton = QtWidgets.QPushButton(self.toolWidget) self.editorButton = QtWidgets.QPushButton(self.toolWidget)
self.editorButton.setMinimumSize(QtCore.QSize(0, 30)) self.editorButton.setMinimumSize(QtCore.QSize(0, 30))
icon5 = QtGui.QIcon() icon1 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon1.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.editorButton.setIcon(icon5) self.editorButton.setIcon(icon1)
self.editorButton.setObjectName("editorButton") self.editorButton.setObjectName("editorButton")
self.horizontalLayout.addWidget(self.editorButton) self.horizontalLayout.addWidget(self.editorButton)
self.wikiButton = QtWidgets.QPushButton(self.toolWidget) self.wikiButton = QtWidgets.QPushButton(self.toolWidget)
self.wikiButton.setMinimumSize(QtCore.QSize(0, 30)) self.wikiButton.setMinimumSize(QtCore.QSize(0, 30))
icon6 = QtGui.QIcon() icon2 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap(":/Other/icons/wiki.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon2.addPixmap(QtGui.QPixmap(":/Other/icons/wiki.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.wikiButton.setIcon(icon6) self.wikiButton.setIcon(icon2)
self.wikiButton.setObjectName("wikiButton") self.wikiButton.setObjectName("wikiButton")
self.horizontalLayout.addWidget(self.wikiButton) self.horizontalLayout.addWidget(self.wikiButton)
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 3) self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
self.buttonWidget = QtWidgets.QWidget(self.centralWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
self.buttonWidget.setSizePolicy(sizePolicy)
self.buttonWidget.setObjectName("buttonWidget")
self.gridLayout_4 = QtWidgets.QGridLayout(self.buttonWidget)
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.gridLayout_4.setObjectName("gridLayout_4")
self.directoryButton = QtWidgets.QPushButton(self.buttonWidget)
self.directoryButton.setMinimumSize(QtCore.QSize(0, 30))
icon3 = QtGui.QIcon()
icon3.addPixmap(QtGui.QPixmap(":/Other/icons/folder_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.directoryButton.setIcon(icon3)
self.directoryButton.setObjectName("directoryButton")
self.gridLayout_4.addWidget(self.directoryButton, 0, 0, 1, 1)
self.fileButton = QtWidgets.QPushButton(self.buttonWidget)
self.fileButton.setMinimumSize(QtCore.QSize(0, 30))
icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap(":/Other/icons/document_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.fileButton.setIcon(icon4)
self.fileButton.setObjectName("fileButton")
self.gridLayout_4.addWidget(self.fileButton, 0, 3, 1, 1)
self.deviceBox = QtWidgets.QComboBox(self.buttonWidget)
self.deviceBox.setMinimumSize(QtCore.QSize(0, 28))
self.deviceBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.deviceBox.setObjectName("deviceBox")
self.gridLayout_4.addWidget(self.deviceBox, 1, 0, 1, 1)
self.formatBox = QtWidgets.QComboBox(self.buttonWidget)
self.formatBox.setMinimumSize(QtCore.QSize(0, 28))
self.formatBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.formatBox.setObjectName("formatBox")
self.gridLayout_4.addWidget(self.formatBox, 1, 3, 1, 1)
self.convertButton = QtWidgets.QPushButton(self.buttonWidget)
self.convertButton.setMinimumSize(QtCore.QSize(0, 30))
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.convertButton.setFont(font)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.convertButton.setIcon(icon5)
self.convertButton.setObjectName("convertButton")
self.gridLayout_4.addWidget(self.convertButton, 1, 2, 1, 1)
self.clearButton = QtWidgets.QPushButton(self.buttonWidget)
self.clearButton.setMinimumSize(QtCore.QSize(0, 30))
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.clearButton.setIcon(icon6)
self.clearButton.setObjectName("clearButton")
self.gridLayout_4.addWidget(self.clearButton, 0, 2, 1, 1)
self.directoryButton.raise_()
self.clearButton.raise_()
self.fileButton.raise_()
self.deviceBox.raise_()
self.convertButton.raise_()
self.formatBox.raise_()
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
mainWindow.setCentralWidget(self.centralWidget) mainWindow.setCentralWidget(self.centralWidget)
self.statusBar = QtWidgets.QStatusBar(mainWindow) self.statusBar = QtWidgets.QStatusBar(mainWindow)
self.statusBar.setSizeGripEnabled(False) self.statusBar.setSizeGripEnabled(False)
@@ -185,19 +208,30 @@ class Ui_mainWindow(object):
self.retranslateUi(mainWindow) self.retranslateUi(mainWindow)
QtCore.QMetaObject.connectSlotsByName(mainWindow) QtCore.QMetaObject.connectSlotsByName(mainWindow)
mainWindow.setTabOrder(self.convertButton, self.clearButton)
mainWindow.setTabOrder(self.clearButton, self.directoryButton)
mainWindow.setTabOrder(self.directoryButton, self.fileButton)
mainWindow.setTabOrder(self.fileButton, self.deviceBox)
mainWindow.setTabOrder(self.deviceBox, self.formatBox)
mainWindow.setTabOrder(self.formatBox, self.mangaBox)
mainWindow.setTabOrder(self.mangaBox, self.rotateBox)
mainWindow.setTabOrder(self.rotateBox, self.qualityBox)
mainWindow.setTabOrder(self.qualityBox, self.webtoonBox)
mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox)
mainWindow.setTabOrder(self.upscaleBox, self.gammaBox)
mainWindow.setTabOrder(self.gammaBox, self.borderBox)
mainWindow.setTabOrder(self.borderBox, self.outputSplit)
mainWindow.setTabOrder(self.outputSplit, self.colorBox)
mainWindow.setTabOrder(self.colorBox, self.editorButton)
mainWindow.setTabOrder(self.editorButton, self.wikiButton)
mainWindow.setTabOrder(self.wikiButton, self.jobList)
mainWindow.setTabOrder(self.jobList, self.gammaSlider)
mainWindow.setTabOrder(self.gammaSlider, self.widthBox)
mainWindow.setTabOrder(self.widthBox, self.heightBox)
def retranslateUi(self, mainWindow): def retranslateUi(self, mainWindow):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
mainWindow.setWindowTitle(_translate("mainWindow", "Kindle Comic Converter")) mainWindow.setWindowTitle(_translate("mainWindow", "Kindle Comic Converter"))
self.directoryButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>"))
self.directoryButton.setText(_translate("mainWindow", "Add directory"))
self.clearButton.setText(_translate("mainWindow", "Clear list"))
self.fileButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>"))
self.fileButton.setText(_translate("mainWindow", "Add file"))
self.deviceBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>"))
self.convertButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>"))
self.convertButton.setText(_translate("mainWindow", "Convert"))
self.formatBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>"))
self.hLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>")) self.hLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
self.hLabel.setText(_translate("mainWindow", "Custom height:")) self.hLabel.setText(_translate("mainWindow", "Custom height:"))
self.widthBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>")) self.widthBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of target device.</p></body></html>"))
@@ -208,8 +242,8 @@ class Ui_mainWindow(object):
self.mangaBox.setText(_translate("mainWindow", "Manga mode")) self.mangaBox.setText(_translate("mainWindow", "Manga mode"))
self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>")) self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>"))
self.rotateBox.setText(_translate("mainWindow", "Spread splitter")) self.rotateBox.setText(_translate("mainWindow", "Spread splitter"))
self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>High quality Panel View.<br/>Require source files with bigger resolution than target device.<br/><span style=\" font-weight:600;\">Highly impact size of output file!</span></p></body></html>")) self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>"))
self.qualityBox.setText(_translate("mainWindow", "HQ zoom")) self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2/HQ"))
self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>")) self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>"))
self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode")) self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode"))
self.upscaleBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>")) self.upscaleBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>"))
@@ -218,12 +252,21 @@ class Ui_mainWindow(object):
self.gammaBox.setText(_translate("mainWindow", "Custom gamma")) self.gammaBox.setText(_translate("mainWindow", "Custom gamma"))
self.borderBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>")) self.borderBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>"))
self.borderBox.setText(_translate("mainWindow", "W/B margins")) self.borderBox.setText(_translate("mainWindow", "W/B margins"))
self.noDitheringBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Create PNG files instead JPEG.<br/>Quality increase is not noticeable on most of devices.<br/>Output files <span style=\" font-weight:600;\">might</span> be smaller.<br/><span style=\" font-weight:600;\">MOBI conversion will be much slower.</span></p></body></html>")) self.outputSplit.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>Output will be splitted automatically.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as separate volume.</p></body></html>"))
self.noDitheringBox.setText(_translate("mainWindow", "PNG output")) self.outputSplit.setText(_translate("mainWindow", "Output split"))
self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>")) self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>"))
self.colorBox.setText(_translate("mainWindow", "Color mode")) self.colorBox.setText(_translate("mainWindow", "Color mode"))
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto")) self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))
self.editorButton.setText(_translate("mainWindow", "Editor")) self.editorButton.setText(_translate("mainWindow", "Editor"))
self.wikiButton.setText(_translate("mainWindow", "Wiki")) self.wikiButton.setText(_translate("mainWindow", "Wiki"))
self.directoryButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>"))
self.directoryButton.setText(_translate("mainWindow", "Add directory"))
self.fileButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>"))
self.fileButton.setText(_translate("mainWindow", "Add file"))
self.deviceBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>"))
self.formatBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>"))
self.convertButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>"))
self.convertButton.setText(_translate("mainWindow", "Convert"))
self.clearButton.setText(_translate("mainWindow", "Clear list"))
from . import KCC_rc from . import KCC_rc

View File

@@ -1,4 +1,4 @@
__version__ = '5.1' __version__ = '5.4'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2016, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __copyright__ = '2012-2017, 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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 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,18 +16,13 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
import sys
import os import os
from zipfile import is_zipfile, ZipFile from zipfile import is_zipfile, ZipFile
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from psutil import Popen from psutil import Popen
from shutil import move, copy from shutil import move
try:
from scandir import walk
except ImportError:
walk = os.walk
from . import rarfile from . import rarfile
from .shared import check7ZFile as is_7zfile, saferReplace from .shared import check7ZFile as is_7zfile
class CBxArchive: class CBxArchive:
@@ -50,12 +45,12 @@ class CBxArchive:
filelist = [] filelist = []
for f in cbzFile.namelist(): for f in cbzFile.namelist():
if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('humbs.db'): if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('humbs.db'):
pass # skip MacOS special files pass
elif f.endswith('/'): elif f.endswith('/'):
try: try:
os.makedirs(os.path.join(targetdir, f)) os.makedirs(os.path.join(targetdir, f))
except Exception: except Exception:
pass # the dir exists so we are going to extract the images only. pass
else: else:
filelist.append(f) filelist.append(f)
cbzFile.extractall(targetdir, filelist) cbzFile.extractall(targetdir, filelist)
@@ -63,24 +58,18 @@ class CBxArchive:
def extractCBR(self, targetdir): def extractCBR(self, targetdir):
cbrFile = rarfile.RarFile(self.origFileName) cbrFile = rarfile.RarFile(self.origFileName)
cbrFile.extractall(targetdir) cbrFile.extractall(targetdir)
for root, dirnames, filenames in walk(targetdir): for root, _, filenames in os.walk(targetdir):
for filename in filenames: for filename in filenames:
if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'): if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'):
os.remove(os.path.join(root, filename)) os.remove(os.path.join(root, filename))
def extractCB7(self, targetdir): def extractCB7(self, targetdir):
# Workaround for some wide UTF-8 + Popen abnormalities
if sys.platform.startswith('darwin'):
copy(self.origFileName, os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP'))
self.origFileName = os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP')
output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' +
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
extracted = False extracted = False
for line in output.stdout: for line in output.stdout:
if b"Everything is Ok" in line: if b"Everything is Ok" in line:
extracted = True extracted = True
if sys.platform.startswith('darwin'):
os.remove(self.origFileName)
if not extracted: if not extracted:
raise OSError raise OSError
@@ -96,10 +85,6 @@ class CBxArchive:
adir.remove('ComicInfo.xml') adir.remove('ComicInfo.xml')
if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])): if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])):
for f in os.listdir(os.path.join(targetdir, adir[0])): for f in os.listdir(os.path.join(targetdir, adir[0])):
# If directory names contain UTF-8 chars shutil.move can't clean up the mess alone
if os.path.isdir(os.path.join(targetdir, f)):
saferReplace(os.path.join(targetdir, adir[0], f), os.path.join(targetdir, adir[0], f + '-A'))
f += '-A'
move(os.path.join(targetdir, adir[0], f), targetdir) 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]))
return targetdir return targetdir

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 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
@@ -36,17 +36,13 @@ 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, disk_usage
from html import escape from html import escape
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
QtCore = None QtCore = None
try: from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace
from scandir import walk
except ImportError:
walk = os.walk
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, saferReplace, saferRemove
from . import comic2panel from . import comic2panel
from . import image from . import image
from . import cbxarchive from . import cbxarchive
@@ -64,7 +60,10 @@ def main(argv=None):
if len(args) == 0: if len(args) == 0:
parser.print_help() parser.print_help()
return 0 return 0
if sys.platform.startswith('win'):
sources = set([source for arg in args for source in glob(arg)]) sources = set([source for arg in args for source in glob(arg)])
else:
sources = set(args)
if len(sources) == 0: if len(sources) == 0:
print('No matching files found.') print('No matching files found.')
return 1 return 1
@@ -82,18 +81,15 @@ def buildHTML(path, imgfile, imgfilepath):
imgfilepath = md5Checksum(imgfilepath) imgfilepath = md5Checksum(imgfilepath)
filename = getImageFileName(imgfile) filename = getImageFileName(imgfile)
deviceres = options.profileData[1] deviceres = options.profileData[1]
if "Rotated" in options.imgIndex[imgfilepath]: if "Rotated" in options.imgMetadata[imgfilepath]:
rotatedPage = True rotatedPage = True
else: else:
rotatedPage = False rotatedPage = False
if "BlackFill" in options.imgIndex[imgfilepath]: if "BlackBackground" in options.imgMetadata[imgfilepath]:
additionalStyle = 'background-color:#000000;' additionalStyle = 'background-color:#000000;'
else: else:
additionalStyle = 'background-color:#FFFFFF;' additionalStyle = 'background-color:#FFFFFF;'
htmlpath = ''
postfix = '' postfix = ''
size = ''
imgfilepv = ''
backref = 1 backref = 1
head = path head = path
while True: while True:
@@ -106,6 +102,11 @@ def buildHTML(path, imgfile, imgfilepath):
if not os.path.exists(htmlpath): if not os.path.exists(htmlpath):
os.makedirs(htmlpath) os.makedirs(htmlpath)
htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml') htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml')
imgsize = Image.open(os.path.join(head, "Images", postfix, imgfile)).size
if options.hq:
imgsizeframe = deviceres
else:
imgsizeframe = imgsize
f = open(htmlfile, "w", encoding='UTF-8') f = open(htmlfile, "w", encoding='UTF-8')
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<!DOCTYPE html>\n", "<!DOCTYPE html>\n",
@@ -114,26 +115,25 @@ def buildHTML(path, imgfile, imgfilepath):
"<title>", escape(filename[0]), "</title>\n", "<title>", escape(filename[0]), "</title>\n",
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n", "<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"<meta name=\"viewport\" " "<meta name=\"viewport\" "
"content=\"width=" + str(deviceres[0]) + ", height=" + str(deviceres[1]) + "\"/>\n" "content=\"width=" + str(imgsize[0]) + ", height=" + str(imgsize[1]) + "\"/>\n"
"</head>\n", "</head>\n",
"<body style=\"background-image: ", "<body style=\"" + additionalStyle + "\">\n",
"url('", "../" * backref, "Images/", postfix, imgfile, "'); " + additionalStyle + "\">\n"]) "<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsize) + "%;\">\n",
"<img width=\"" + str(imgsizeframe[0]) + "\" height=\"" + str(imgsizeframe[1]) + "\" ",
"src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n</div>\n"])
if options.iskindle and options.panelview: if options.iskindle and options.panelview:
if options.hqmode: if options.autoscale:
imgfilepv = list(os.path.splitext(imgfile)) size = (getPanelViewResolution(imgsize, deviceres))
imgfilepv[0] += "-hq" else:
imgfilepv = "".join(imgfilepv) if options.hq:
if os.path.isfile(os.path.join(head, "Images", postfix, imgfilepv)): size = imgsize
size = Image.open(os.path.join(head, "Images", postfix, imgfilepv)).size else:
if not options.hqmode or not size: size = (int(imgsize[0] * 1.5), int(imgsize[1] * 1.5))
imgfilepv = imgfile if size[0] - deviceres[0] < deviceres[0] * 0.01:
sizeTmp = Image.open(os.path.join(head, "Images", postfix, imgfilepv)).size
size = (int(sizeTmp[0] * 1.5), int(sizeTmp[1] * 1.5))
if size[0] <= deviceres[0]:
noHorizontalPV = True noHorizontalPV = True
else: else:
noHorizontalPV = False noHorizontalPV = False
if size[1] <= deviceres[1]: if size[1] - deviceres[1] < deviceres[1] * 0.01:
noVerticalPV = True noVerticalPV = True
else: else:
noVerticalPV = False noVerticalPV = False
@@ -190,7 +190,7 @@ def buildHTML(path, imgfile, imgfilepath):
for box in boxes: for box in boxes:
f.writelines(["<div class=\"PV-P\" id=\"" + box + "-P\" style=\"" + additionalStyle + "\">\n", f.writelines(["<div class=\"PV-P\" id=\"" + box + "-P\" style=\"" + additionalStyle + "\">\n",
"<img style=\"" + boxStyles[box] + "\" src=\"", "../" * backref, "Images/", postfix, "<img style=\"" + boxStyles[box] + "\" src=\"", "../" * backref, "Images/", postfix,
imgfilepv, "\" width=\"" + str(size[0]) + "\" height=\"" + str(size[1]) + "\"/>\n", imgfile, "\" width=\"" + str(size[0]) + "\" height=\"" + str(size[1]) + "\"/>\n",
"</div>\n"]) "</div>\n"])
f.writelines(["</body>\n", f.writelines(["</body>\n",
"</html>\n"]) "</html>\n"])
@@ -333,29 +333,6 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
else: else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
# if options.iskindle and options.profile != 'Custom':
# if options.righttoleft:
# nextflow = 'right'
# else:
# nextflow = 'left'
# for entry in reflist:
# if '-kcc-b' in entry:
# if options.righttoleft:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-right\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-left\"/>\n")
# elif '-kcc-c' in entry:
# if options.righttoleft:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-left\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-right\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"facing-page-" + nextflow + "\"/>\n")
# if nextflow == 'right':
# nextflow = 'left'
# else:
# nextflow = 'right'
# else:
for entry in reflist: for entry in reflist:
f.write("<itemref idref=\"page_" + entry + "\"/>\n") f.write("<itemref idref=\"page_" + entry + "\"/>\n")
f.write("</spine>\n</package>\n") f.write("</spine>\n</package>\n")
@@ -384,9 +361,6 @@ def buildEPUB(path, chapterNames, tomeNumber):
"display: block;\n", "display: block;\n",
"margin: 0;\n", "margin: 0;\n",
"padding: 0;\n", "padding: 0;\n",
"background-position: center center;\n",
"background-repeat: no-repeat;\n",
"background-size: auto auto;\n",
"}\n", "}\n",
"#PV {\n", "#PV {\n",
"position: absolute;\n", "position: absolute;\n",
@@ -453,12 +427,10 @@ def buildEPUB(path, chapterNames, tomeNumber):
"display: none;\n", "display: none;\n",
"}\n"]) "}\n"])
f.close() f.close()
for (dirpath, dirnames, filenames) in walk(os.path.join(path, 'OEBPS', 'Images')): for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
chapter = False chapter = False
dirnames, filenames = walkSort(dirnames, filenames) dirnames, filenames = walkSort(dirnames, filenames)
for afile in filenames: for afile in filenames:
filename = getImageFileName(afile)
if not filename[0].endswith('-hq'):
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile))) filelist.append(buildHTML(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]))
@@ -490,13 +462,13 @@ def buildEPUB(path, chapterNames, tomeNumber):
def imgDirectoryProcessing(path): def imgDirectoryProcessing(path):
global workerPool, workerOutput global workerPool, workerOutput
workerPool = Pool() workerPool = Pool(maxtasksperchild=100)
workerOutput = [] workerOutput = []
options.imgIndex = {} options.imgMetadata = {}
options.imgPurgeIndex = [] options.imgOld = []
work = [] work = []
pagenumber = 0 pagenumber = 0
for (dirpath, dirnames, filenames) in walk(path): for dirpath, _, filenames in os.walk(path):
for afile in filenames: for afile in filenames:
pagenumber += 1 pagenumber += 1
work.append([afile, dirpath, options]) work.append([afile, dirpath, options])
@@ -512,24 +484,24 @@ def imgDirectoryProcessing(path):
raise UserWarning("Conversion interrupted.") raise UserWarning("Conversion interrupted.")
if len(workerOutput) > 0: if len(workerOutput) > 0:
rmtree(os.path.join(path, '..', '..'), True) rmtree(os.path.join(path, '..', '..'), True)
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0]) raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
for file in options.imgPurgeIndex: for file in options.imgOld:
if os.path.isfile(file): if os.path.isfile(file):
saferRemove(file) os.remove(file)
else: else:
rmtree(os.path.join(path, '..', '..'), True) rmtree(os.path.join(path, '..', '..'), True)
raise UserWarning("Source directory is empty.") raise UserWarning("Source directory is empty.")
def imgFileProcessingTick(output): def imgFileProcessingTick(output):
if isinstance(output, str): if isinstance(output, tuple):
workerOutput.append(output) workerOutput.append(output)
workerPool.terminate() workerPool.terminate()
else: else:
for page in output: for page in output:
if page is not None: if page is not None:
options.imgIndex[page[0]] = page[1] options.imgMetadata[page[0]] = page[1]
options.imgPurgeIndex.append(page[2]) options.imgOld.append(page[2])
if GUI: if GUI:
GUI.progressBarTick.emit('tick') GUI.progressBarTick.emit('tick')
if not GUI.conversionAlive: if not GUI.conversionAlive:
@@ -544,11 +516,11 @@ def imgFileProcessing(work):
output = [] output = []
workImg = image.ComicPageParser((dirpath, afile), opt) workImg = image.ComicPageParser((dirpath, afile), opt)
for i in workImg.payload: for i in workImg.payload:
img = image.ComicPage(i[0], i[1], i[2], i[3], i[4], opt) img = image.ComicPage(opt, *i)
if opt.cropping > 0 and not opt.webtoon:
img.cropWhiteSpace(opt.croppingp)
if opt.cropping == 2 and not opt.webtoon: if opt.cropping == 2 and not opt.webtoon:
img.cutPageNumber(opt.croppingpn) img.cropPageNumber(opt.croppingp)
if opt.cropping > 0 and not opt.webtoon:
img.cropMargin(opt.croppingp)
img.autocontrastImage() img.autocontrastImage()
img.resizeImage() img.resizeImage()
if opt.forcepng and not opt.forcecolor: if opt.forcepng and not opt.forcecolor:
@@ -556,11 +528,13 @@ def imgFileProcessing(work):
output.append(img.saveToDir()) output.append(img.saveToDir())
return output return output
except Exception: except Exception:
return str(sys.exc_info()[:2]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def getWorkFolder(afile): def getWorkFolder(afile):
if os.path.isdir(afile): if os.path.isdir(afile):
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
workdir = mkdtemp('', 'KCC-') workdir = mkdtemp('', 'KCC-')
try: try:
os.rmdir(workdir) os.rmdir(workdir)
@@ -571,13 +545,16 @@ def getWorkFolder(afile):
except: except:
rmtree(workdir, True) rmtree(workdir, True)
raise UserWarning("Failed to prepare a workspace.") raise UserWarning("Failed to prepare a workspace.")
elif os.path.isfile(afile) and afile.lower().endswith('.pdf'): elif os.path.isfile(afile):
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
if afile.lower().endswith('.pdf'):
pdf = pdfjpgextract.PdfJpgExtract(afile) pdf = pdfjpgextract.PdfJpgExtract(afile)
path, njpg = pdf.extract() path, njpg = pdf.extract()
if njpg == 0: if njpg == 0:
rmtree(path, True) rmtree(path, True)
raise UserWarning("Failed to extract images from PDF file.") raise UserWarning("Failed to extract images from PDF file.")
elif os.path.isfile(afile): else:
workdir = mkdtemp('', 'KCC-') workdir = mkdtemp('', 'KCC-')
cbx = cbxarchive.CBxArchive(afile) cbx = cbxarchive.CBxArchive(afile)
if cbx.isCbxFile(): if cbx.isCbxFile():
@@ -694,13 +671,23 @@ def getCoversFromMCB(mangaID):
def getDirectorySize(start_path='.'): def getDirectorySize(start_path='.'):
total_size = 0 total_size = 0
for dirpath, dirnames, filenames in walk(start_path): for dirpath, _, filenames in os.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)
return total_size return total_size
def getTopMargin(deviceres, size):
y = int((deviceres[1] - size[1]) / 2) / deviceres[1] * 100
return str(round(y, 1))
def getPanelViewResolution(imageSize, deviceRes):
scale = float(deviceRes[0]) / float(imageSize[0])
return int(deviceRes[0]), int(scale * imageSize[1])
def getPanelViewSize(deviceres, size): def getPanelViewSize(deviceres, size):
x = int(deviceres[0] / 2 - size[0] / 2) / deviceres[0] * 100 x = int(deviceres[0] / 2 - size[0] / 2) / deviceres[0] * 100
y = int(deviceres[1] / 2 - size[1] / 2) / deviceres[1] * 100 y = int(deviceres[1] / 2 - size[1] / 2) / deviceres[1] * 100
@@ -709,7 +696,7 @@ def getPanelViewSize(deviceres, size):
def sanitizeTree(filetree): def sanitizeTree(filetree):
chapterNames = {} chapterNames = {}
for root, dirs, files in walk(filetree, False): for root, dirs, files in os.walk(filetree, False):
for name in files: for name in files:
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
slugified = slugify(splitname[0]) slugified = slugify(splitname[0])
@@ -719,7 +706,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:
saferReplace(key, newKey) os.replace(key, newKey)
for name in dirs: for name in dirs:
tmpName = name tmpName = name
slugified = slugify(name) slugified = slugify(name)
@@ -729,13 +716,13 @@ 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:
saferReplace(key, newKey) os.replace(key, newKey)
return chapterNames return chapterNames
def sanitizeTreeKobo(filetree): def sanitizeTreeKobo(filetree):
pageNumber = 0 pageNumber = 0
for root, dirs, files in walk(filetree): for root, dirs, files in os.walk(filetree):
dirs, files = walkSort(dirs, files) dirs, files = walkSort(dirs, files)
for name in files: for name in files:
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
@@ -747,74 +734,36 @@ 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:
saferReplace(key, newKey) os.replace(key, newKey)
def sanitizePermissions(filetree): def sanitizePermissions(filetree):
for root, dirs, files in walk(filetree, False): for root, dirs, files in os.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:
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC) os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
# noinspection PyUnboundLocalVariable
def splitDirectory(path): def splitDirectory(path):
# Detect directory stucture level = -1
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 0): for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
subdirectoryNumber = len(dirs) for f in files:
filesNumber = len(files) if f.endswith('.jpg') or f.endswith('.jpeg') or f.endswith('.png') or f.endswith('.gif'):
if subdirectoryNumber == 0: newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
# No subdirectories if level != -1 and level != newLevel:
mode = 0 level = 0
break
else: else:
if filesNumber > 0: level = newLevel
print('WARNING: Automatic output splitting failed.') if level > 0:
if GUI: splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), level)
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning', False)
GUI.addMessage.emit('', '', False)
return [path]
detectedSubSubdirectories = False
detectedFilesInSubdirectories = False
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 1):
if root != os.path.join(path, 'OEBPS', 'Images'):
if len(dirs) != 0:
detectedSubSubdirectories = True
elif len(dirs) == 0 and detectedSubSubdirectories:
print('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning', False)
GUI.addMessage.emit('', '', False)
return [path]
if len(files) != 0:
detectedFilesInSubdirectories = True
if detectedSubSubdirectories:
# Two levels of subdirectories
mode = 2
else:
# One level of subdirectories
mode = 1
if detectedFilesInSubdirectories and detectedSubSubdirectories:
print('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning', False)
GUI.addMessage.emit('', '', False)
return [path]
# Split directories
splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), mode)
path = [path] path = [path]
for tome in splitter: for tome in splitter:
path.append(tome) path.append(tome)
return path return path
else:
raise UserWarning('Unsupported directory structure.')
def splitProcess(path, mode): def splitProcess(path, mode):
@@ -825,21 +774,14 @@ def splitProcess(path, mode):
targetSize = 104857600 targetSize = 104857600
else: else:
targetSize = 419430400 targetSize = 419430400
if mode == 0: if options.batchsplit == 2 and mode == 2:
mode = 3
if mode < 3:
for root, dirs, files in walkLevel(path, 0): for root, dirs, files in walkLevel(path, 0):
for name in files: for name in files if mode == 1 else dirs:
if mode == 1:
size = os.path.getsize(os.path.join(root, name)) size = os.path.getsize(os.path.join(root, name))
if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome()
output.append(pathRoot)
currentSize = size
else: else:
currentSize += size
if path != currentTarget:
move(os.path.join(root, name), os.path.join(currentTarget, name))
elif mode == 1:
for root, dirs, files in walkLevel(path, 0):
for name in dirs:
size = getDirectorySize(os.path.join(root, name)) size = getDirectorySize(os.path.join(root, name))
if currentSize + size > targetSize: if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome() currentTarget, pathRoot = createNewTome()
@@ -849,30 +791,10 @@ def splitProcess(path, mode):
currentSize += size currentSize += size
if path != currentTarget: if path != currentTarget:
move(os.path.join(root, name), os.path.join(currentTarget, name)) move(os.path.join(root, name), os.path.join(currentTarget, name))
elif mode == 2: else:
firstTome = True firstTome = True
for root, dirs, files in walkLevel(path, 0): for root, dirs, _ in walkLevel(path, 0):
for name in dirs: for name in dirs:
size = getDirectorySize(os.path.join(root, name))
currentSize = 0
if size > targetSize:
if not firstTome:
currentTarget, pathRoot = createNewTome()
output.append(pathRoot)
else:
firstTome = False
for rootInside, dirsInside, filesInside in walkLevel(os.path.join(root, name), 0):
for nameInside in dirsInside:
size = getDirectorySize(os.path.join(rootInside, nameInside))
if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome()
output.append(pathRoot)
currentSize = size
else:
currentSize += size
if path != currentTarget:
move(os.path.join(rootInside, nameInside), os.path.join(currentTarget, nameInside))
else:
if not firstTome: if not firstTome:
currentTarget, pathRoot = createNewTome() currentTarget, pathRoot = createNewTome()
output.append(pathRoot) output.append(pathRoot)
@@ -885,9 +807,12 @@ def splitProcess(path, mode):
def detectCorruption(tmpPath, orgPath): def detectCorruption(tmpPath, orgPath):
imageNumber = 0 imageNumber = 0
imageSmaller = 0 imageSmaller = 0
for root, dirs, files in walk(tmpPath, False): alreadyProcessed = False
for root, _, files in os.walk(tmpPath, False):
for name in files: for name in files:
if getImageFileName(name) is not None: if getImageFileName(name) is not None:
if not alreadyProcessed and getImageFileName(name)[0].endswith('-kcc'):
alreadyProcessed = True
path = os.path.join(root, name) path = os.path.join(root, name)
pathOrg = orgPath + path.split('OEBPS' + os.path.sep + 'Images')[1] pathOrg = orgPath + path.split('OEBPS' + os.path.sep + 'Images')[1]
if os.path.getsize(path) == 0: if os.path.getsize(path) == 0:
@@ -908,12 +833,18 @@ def detectCorruption(tmpPath, orgPath):
else: else:
raise RuntimeError('Image file %s is corrupted.' % pathOrg) raise RuntimeError('Image file %s is corrupted.' % pathOrg)
else: else:
saferRemove(os.path.join(root, name)) os.remove(os.path.join(root, name))
if alreadyProcessed:
print("WARNING: Source files are probably created by KCC. Second conversion will decrease quality.")
if GUI:
GUI.addMessage.emit('Source files are probably created by KCC. Second conversion will decrease quality.',
'warning', False)
GUI.addMessage.emit('', '', False)
if imageSmaller > imageNumber * 0.25 and not options.upscale and not options.stretch: if imageSmaller > imageNumber * 0.25 and not options.upscale and not options.stretch:
print("WARNING: More than 1/4 of images are smaller than target device resolution. " print("WARNING: More than 25% of images are smaller than target device resolution. "
"Consider enabling stretching or upscaling to improve readability.") "Consider enabling stretching or upscaling to improve readability.")
if GUI: if GUI:
GUI.addMessage.emit('More than 1/4 of images are smaller than target device resolution.', 'warning', False) GUI.addMessage.emit('More than 25% of images are smaller than target device resolution.', 'warning', False)
GUI.addMessage.emit('Consider enabling stretching or upscaling to improve readability.', 'warning', False) GUI.addMessage.emit('Consider enabling stretching or upscaling to improve readability.', 'warning', False)
GUI.addMessage.emit('', '', False) GUI.addMessage.emit('', '', False)
@@ -936,7 +867,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 walk(baseDir): for dirpath, _, filenames in os.walk(baseDir):
for name in filenames: for name in filenames:
path = os.path.normpath(os.path.join(dirpath, name)) path = os.path.normpath(os.path.join(dirpath, name))
aPath = os.path.normpath(os.path.join(dirpath.replace(baseDir, ''), name)) aPath = os.path.normpath(os.path.join(dirpath.replace(baseDir, ''), name))
@@ -957,9 +888,13 @@ def makeParser():
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV", mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV",
help="Device profile (Available options: K1, K2, K3, K45, KDX, KPW, KV, KoMT, KoG, KoGHD," help="Device profile (Available options: K1, K2, K3, K45, KDX, KPW, KV, KoMT, KoG, KoGHD,"
" KoA, KoAHD, KoAH2O) [Default=KV]") " KoA, KoAHD, KoAH2O, KoAO) [Default=KV]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (right-to-left reading and splitting)") help="Manga style (right-to-left reading and splitting)")
mainOptions.add_option("-q", "--hq", action="store_true", dest="hq", default=False,
help="Try to increase the quality of magnification")
mainOptions.add_option("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
help="Display two not four panels in Panel View mode")
mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False, mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
help="Webtoon processing mode"), help="Webtoon processing mode"),
@@ -969,8 +904,9 @@ def makeParser():
help="Comic title [Default=filename or directory name]") help="Comic title [Default=filename or directory name]")
outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto", outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto",
help="Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]") help="Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]")
outputOptions.add_option("-b", "--batchsplit", action="store_true", dest="batchsplit", default=False, outputOptions.add_option("-b", "--batchsplit", type="int", dest="batchsplit", default="0",
help="Split output into multiple files"), help="Split output into multiple files. 0: Don't split 1: Automatic mode "
"2: Consider every subdirectory as separate volume [Default=0]")
processingOptions.add_option("-u", "--upscale", action="store_true", dest="upscale", default=False, processingOptions.add_option("-u", "--upscale", action="store_true", dest="upscale", default=False,
help="Resize images smaller than device's resolution") help="Resize images smaller than device's resolution")
@@ -980,8 +916,10 @@ def makeParser():
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]") help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
processingOptions.add_option("-g", "--gamma", type="float", dest="gamma", default="0.0", processingOptions.add_option("-g", "--gamma", type="float", dest="gamma", default="0.0",
help="Apply gamma correction to linearize the image [Default=Auto]") help="Apply gamma correction to linearize the image [Default=Auto]")
processingOptions.add_option("--hq", action="store_true", dest="hqmode", default=False, processingOptions.add_option("-c", "--cropping", type="int", dest="cropping", default="2",
help="Enable high quality Panel View") help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
processingOptions.add_option("--cp", "--croppingpower", type="float", dest="croppingp", default="1.0",
help="Set cropping power [Default=1.0]")
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False, processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
help="Disable autodetection and force black borders") help="Disable autodetection and force black borders")
processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False, processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False,
@@ -990,12 +928,6 @@ def makeParser():
help="Don't convert images to grayscale") help="Don't convert images to grayscale")
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False, processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG") help="Create PNG files instead JPEG")
processingOptions.add_option("--cropping", type="int", dest="cropping", default="2",
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
processingOptions.add_option("--croppingpower", type="float", dest="croppingp", default="0.1",
help="Set margin cropping threshold [Default=0.1]")
processingOptions.add_option("--croppingpowerpage", type="float", dest="croppingpn", default="5.0",
help="Set page number cropping threshold [Default=5.0]")
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0, customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
help="Replace screen width provided by device profile") help="Replace screen width provided by device profile")
@@ -1021,7 +953,7 @@ def checkOptions():
if options.format == 'Auto': if options.format == 'Auto':
if options.profile in ['K1', 'K2', 'K3', 'K45', 'KPW', 'KV']: if options.profile in ['K1', 'K2', 'K3', 'K45', 'KPW', 'KV']:
options.format = 'MOBI' options.format = 'MOBI'
elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O']: elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O', 'KoAO']:
options.format = 'EPUB' options.format = 'EPUB'
elif options.profile in ['KDX']: elif options.profile in ['KDX']:
options.format = 'CBZ' options.format = 'CBZ'
@@ -1032,25 +964,25 @@ def checkOptions():
if options.black_borders: if options.black_borders:
options.bordersColor = 'black' options.bordersColor = 'black'
# Splitting MOBI is not optional # Splitting MOBI is not optional
if options.format == 'MOBI': if options.format == 'MOBI' and options.batchsplit != 2:
options.batchsplit = True options.batchsplit = 1
# Older Kindle don't need higher resolution files due lack of Panel View. # Older Kindle models don't support Panel View.
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'K3' or options.profile == 'KDX': if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX':
options.panelview = False options.panelview = False
options.hqmode = False options.hq = False
# Webtoon mode mandatory options # Webtoon mode mandatory options
if options.webtoon: if options.webtoon:
options.panelview = False options.panelview = False
options.hqmode = False
options.righttoleft = False options.righttoleft = False
options.upscale = True options.upscale = True
options.hq = False
# Disable all Kindle features for other e-readers # Disable all Kindle features for other e-readers
if options.profile == 'OTHER': if options.profile == 'OTHER':
options.panelview = False options.panelview = False
options.hqmode = False options.hq = False
if 'Ko' in options.profile: if 'Ko' in options.profile:
options.panelview = False options.panelview = False
options.hqmode = False options.hq = False
# CBZ files on Kindle DX/DXG support higher resolution # CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.format == 'CBZ': if options.profile == 'KDX' and options.format == 'CBZ':
options.customheight = 1200 options.customheight = 1200
@@ -1063,7 +995,7 @@ def checkOptions():
if options.customheight != 0: if options.customheight != 0:
Y = options.customheight Y = options.customheight
newProfile = ("Custom", (int(X), int(Y)), image.ProfileData.Palette16, newProfile = ("Custom", (int(X), int(Y)), image.ProfileData.Palette16,
image.ProfileData.Profiles[options.profile][3], (int(int(X) * 1.5), int(int(Y) * 1.5))) image.ProfileData.Profiles[options.profile][3])
image.ProfileData.Profiles["Custom"] = newProfile image.ProfileData.Profiles["Custom"] = newProfile
options.profile = "Custom" options.profile = "Custom"
options.profileData = image.ProfileData.Profiles[options.profile] options.profileData = image.ProfileData.Profiles[options.profile]
@@ -1122,8 +1054,8 @@ 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 image.ProfileData.Profiles[options.profile][1][1] > 1000: if image.ProfileData.Profiles[options.profile][1][1] > 1024:
y = 1000 y = 1024
else: else:
y = image.ProfileData.Profiles[options.profile][1][1] y = image.ProfileData.Profiles[options.profile][1][1]
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtGUI) comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtGUI)
@@ -1136,7 +1068,7 @@ def makeBook(source, qtGUI=None):
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images')) chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
if 'Ko' in options.profile and options.format == 'CBZ': if 'Ko' in options.profile and options.format == 'CBZ':
sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images')) sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images'))
if options.batchsplit: if options.batchsplit > 0:
tomes = splitDirectory(path) tomes = splitDirectory(path)
else: else:
tomes = [path] tomes = [path]
@@ -1181,7 +1113,6 @@ def makeBook(source, qtGUI=None):
if not GUI and options.format == 'MOBI': if not GUI and options.format == 'MOBI':
print("Creating MOBI files...") print("Creating MOBI files...")
work = [] work = []
k = kindle.Kindle()
for i in filepath: for i in filepath:
work.append([i]) work.append([i])
output = makeMOBI(work, GUI) output = makeMOBI(work, GUI)
@@ -1190,6 +1121,7 @@ def makeBook(source, qtGUI=None):
print('Error: KindleGen failed to create MOBI!') print('Error: KindleGen failed to create MOBI!')
print(errors) print(errors)
return filepath return filepath
k = kindle.Kindle()
if k.path and k.coverSupport: if k.path and k.coverSupport:
print("Kindle detected. Uploading covers...") print("Kindle detected. Uploading covers...")
for i in filepath: for i in filepath:
@@ -1266,9 +1198,11 @@ def makeMOBI(work, qtGUI=None):
threadNumber = 1 threadNumber = 1
elif 2 < availableMemory <= 4: elif 2 < availableMemory <= 4:
threadNumber = 2 threadNumber = 2
else: elif 4 < availableMemory <= 8:
threadNumber = 4 threadNumber = 4
makeMOBIWorkerPool = Pool(threadNumber) else:
threadNumber = None
makeMOBIWorkerPool = Pool(threadNumber, maxtasksperchild=10)
for i in work: for i in work:
makeMOBIWorkerPool.apply_async(func=makeMOBIWorker, args=(i, ), callback=makeMOBIWorkerTick) makeMOBIWorkerPool.apply_async(func=makeMOBIWorker, args=(i, ), callback=makeMOBIWorkerTick)
makeMOBIWorkerPool.close() makeMOBIWorkerPool.close()

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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re>
# #
# Permission to use, copy, modify, and/or distribute this software for # Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the # any purpose with or without fee is hereby granted, provided that the
@@ -23,16 +23,12 @@ 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, ImageOps from PIL import Image, ImageChops, ImageOps, ImageDraw
from .shared import getImageFileName, walkLevel, walkSort from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
QtCore = None QtCore = None
try:
from scandir import walk
except ImportError:
walk = os.walk
def mergeDirectoryTick(output): def mergeDirectoryTick(output):
@@ -52,7 +48,7 @@ def mergeDirectory(work):
imagesValid = [] imagesValid = []
sizes = [] sizes = []
targetHeight = 0 targetHeight = 0
for root, dirs, files in walkLevel(directory, 0): for root, _, 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:
i = Image.open(os.path.join(root, name)) i = Image.open(os.path.join(root, name))
@@ -71,8 +67,7 @@ def mergeDirectory(work):
result = Image.new('RGB', (targetWidth, targetHeight)) result = Image.new('RGB', (targetWidth, targetHeight))
y = 0 y = 0
for i in imagesValid: for i in imagesValid:
img = Image.open(i) img = Image.open(i).convert('RGB')
img = img.convert('RGB')
if img.size[0] < targetWidth: if img.size[0] < targetWidth:
img = ImageOps.fit(img, (targetWidth, img.size[1]), method=Image.BICUBIC, centering=(0.5, 0.5)) 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))
@@ -81,33 +76,11 @@ def mergeDirectory(work):
savePath = os.path.split(imagesValid[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]), sanitizeTrace(sys.exc_info()[2])
def sanitizePanelSize(panel, opt): def detectSolid(img):
newPanels = [] return not ImageChops.invert(img).getbbox() or not img.getbbox()
if panel[2] > 6 * opt.height:
diff = int(panel[2] / 8)
newPanels.append([panel[0], panel[1] - diff * 7, diff])
newPanels.append([panel[1] - diff * 7, panel[1] - diff * 6, diff])
newPanels.append([panel[1] - diff * 6, panel[1] - diff * 5, diff])
newPanels.append([panel[1] - diff * 5, panel[1] - diff * 4, diff])
newPanels.append([panel[1] - diff * 4, panel[1] - diff * 3, diff])
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 3 * opt.height:
diff = int(panel[2] / 4)
newPanels.append([panel[0], panel[1] - diff * 3, diff])
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 1.5 * opt.height:
newPanels.append([panel[0], panel[1] - int(panel[2] / 2), int(panel[2] / 2)])
newPanels.append([panel[1] - int(panel[2] / 2), panel[1], int(panel[2] / 2)])
else:
newPanels = [panel]
return newPanels
def splitImageTick(output): def splitImageTick(output):
@@ -125,56 +98,60 @@ def splitImage(work):
path = work[0] path = work[0]
name = work[1] name = work[1]
opt = work[2] opt = work[2]
# Hardcoded options
threshold = 1.0
delta = 15
fileExpanded = os.path.splitext(name)
filePath = os.path.join(path, name) filePath = os.path.join(path, name)
image = Image.open(filePath) imgOrg = Image.open(filePath).convert('RGB')
image = image.convert('RGB') imgProcess = Image.open(filePath).convert('1')
widthImg, heightImg = image.size widthImg, heightImg = imgOrg.size
if heightImg > opt.height: if heightImg > opt.height:
if opt.debug: if opt.debug:
from PIL import ImageDraw drawImg = Image.open(filePath).convert(mode='RGBA')
debugImage = Image.open(filePath) draw = ImageDraw.Draw(drawImg)
draw = ImageDraw.Draw(debugImage)
# Find panels # Find panels
y1 = 0 yWork = 0
y2 = 15 panelDetected = False
panels = [] panels = []
while y2 < heightImg: while yWork < heightImg:
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] < threshold and y2 < heightImg: tmpImg = imgProcess.crop([0, yWork, widthImg, yWork + 4])
y2 += delta solid = detectSolid(tmpImg)
y2 -= delta if not solid and not panelDetected:
y1Temp = y2 panelDetected = True
y1 = y2 + delta panelY1 = yWork - 2
y2 = y1 + delta if solid and panelDetected:
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] >= threshold and y2 < heightImg: panelDetected = False
y1 += delta panelY2 = yWork + 6
y2 += delta panels.append((panelY1, panelY2, panelY2 - panelY1))
if y1 + delta >= heightImg: yWork += 5
y1 = heightImg - 1
y2Temp = y1 # Split too big panels
if opt.debug: panelsProcessed = []
draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0)) for panel in panels:
draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0)) if panel[2] <= opt.height * 1.5:
panelHeight = y2Temp - y1Temp panelsProcessed.append(panel)
if panelHeight > delta: elif panel[2] < opt.height * 2:
# Panels that can't be cut nicely will be forcefully splitted diff = panel[2] - opt.height
panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], opt) panelsProcessed.append((panel[0], panel[1] - diff, opt.height))
for panel in panelsCleaned: panelsProcessed.append((panel[1] - opt.height, panel[1], opt.height))
panels.append(panel) else:
parts = round(panel[2] / opt.height)
diff = panel[2] // parts
for x in range(0, parts):
panelsProcessed.append((panel[0] + (x * diff), panel[1] - ((parts - x - 1) * diff), diff))
if opt.debug: if opt.debug:
for panel in panelsProcessed:
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG') draw.rectangle([(0, panel[0]), (widthImg, panel[1])], (0, 255, 0, 128), (0, 0, 255, 255))
# noinspection PyUnboundLocalVariable
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
# Create virtual pages # Create virtual pages
pages = [] pages = []
currentPage = [] currentPage = []
pageLeft = opt.height pageLeft = opt.height
panelNumber = 0 panelNumber = 0
for panel in panels: for panel in panelsProcessed:
if pageLeft - panel[2] > 0: if pageLeft - panel[2] > 0:
pageLeft -= panel[2] pageLeft -= panel[2]
currentPage.append(panelNumber) currentPage.append(panelNumber)
@@ -194,18 +171,18 @@ def splitImage(work):
pageHeight = 0 pageHeight = 0
targetHeight = 0 targetHeight = 0
for panel in page: for panel in page:
pageHeight += panels[panel][2] pageHeight += panelsProcessed[panel][2]
if pageHeight > delta: if pageHeight > 15:
newPage = Image.new('RGB', (widthImg, pageHeight)) newPage = Image.new('RGB', (widthImg, pageHeight))
for panel in page: for panel in page:
panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]]) panelImg = imgOrg.crop([0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]])
newPage.paste(panelImg, (0, targetHeight)) newPage.paste(panelImg, (0, targetHeight))
targetHeight += panels[panel][2] targetHeight += panelsProcessed[panel][2]
newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG') newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
pageNumber += 1 pageNumber += 1
os.remove(filePath) os.remove(filePath)
except Exception: except Exception:
return str(sys.exc_info()[1]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def main(argv=None, qtGUI=None): def main(argv=None, qtGUI=None):
@@ -242,15 +219,15 @@ def main(argv=None, qtGUI=None):
work = [] work = []
pagenumber = 1 pagenumber = 1
splitWorkerOutput = [] splitWorkerOutput = []
splitWorkerPool = Pool() splitWorkerPool = Pool(maxtasksperchild=10)
if options.merge: if options.merge:
print("Merging images...") print("Merging images...")
directoryNumer = 1 directoryNumer = 1
mergeWork = [] mergeWork = []
mergeWorkerOutput = [] mergeWorkerOutput = []
mergeWorkerPool = Pool() mergeWorkerPool = Pool(maxtasksperchild=10)
mergeWork.append([options.targetDir]) mergeWork.append([options.targetDir])
for root, dirs, files in walk(options.targetDir, False): for root, dirs, files in os.walk(options.targetDir, False):
dirs, files = walkSort(dirs, files) dirs, files = walkSort(dirs, files)
for directory in dirs: for directory in dirs:
directoryNumer += 1 directoryNumer += 1
@@ -267,9 +244,10 @@ def main(argv=None, qtGUI=None):
raise UserWarning("Conversion interrupted.") raise UserWarning("Conversion interrupted.")
if len(mergeWorkerOutput) > 0: if len(mergeWorkerOutput) > 0:
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][0],
mergeWorkerOutput[0][1])
print("Splitting images...") print("Splitting images...")
for root, dirs, files in walk(options.targetDir, False): for root, _, files in os.walk(options.targetDir, False):
for name in files: for name in files:
if getImageFileName(name) is not None: if getImageFileName(name) is not None:
pagenumber += 1 pagenumber += 1
@@ -290,7 +268,8 @@ def main(argv=None, qtGUI=None):
raise UserWarning("Conversion interrupted.") raise UserWarning("Conversion interrupted.")
if len(splitWorkerOutput) > 0: if len(splitWorkerOutput) > 0:
rmtree(options.targetDir, True) rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0]) raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
splitWorkerOutput[0][1])
if options.inPlace: if options.inPlace:
rmtree(options.sourceDir) rmtree(options.sourceDir)
move(options.targetDir, options.sourceDir) move(options.targetDir, options.sourceDir)

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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Changes for KCC Copyright (C) 2014-2017 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,8 @@
# Copyright (C) 2010 Alex Yatskov # Copyright (C) 2010 Alex Yatskov
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com> # Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
# Copyright (c) 2016 Alberto Planas <aplanas@gmail.com>
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 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
@@ -20,7 +21,7 @@ import os
from io import BytesIO from io import BytesIO
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from urllib.parse import quote from urllib.parse import quote
from PIL import Image, ImageOps, ImageStat, ImageChops from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
from .shared import md5Checksum from .shared import md5Checksum
from . import __version__ from . import __version__
@@ -77,20 +78,21 @@ class ProfileData:
] ]
Profiles = { Profiles = {
'K1': ("Kindle 1", (600, 670), Palette4, 1.8, (900, 1005)), 'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)), 'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
'K3': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)), 'K3': ("Kindle", (600, 800), Palette16, 1.8),
'K45': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)), 'K45': ("Kindle", (600, 800), Palette16, 1.8),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)), 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8, (1137, 1536)), 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8, (1608, 2172)), 'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)), 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8, (1152, 1536)), 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8, (1608, 2172)), 'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)), 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8),
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)), 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8),
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8, (1620, 2145)), 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8),
'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)), 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
'OTHER': ("Other", (0, 0), Palette16, 1.8),
} }
@@ -104,8 +106,6 @@ class ComicPageParser:
self.color = self.colorCheck() self.color = self.colorCheck()
self.fill = self.fillCheck() self.fill = self.fillCheck()
self.splitCheck() self.splitCheck()
if self.opt.hqmode:
self.sizeCheck()
def getImageHistogram(self, image): def getImageHistogram(self, image):
histogram = image.histogram() histogram = image.histogram()
@@ -119,15 +119,15 @@ class ComicPageParser:
def splitCheck(self): def splitCheck(self):
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 if (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
if (width > height) != (dstwidth > dstheight) and not self.opt.webtoon: and not self.opt.webtoon and self.opt.splitter == 1:
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True), self.color, self.fill])
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
if self.opt.splitter != 1: if self.opt.splitter != 1:
if width > height: if width > height:
# Source is landscape, so split by the width
leftbox = (0, 0, int(width / 2), height) leftbox = (0, 0, int(width / 2), height)
rightbox = (int(width / 2), 0, width, height) rightbox = (int(width / 2), 0, width, height)
else: else:
# 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)
if self.opt.righttoleft: if self.opt.righttoleft:
@@ -204,29 +204,18 @@ class ComicPageParser:
else: else:
return 'white' return 'white'
def sizeCheck(self):
additionalPayload = []
width, height = self.image.size
dstwidth, dstheight = self.size
for work in self.payload:
if width > dstwidth and height > dstheight:
additionalPayload.append([work[0] + '+', work[1], work[2].copy(), work[3], work[4]])
self.payload = self.payload + additionalPayload
class ComicPage: class ComicPage:
def __init__(self, mode, path, image, color, fill, options): def __init__(self, options, mode, path, image, color, fill):
self.opt = options self.opt = options
_, self.size, self.palette, self.gamma, self.panelviewsize = self.opt.profileData _, self.size, self.palette, self.gamma = self.opt.profileData
if self.opt.hq:
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
self.image = image self.image = image
self.color = color self.color = color
self.fill = fill self.fill = fill
self.rotated = False self.rotated = False
self.orgPath = os.path.join(path[0], path[1]) self.orgPath = os.path.join(path[0], path[1])
if '+' in mode:
self.hqMode = True
else:
self.hqMode = False
if 'N' in mode: if 'N' in mode:
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC' self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
elif 'R' in mode: elif 'R' in mode:
@@ -245,18 +234,16 @@ class ComicPage:
if self.rotated: if self.rotated:
flags.append('Rotated') flags.append('Rotated')
if self.fill != 'white': if self.fill != 'white':
flags.append('BlackFill') flags.append('BlackBackground')
if self.hqMode:
self.targetPath += '-HQ'
if self.opt.forcepng: if self.opt.forcepng:
self.targetPath += '.png' self.targetPath += '.png'
self.image.save(self.targetPath, 'PNG', optimize=1) self.image.save(self.targetPath, 'PNG', optimize=1)
else: else:
self.targetPath += '.jpg' self.targetPath += '.jpg'
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=80) self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
return [md5Checksum(self.targetPath), flags, self.orgPath] return [md5Checksum(self.targetPath), flags, self.orgPath]
except IOError: except IOError as err:
raise RuntimeError('Cannot save image.') raise RuntimeError('Cannot save image. ' + str(err))
def autocontrastImage(self): def autocontrastImage(self):
gamma = self.opt.gamma gamma = self.opt.gamma
@@ -281,128 +268,69 @@ class ComicPage:
self.image = self.image.quantize(palette=palImg) self.image = self.image.quantize(palette=palImg)
def resizeImage(self): def resizeImage(self):
if self.hqMode: if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
size = (self.panelviewsize[0], self.panelviewsize[1])
if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
self.image.thumbnail(size, Image.LANCZOS)
else:
size = (self.size[0], self.size[1])
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
method = Image.BICUBIC method = Image.BICUBIC
else: else:
method = Image.LANCZOS method = Image.LANCZOS
if self.opt.stretch: if self.opt.stretch:
self.image = self.image.resize(size, method) self.image = self.image.resize(self.size, method)
elif self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale: elif self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not self.opt.upscale:
if self.opt.format == 'CBZ': if self.opt.format == 'CBZ':
borderw = int((size[0] - self.image.size[0]) / 2) borderw = int((self.size[0] - self.image.size[0]) / 2)
borderh = int((size[1] - self.image.size[1]) / 2) borderh = int((self.size[1] - self.image.size[1]) / 2)
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill) self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
if self.image.size[0] != size[0] or self.image.size[1] != size[1]: if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
self.image = ImageOps.fit(self.image, size, method=Image.BICUBIC, centering=(0.5, 0.5)) self.image = ImageOps.fit(self.image, self.size, method=Image.BICUBIC, centering=(0.5, 0.5))
else: else:
if self.opt.format == 'CBZ': if self.opt.format == 'CBZ':
ratioDev = float(size[0]) / float(size[1]) ratioDev = float(self.size[0]) / float(self.size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0] diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill) self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev: elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1] diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill) self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5)) self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
else: else:
hpercent = size[1] / float(self.image.size[1]) hpercent = self.size[1] / float(self.image.size[1])
wsize = int((float(self.image.size[0]) * float(hpercent))) wsize = int((float(self.image.size[0]) * float(hpercent)))
self.image = self.image.resize((wsize, size[1]), method) self.image = self.image.resize((wsize, self.size[1]), method)
if self.image.size[0] > size[0] or self.image.size[1] > size[1]: if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]:
self.image.thumbnail(size, Image.LANCZOS) self.image.thumbnail(self.size, Image.LANCZOS)
def cutPageNumber(self, fixedThreshold): def getBoundingBox(self, tmpImg):
if ImageChops.invert(self.image).getbbox() is not None: min_margin = [int(0.005 * i + 0.5) for i in tmpImg.size]
widthImg, heightImg = self.image.size max_margin = [int(0.1 * i + 0.5) for i in tmpImg.size]
delta = 2 bbox = tmpImg.getbbox()
diff = delta bbox = (
if ImageStat.Stat(self.image).var[0] < 2 * fixedThreshold: max(0, min(max_margin[0], bbox[0] - min_margin[0])),
return self.image max(0, min(max_margin[1], bbox[1] - min_margin[1])),
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\ min(tmpImg.size[0],
and diff < heightImg: max(tmpImg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
diff += delta min(tmpImg.size[1],
diff -= delta max(tmpImg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
pageNumberCut1 = diff )
if diff < delta: return bbox
diff = delta
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] def cropPageNumber(self, power):
diff += delta if self.fill != 'white':
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] - oldStat > 0\ tmpImg = self.image.convert(mode='L')
and diff < heightImg // 4:
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0]
diff += delta
diff -= delta
pageNumberCut2 = diff
diff += delta
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg,
heightImg - pageNumberCut2))).var[0]
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg - pageNumberCut2))).var[0]\
< fixedThreshold + oldStat and diff < heightImg // 4:
diff += delta
diff -= delta
pageNumberCut3 = diff
delta = 5
diff = delta
while ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut2, diff, heightImg))).var[0]\
< fixedThreshold and diff < widthImg:
diff += delta
diff -= delta
pageNumberX1 = diff
diff = delta
while ImageStat.Stat(self.image.crop((widthImg - diff, heightImg - pageNumberCut2,
widthImg, heightImg))).var[0] < fixedThreshold and diff < widthImg:
diff += delta
diff -= delta
pageNumberX2 = widthImg - diff
if pageNumberCut3 - pageNumberCut1 > 2 * delta\
and float(pageNumberX2 - pageNumberX1) / float(pageNumberCut2 - pageNumberCut1) <= 9.0\
and ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut3, widthImg, heightImg))).var[0]\
/ ImageStat.Stat(self.image).var[0] < 0.1\
and pageNumberCut3 < heightImg / 4 - delta:
diff = pageNumberCut3
else: else:
diff = pageNumberCut1 tmpImg = ImageOps.invert(self.image.convert(mode='L'))
self.image = self.image.crop((0, 0, widthImg, heightImg - diff)) tmpImg = tmpImg.point(lambda x: x and 255)
tmpImg = tmpImg.filter(ImageFilter.MinFilter(size=3))
tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=5))
tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image
def cropWhiteSpace(self, fixedThreshold): def cropMargin(self, power):
if ImageChops.invert(self.image).getbbox() is not None: if self.fill != 'white':
widthImg, heightImg = self.image.size tmpImg = self.image.convert(mode='L')
delta = 10 else:
diff = delta tmpImg = ImageOps.invert(self.image.convert(mode='L'))
# top tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=3))
while ImageStat.Stat(self.image.crop((0, 0, widthImg, diff))).var[0] < fixedThreshold and diff < heightImg: tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
diff += delta self.image = self.image.crop(self.getBoundingBox(tmpImg)) if tmpImg.getbbox() else self.image
diff -= delta
self.image = self.image.crop((0, diff, widthImg, heightImg))
widthImg, heightImg = self.image.size
diff = delta
# left
while ImageStat.Stat(self.image.crop((0, 0, diff, heightImg))).var[0] < fixedThreshold and diff < widthImg:
diff += delta
diff -= delta
self.image = self.image.crop((diff, 0, widthImg, heightImg))
widthImg, heightImg = self.image.size
diff = delta
# down
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\
and diff < heightImg:
diff += delta
diff -= delta
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
widthImg, heightImg = self.image.size
diff = delta
# right
while ImageStat.Stat(self.image.crop((widthImg - diff, 0, widthImg, heightImg))).var[0] < fixedThreshold\
and diff < widthImg:
diff += delta
diff -= delta
self.image = self.image.crop((0, 0, widthImg - diff, heightImg))
class Cover: class Cover:
@@ -419,44 +347,30 @@ class Cover:
source = urlopen(Request(quote(self.options.remoteCovers[self.tomeNumber]).replace('%3A', ':', 1), source = urlopen(Request(quote(self.options.remoteCovers[self.tomeNumber]).replace('%3A', ':', 1),
headers={'User-Agent': 'KindleComicConverter/' + __version__})).read() headers={'User-Agent': 'KindleComicConverter/' + __version__})).read()
self.image = Image.open(BytesIO(source)) self.image = Image.open(BytesIO(source))
self.processExternal()
except Exception: except Exception:
self.image = Image.open(source) self.image = Image.open(source)
self.processInternal()
else: else:
self.image = Image.open(source) self.image = Image.open(source)
self.processInternal() self.process()
def processInternal(self): def process(self):
self.image = self.image.convert('RGB')
self.image = self.trim()
self.save()
def processExternal(self):
self.image = self.image.convert('RGB') self.image = self.image.convert('RGB')
self.image = ImageOps.autocontrast(self.image)
if not self.options.forcecolor:
self.image = self.image.convert('L')
self.image.thumbnail(self.options.profileData[1], Image.LANCZOS) self.image.thumbnail(self.options.profileData[1], Image.LANCZOS)
self.save() self.save()
def trim(self):
bg = Image.new(self.image.mode, self.image.size, self.image.getpixel((0, 0)))
diff = ImageChops.difference(self.image, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
bbox = diff.getbbox()
if bbox:
return self.image.crop(bbox)
else:
return self.image
def save(self): def save(self):
try: try:
self.image.save(self.target, "JPEG", optimize=1, quality=80) self.image.save(self.target, "JPEG", optimize=1, quality=85)
except IOError: except IOError:
raise RuntimeError('Failed to process downloaded cover.') raise RuntimeError('Failed to process downloaded cover.')
def saveToKindle(self, kindle, asin): def saveToKindle(self, kindle, asin):
self.image = self.image.resize((300, 470), Image.ANTIALIAS).convert('L') self.image = self.image.resize((300, 470), Image.ANTIALIAS)
try: try:
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails', self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG') 'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=85)
except IOError: except IOError:
raise RuntimeError('Failed to upload cover.') raise RuntimeError('Failed to upload cover.')

View File

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

View File

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

View File

@@ -1,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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 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)
@@ -61,7 +61,6 @@ class PdfJpgExtract:
iend += endfix iend += endfix
jpg = pdf[istart:iend] jpg = pdf[istart:iend]
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb") jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
# noinspection PyTypeChecker
jpgfile.write(jpg) jpgfile.write(jpg)
jpgfile.close() jpgfile.close()
njpg += 1 njpg += 1

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-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2017 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,20 +17,14 @@
# #
import os import os
from sys import version_info
from hashlib import md5 from hashlib import md5
from html.parser import HTMLParser from html.parser import HTMLParser
from distutils.version import StrictVersion from distutils.version import StrictVersion
from time import sleep
from shutil import rmtree, copy from shutil import rmtree, copy
from tempfile import mkdtemp from tempfile import mkdtemp
from zipfile import ZipFile, ZIP_DEFLATED from zipfile import ZipFile, ZIP_DEFLATED
from re import split from re import split
from traceback import format_tb from traceback import format_tb
try:
from scandir import walk
except ImportError:
walk = os.walk
class HTMLStripper(HTMLParser): class HTMLStripper(HTMLParser):
@@ -71,7 +65,7 @@ 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 walk(some_dir): for root, dirs, files in os.walk(some_dir):
dirs, files = walkSort(dirs, files) 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)
@@ -96,30 +90,6 @@ def check7ZFile(filePath):
return header == b"7z\xbc\xaf'\x1c" return header == b"7z\xbc\xaf'\x1c"
def saferReplace(old, new):
for x in range(30):
try:
os.replace(old, new)
except PermissionError:
sleep(1)
else:
break
else:
raise PermissionError("Failed to move the file.")
def saferRemove(target):
for x in range(30):
try:
os.remove(target)
except PermissionError:
sleep(1)
else:
break
else:
raise PermissionError("Failed to remove the file.")
def removeFromZIP(zipfname, *filenames): def removeFromZIP(zipfname, *filenames):
tempdir = mkdtemp('', 'KCC-') tempdir = mkdtemp('', 'KCC-')
try: try:
@@ -129,23 +99,17 @@ def removeFromZIP(zipfname, *filenames):
for item in zipread.infolist(): for item in zipread.infolist():
if item.filename not in filenames: if item.filename not in filenames:
zipwrite.writestr(item, zipread.read(item.filename)) zipwrite.writestr(item, zipread.read(item.filename))
for x in range(30):
try:
copy(tempname, zipfname) copy(tempname, zipfname)
except PermissionError:
sleep(1)
else:
break
else:
raise PermissionError
finally: finally:
rmtree(tempdir, True) rmtree(tempdir, True)
def sanitizeTrace(traceback): def sanitizeTrace(traceback):
return ''.join(format_tb(traceback))\ return ''.join(format_tb(traceback))\
.replace('C:\\Users\\pawel\\Documents\\Projekty\\KCC\\', '') \ .replace('C:/Users/Pawel/Documents/Projekty/KCC/', '')\
.replace('C:\\Users\\Paweł\\Documents\\Projekty\\KCC\\', '') \ .replace('C:/Python35/', '')\
.replace('c:/python35/', '')\
.replace('C:\\Users\\Pawel\\Documents\\Projekty\\KCC\\', '')\
.replace('C:\\Python35\\', '')\ .replace('C:\\Python35\\', '')\
.replace('c:\\python35\\', '') .replace('c:\\python35\\', '')
@@ -162,33 +126,26 @@ def dependencyCheck(level):
try: try:
import raven import raven
except ImportError: except ImportError:
missing.append('raven 5.13.0+') missing.append('raven 6.0.0+')
if level > 1: if level > 1:
try: try:
from psutil import __version__ as psutilVersion from psutil import __version__ as psutilVersion
if StrictVersion('4.1.0') > StrictVersion(psutilVersion): if StrictVersion('5.0.0') > StrictVersion(psutilVersion):
missing.append('psutil 4.1.0+') missing.append('psutil 5.0.0+')
except ImportError: except ImportError:
missing.append('psutil 4.1.0+') missing.append('psutil 5.0.0+')
try: try:
from slugify import __version__ as slugifyVersion from slugify import __version__ as slugifyVersion
if StrictVersion('1.2.0') > StrictVersion(slugifyVersion): if StrictVersion('1.2.1') > StrictVersion(slugifyVersion):
missing.append('python-slugify 1.2.0+') missing.append('python-slugify 1.2.1+')
except ImportError: except ImportError:
missing.append('python-slugify 1.2.0+') missing.append('python-slugify 1.2.1+')
try: try:
from PIL import PILLOW_VERSION as pillowVersion from PIL import PILLOW_VERSION as pillowVersion
if StrictVersion('3.2.0') > StrictVersion(pillowVersion): if StrictVersion('4.0.0') > StrictVersion(pillowVersion):
missing.append('Pillow 3.2.0+') missing.append('Pillow 4.0.0+')
except ImportError: except ImportError:
missing.append('Pillow 3.2.0+') missing.append('Pillow 4.0.0+')
if version_info[1] < 5:
try:
from scandir import __version__ as scandirVersion
if StrictVersion('1.2') > StrictVersion(scandirVersion):
missing.append('scandir 1.2+')
except ImportError:
missing.append('scandir 1.2+')
if len(missing) > 0: if len(missing) > 0:
print('ERROR: ' + ', '.join(missing) + ' is not installed!') print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1) exit(1)

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2017 Pawel Jastrzebski <pawelj@iosphe.re>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
import os
import sys
from . import __version__
from .shared import dependencyCheck
def start():
dependencyCheck(3)
from . import KCC_gui
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1"
KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
if KCCAplication.isRunning():
if len(sys.argv) > 1:
KCCAplication.sendMessage(sys.argv[1])
else:
KCCAplication.sendMessage('ARISE')
else:
KCCWindow = KCC_gui.QMainWindowKCC()
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
if len(sys.argv) > 1:
KCCUI.handleMessage(sys.argv[1])
sys.exit(KCCAplication.exec_())
def startC2E():
dependencyCheck(2)
from .comic2ebook import main
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
sys.exit(main(sys.argv[1:]))
def startC2P():
dependencyCheck(1)
from .comic2panel import main
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
sys.exit(main(sys.argv[1:]))

View File

@@ -5,7 +5,7 @@ Name=Kindle Comic Converter
GenericName=Kindle Comic Converter GenericName=Kindle Comic Converter
Comment=Comic and Manga converter for e-book readers Comment=Comic and Manga converter for e-book readers
Icon=/usr/share/kindlecomicconverter/comic2ebook.png Icon=/usr/share/kindlecomicconverter/comic2ebook.png
Exec=/usr/bin/kcc Exec=/usr/bin/kcc %f
Terminal=false Terminal=false
Categories=Graphics; Categories=Graphics;
MimeType=application/zip;application/x-rar;application/x-7z-compressed; MimeType=application/zip;application/x-rar;application/x-7z-compressed;

View File

@@ -6,10 +6,31 @@
<string>English</string> <string>English</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Kindle Comic Converter</string> <string>Kindle Comic Converter</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cbz</string>
<string>cbr</string>
<string>cb7</string>
<string>zip</string>
<string>rar</string>
<string>7z</string>
<string>pdf</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>comic2ebook.icns</string>
<key>CFBundleTypeName</key>
<string>Comics</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
</array>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>MacOS/Kindle Comic Converter</string> <string>MacOS/Kindle Comic Converter</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>KindleComicConverter 5.1, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski</string> <string>KindleComicConverter 5.4.0, written 2012-2017 by Ciro Mattia Gonano and Pawel Jastrzebski</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>comic2ebook.icns</string> <string>comic2ebook.icns</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@@ -21,11 +42,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.1</string> <string>5.4.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>5.1</string> <string>5.4.0</string>
<key>LSEnvironment</key> <key>LSEnvironment</key>
<dict> <dict>
<key>PATH</key> <key>PATH</key>

5
requirements.txt Normal file
View File

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

111
setup.py
View File

@@ -2,11 +2,11 @@
""" """
pip/pyinstaller build script for KCC. pip/pyinstaller build script for KCC.
Usage (Windows): Install as Python package:
py -3 setup.py build_binary python3 setup.py install
Usage (Linux/OS X): Create EXE/APP/DEB:
python3 setup.py build_binary or python3 setup.py build_binary --pyz python3 setup.py build_binary
""" """
import os import os
@@ -14,31 +14,29 @@ import sys
import shutil import shutil
import setuptools import setuptools
import distutils.cmd import distutils.cmd
from distutils.command.build import build from kindlecomicconverter import __version__
from kcc import __version__
NAME = 'KindleComicConverter' NAME = 'KindleComicConverter'
MAIN = 'kcc.py' MAIN = 'kcc.py'
VERSION = __version__ VERSION = __version__
OPTIONS = {}
class BuildBinaryCommand(distutils.cmd.Command): class BuildBinaryCommand(distutils.cmd.Command):
description = 'build binary release' description = 'build binary release'
user_options = [ user_options = []
('pyz', None, 'build PYZ package'),
]
def initialize_options(self): def initialize_options(self):
# noinspection PyAttributeOutsideInit pass
self.pyz = False
def finalize_options(self): def finalize_options(self):
pass pass
def run(self): def run(self):
if sys.platform == 'darwin': if sys.platform == 'darwin':
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s --noupx kcc.py') if os.path.isfile('Kindle Comic Converter.spec'):
os.system('pyinstaller "Kindle Comic Converter.spec"')
else:
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents') shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents')
@@ -51,79 +49,20 @@ class BuildBinaryCommand(distutils.cmd.Command):
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg') os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
exit(0) exit(0)
elif sys.platform == 'win32': elif sys.platform == 'win32':
if os.path.isfile('KCC.spec'):
os.system('pyinstaller KCC.spec')
else:
os.system('pyinstaller -y -F -i icons\comic2ebook.ico -n KCC -w --noupx kcc.py') os.system('pyinstaller -y -F -i icons\comic2ebook.ico -n KCC -w --noupx kcc.py')
if os.path.isfile('setup.bat'): if os.path.isfile('setup.bat'):
os.system('setup.bat') os.system('setup.bat')
exit(0) exit(0)
else:
if self.pyz:
script = '''
cp kcc.py __main__.py
zip kcc.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-bin
cat kcc.zip >> kcc-bin
chmod +x kcc-bin
cp kcc-c2e.py __main__.py
zip kcc-c2e.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-c2e-bin
cat kcc-c2e.zip >> kcc-c2e-bin
chmod +x kcc-c2e-bin
cp kcc-c2p.py __main__.py
zip kcc-c2p.zip __main__.py kcc/*.py
echo "#!/usr/bin/env python3" > kcc-c2p-bin
cat kcc-c2p.zip >> kcc-c2p-bin
chmod +x kcc-c2p-bin
mkdir dist
tar --xform s:^.*/:: \
--xform s/LICENSE.txt/LICENSE/ \
--xform s/kcc-bin/kcc/ \
--xform s/kcc-c2p-bin/kcc-c2p/ \
--xform s/kcc-c2e-bin/kcc-c2e/ \
--xform s/comic2ebook/kcc/ \
-czf dist/KindleComicConverter_linux_''' + VERSION + '''.tar.gz \
kcc-bin kcc-c2e-bin kcc-c2p-bin LICENSE.txt README.md icons/comic2ebook.png
rm __main__.py kcc.zip kcc-c2e.zip kcc-c2p.zip kcc-bin kcc-c2e-bin kcc-c2p-bin
'''
os.system("bash -c '%s'" % script)
exit(0)
else: else:
os.system('docker run --rm -v ' + os.getcwd() + ':/app -e KCCVER=' + VERSION + ' acidweb/kcc') os.system('docker run --rm -v ' + os.getcwd() + ':/app -e KCCVER=' + VERSION + ' acidweb/kcc')
exit(0) exit(0)
class BuildCommand(build):
def run(self):
os.makedirs('build/_scripts/', exist_ok=True)
shutil.copyfile('kcc.py', 'build/_scripts/kcc')
shutil.copyfile('kcc-c2e.py', 'build/_scripts/kcc-c2e')
shutil.copyfile('kcc-c2p.py', 'build/_scripts/kcc-c2p')
# noinspection PyShadowingNames
OPTIONS = dict(
scripts=['build/_scripts/kcc',
'build/_scripts/kcc-c2e',
'build/_scripts/kcc-c2p'],
packages=['kcc'],
install_requires=[
'PyQt5>=5.6.0'
'Pillow>=3.2.0',
'psutil>=4.1.0',
'python-slugify>=1.2.0',
'raven>=5.13.0',
],
zip_safe=False,
)
if sys.version_info[1] < 5:
OPTIONS['install_requires'].append('scandir>=1.2.0')
build.run(self)
setuptools.setup( setuptools.setup(
cmdclass={ cmdclass={
'build_binary': BuildBinaryCommand, 'build_binary': BuildBinaryCommand,
'build': BuildCommand,
}, },
name=NAME, name=NAME,
version=VERSION, version=VERSION,
@@ -131,7 +70,25 @@ setuptools.setup(
author_email='ciromattia@gmail.com, pawelj@iosphe.re', author_email='ciromattia@gmail.com, pawelj@iosphe.re',
description='Comic and Manga converter for e-book readers.', description='Comic and Manga converter for e-book readers.',
license='ISC License (ISCL)', license='ISC License (ISCL)',
keywords='kindle comic mobipocket mobi cbz cbr manga', keywords=['kindle', 'kobo', 'comic', 'manga', 'mobi', 'epub', 'cbz'],
url='http://github.com/ciromattia/kcc', url='http://github.com/ciromattia/kcc',
**OPTIONS entry_points={
'console_scripts': [
'kcc-c2e = kindlecomicconverter.startup:startC2E',
'kcc-c2p = kindlecomicconverter.startup:startC2P',
],
'gui_scripts': [
'kcc = kindlecomicconverter.startup:start',
],
},
packages=['kindlecomicconverter'],
install_requires=[
'PyQt5>=5.6.0',
'Pillow>=4.0.0',
'psutil>=5.0.0',
'python-slugify>=1.2.1',
'raven>=6.0.0',
],
classifiers=[],
zip_safe=False,
) )