mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 13:38:46 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd665c3261 | ||
|
|
94586fa590 | ||
|
|
89806dfcfc | ||
|
|
c9eb73ab90 | ||
|
|
2edcc0369a | ||
|
|
bc0a52b848 | ||
|
|
5ae72bf06d | ||
|
|
24c32643c1 | ||
|
|
40b988f964 | ||
|
|
ac794eff85 | ||
|
|
eaa387a9d6 | ||
|
|
d0f5d6dac4 | ||
|
|
b174534c1c | ||
|
|
19d486a4ee | ||
|
|
652762b709 | ||
|
|
e6df87f8fd | ||
|
|
1a9cd0beb5 | ||
|
|
108e351126 | ||
|
|
dfe3e10470 | ||
|
|
787ad9fa66 | ||
|
|
f10e8869a9 | ||
|
|
715ada328f | ||
|
|
56f23ab488 | ||
|
|
996af59e00 | ||
|
|
37aa84c4aa | ||
|
|
50574632e6 | ||
|
|
0afb9e8c0b | ||
|
|
7511c7eed6 | ||
|
|
836a4146f9 | ||
|
|
15a240ccea | ||
|
|
0722ddf8b0 | ||
|
|
b3159b94e7 | ||
|
|
ef5207c990 | ||
|
|
db77d89817 | ||
|
|
4571fadadb | ||
|
|
94f56238ae | ||
|
|
5efb5d6dbb | ||
|
|
623f615dd9 | ||
|
|
39fbbc42b3 | ||
|
|
99405ab8a6 | ||
|
|
aadfca8306 | ||
|
|
5450502c2a | ||
|
|
c976b06413 | ||
|
|
b6facda95b | ||
|
|
3f608eb602 | ||
|
|
104cd04994 | ||
|
|
b323204628 | ||
|
|
56195d301d | ||
|
|
287723ca6f | ||
|
|
90490149c7 | ||
|
|
2210f484df | ||
|
|
d5502e85b0 | ||
|
|
59b26cfc8b | ||
|
|
a722e5fa49 | ||
|
|
fce3072dca | ||
|
|
f53cdf9cd7 | ||
|
|
de74318c69 | ||
|
|
b137e69510 | ||
|
|
888663fa4c | ||
|
|
8a2ba96ac5 | ||
|
|
cb6b0e0a7b | ||
|
|
49d2a7fbab | ||
|
|
3d84b27d58 | ||
|
|
e98a23d0c0 | ||
|
|
7f80dacea8 | ||
|
|
6efb3dcef3 | ||
|
|
942a828b7e | ||
|
|
df5ee1badf | ||
|
|
e3ab28642d | ||
|
|
a1831c45d5 | ||
|
|
52bea9957a | ||
|
|
a71339ec34 | ||
|
|
c5f09c44b8 | ||
|
|
64b199ef74 | ||
|
|
8dd0b0e694 | ||
|
|
181a2e8ab4 | ||
|
|
3fdff845b7 | ||
|
|
2ee5dc310b | ||
|
|
f32e9560b5 | ||
|
|
621827c1c2 | ||
|
|
dc312f36c2 | ||
|
|
4573ff6ec2 | ||
|
|
d77498405b | ||
|
|
e491fca445 | ||
|
|
d22ee1a488 | ||
|
|
7ebcccd8a2 | ||
|
|
9a691c3c63 | ||
|
|
2b04a0298e |
@@ -110,7 +110,7 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html></string>
|
||||
<string><html><head/><body><p>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>This mode was created for pages with a low width, high height and vertical panel flow.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Webtoon mode</string>
|
||||
@@ -386,7 +386,7 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span><span style=" font-style:italic;">Use it when Panel View support is not needed.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span><span style=" font-style:italic;">Not zoomed images </span><span style=" font-weight:600; font-style:italic;">might</span><span style=" font-style:italic;"> be blurry.<br/></span>- High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span><span style=" font-style:italic;">Maximum possible quality.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode</span><br/>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode</span><br/>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.<br/><br/><span style=" font-weight:600; text-decoration: underline;">Checked - Ultra quality mode</span><br/>Highest possible quality. Output files will be big.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>High/Ultra quality</string>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html></string>
|
||||
<string><html><head/><body><p>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God </span>or <span style=" font-style:italic;">Noblesse</span>.<br/>This mode was created for pages with a low width, high height and vertical panel flow.</p><p><br/></p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Webtoon mode</string>
|
||||
@@ -391,7 +391,7 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span><span style=" font-style:italic;">Use it when Panel View support is not needed.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span><span style=" font-style:italic;">Not zoomed image </span><span style=" font-weight:600; font-style:italic;">might</span><span style=" font-style:italic;"> be a little blurry.<br/></span>- High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span><span style=" font-style:italic;">Maximum possible quality.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode</span><br/>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode</span><br/>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Ultra quality mode</span><br/>Highest possible quality. Output files will be big.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>High/Ultra quality</string>
|
||||
|
||||
4
KCC.ui
4
KCC.ui
@@ -94,7 +94,7 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p style='white-space:pre'>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html></string>
|
||||
<string><html><head/><body><p style="white-space:pre">Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>This mode is created for pages with a low width, high height and vertical panel flow.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Webtoon mode</string>
|
||||
@@ -338,7 +338,7 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span><span style=" font-style:italic;">Use it when Panel View support is not needed.</span><span style=" font-weight:600; text-decoration: underline;"><br/></span>- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span><span style=" font-style:italic;">Not zoomed images </span><span style=" font-weight:600; font-style:italic;">might </span><span style=" font-style:italic;">be blurry.</span><span style=" font-weight:600; text-decoration: underline;"><br/></span>- High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span><span style=" font-style:italic;">Maximum possible quality.</span><span style=" font-weight:600; text-decoration: underline;"><br/></span>- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</p></body></html></string>
|
||||
<string><html><head/><body><p style="white-space:pre"><span style=" font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p style="white-space:pre"><span style=" font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.</p><p style="white-space:pre"><span style=" font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span>Highest possible quality. Output files will be big.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>High/Ultra quality</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
ISC LICENSE
|
||||
|
||||
Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
Copyright (c) 2013-2014 Paweł Jastrzębski <pawelj@vulturis.eu>
|
||||
Copyright (c) 2013-2014 Paweł Jastrzębski <pawelj@iosphe.re>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
|
||||
139
README.md
139
README.md
@@ -1,13 +1,13 @@
|
||||
# KCC
|
||||
|
||||
**Kindle Comic Converter** is a Python app to convert comic files or folders to ePub, Panel View MOBI or E-Ink optimized CBZ.
|
||||
It was initally developed for Kindle but since v2.2 it outputs valid ePub 2.0 so _**despite its name, KCC is
|
||||
actually a comic to EPUB converter that every e-reader owner can happily use**_.
|
||||
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
|
||||
It was initially developed for Kindle but since version 2.2 it outputs valid EPUB 2.0 so _**despite its name, KCC is
|
||||
actually a comic/manga to EPUB converter that every e-reader owner can happily use**_.
|
||||
It can also optionally optimize images by applying a number of transformations.
|
||||
|
||||
### A word of warning
|
||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic readers.
|
||||
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||
_KC2_ in no way is a replacement for **KCC** so you can be quite confident we'll going to carry on developing our little monster ;-)
|
||||
|
||||
### Issues / new features / donations
|
||||
@@ -17,14 +17,18 @@ If you can fix an open issue, fork & make a pull request.
|
||||
If you want more chances an issue is fixes or your wanted feature added, consider [placing a bounty](https://www.bountysource.com/trackers/65571-ciromattia-kcc)!
|
||||
|
||||
If you find **KCC** valuable you can consider donating to the authors:
|
||||
* Ciro Mattia Gonano: [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2) [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
||||
* Paweł Jastrzębski: [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS) [](bitcoin:1W15wwqsfd7wbaZ6wvSJf1LW1bz6q5L8b?label=KCC) [1W15wwqsfd7wbaZ6wvSJf1LW1bz6q5L8b](bitcoin:1W15wwqsfd7wbaZ6wvSJf1LW1bz6q5L8b?label=KCC)
|
||||
- Ciro Mattia Gonano:
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||
- [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
||||
- Paweł Jastrzębski:
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||
- Bitcoin: 1W15wwqsfd7wbaZ6wvSJf1LW1bz6q5L8b
|
||||
|
||||
## BINARY RELEASES
|
||||
You can find the latest released binary at the following links:
|
||||
- **Windows:** [http://kcc.vulturis.eu/Windows/](http://kcc.vulturis.eu/Windows/)
|
||||
- **Linux:** [http://kcc.vulturis.eu/Linux/](http://kcc.vulturis.eu/Linux/)
|
||||
- **OS X (10.8+):** [http://kcc.vulturis.eu/OSX/](http://kcc.vulturis.eu/OSX/)
|
||||
- **Windows (Vista or newer):** [http://kcc.iosphe.re/Windows/](http://kcc.iosphe.re/Windows/)
|
||||
- **Linux:** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/)
|
||||
- **OS X (10.8+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/)
|
||||
|
||||
## INPUT FORMATS
|
||||
**KCC** can understand and convert, at the moment, the following input types:
|
||||
@@ -32,7 +36,7 @@ You can find the latest released binary at the following links:
|
||||
- CBZ, ZIP
|
||||
- CBR, RAR *(With `unrar` executable)*
|
||||
- CB7, 7Z *(With `7za` executable)*
|
||||
- PDF *(Extracting only contained JPG images)*
|
||||
- PDF *(Only extracting JPG images)*
|
||||
|
||||
## OPTIONAL REQUIREMENTS
|
||||
- [KindleGen](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211) v2.9+ in a directory reachable by your _PATH_ or in _KCC_ directory *(For MOBI generation)*
|
||||
@@ -40,10 +44,10 @@ You can find the latest released binary at the following links:
|
||||
- [7za](http://www.7-zip.org/download.html) *(For 7z/CB7 support)*
|
||||
|
||||
### For running from source:
|
||||
- Python 3.3
|
||||
- [PyQt5](http://www.riverbankcomputing.co.uk/software/pyqt/download5)
|
||||
- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.3.0+
|
||||
- [psutil](https://pypi.python.org/pypi/psutil)
|
||||
- Python 3.3+
|
||||
- [PyQt5](http://www.riverbankcomputing.co.uk/software/pyqt/download5) 5.2.0+
|
||||
- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.5.0+
|
||||
- [psutil](https://pypi.python.org/pypi/psutil) 2.0+
|
||||
- [python-slugify](http://pypi.python.org/pypi/python-slugify)
|
||||
|
||||
On Debian based distributions these two commands should install all dependencies:
|
||||
@@ -53,8 +57,8 @@ sudo pip3 install pillow python-slugify psutil
|
||||
```
|
||||
|
||||
### For freezing code:
|
||||
- Windows - [cx_Freeze](https://bitbucket.org/anthony_tuininga/cx_freeze) version 4.3.2 with [this](https://bitbucket.org/anthony_tuininga/cx_freeze/pull-request/29/conversions-to-support-untranslated-wide) patchset.
|
||||
- OS X - [py2app](https://bitbucket.org/ronaldoussoren/py2app) HEAD version.
|
||||
- Windows - [py2exe](https://pypi.python.org/pypi/py2exe) 0.9.2+
|
||||
- OS X - [py2app](https://bitbucket.org/ronaldoussoren/py2app) 0.8.0+
|
||||
|
||||
## USAGE
|
||||
|
||||
@@ -63,15 +67,19 @@ 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.
|
||||
|
||||
### Standalone `comic2ebook.py` usage:
|
||||
CLI version of **KCC** is intended for power users. It is not idiot-proof like GUI :-)
|
||||
|
||||
### Standalone `kcc-c2e.py` usage:
|
||||
|
||||
```
|
||||
Usage: comic2ebook.py [options] comic_file|comic_folder
|
||||
Usage: kcc-c2e [options] comic_file|comic_folder
|
||||
|
||||
Options:
|
||||
MAIN:
|
||||
-p PROFILE, --profile=PROFILE
|
||||
Device profile (Choose one among K1, K2, K345, KDX, KHD, KF, KFHD, KFHD8, KFHDX, KFHDX8, KFA, KoMT, KoG, KoA, KoAHD) [Default=KHD]
|
||||
Device profile (Available options: K1, K2, K345, KDX,
|
||||
KPW, KV, KFHD, KFHDX, KFHDX8, KFA, KoMT, KoG, KoA,
|
||||
KoAHD, KoAH2O) [Default=KV]
|
||||
-q QUALITY, --quality=QUALITY
|
||||
Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
|
||||
-m, --manga-style Manga style (Right-to-left reading and splitting)
|
||||
@@ -82,7 +90,9 @@ Options:
|
||||
Output generated file to specified directory or file
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [Default=filename or directory name]
|
||||
--cbz-output Outputs a CBZ archive and does not generate EPUB
|
||||
-f FORMAT, --format=FORMAT
|
||||
Output format (Available options: Auto, MOBI,
|
||||
EPUB, CBZ) [Default=Auto]
|
||||
--batchsplit Split output into multiple files
|
||||
|
||||
PROCESSING:
|
||||
@@ -108,10 +118,10 @@ Options:
|
||||
-h, --help Show this help message and exit
|
||||
```
|
||||
|
||||
### Standalone `comic2panel.py` usage:
|
||||
### Standalone `kcc-c2p.py` usage:
|
||||
|
||||
```
|
||||
Usage: comic2panel.py [options] comic_folder
|
||||
Usage: kcc-c2p [options] comic_folder
|
||||
|
||||
Options:
|
||||
MANDATORY:
|
||||
@@ -126,29 +136,27 @@ Options:
|
||||
```
|
||||
|
||||
## CREDITS
|
||||
**KCC** is made by [Ciro Mattia Gonano](http://github.com/ciromattia) and [Paweł Jastrzębski](http://github.com/AcidWeb)
|
||||
**KCC** is made by [Ciro Mattia Gonano](http://github.com/ciromattia) and [Paweł Jastrzębski](http://github.com/AcidWeb).
|
||||
|
||||
This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published [here](http://www.mobileread.com/forums/showthread.php?t=192783))
|
||||
This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published [here](http://www.mobileread.com/forums/showthread.php?t=192783)).
|
||||
|
||||
The app relies and includes the following scripts/binaries:
|
||||
The app relies and includes the following scripts:
|
||||
|
||||
- `KindleUnpack` script by Charles **M. Hannum, P. Durrant, K. Hendricks, S. Siebert, fandrieu, DiapDealer, nickredding**. Released with GPLv3 License.
|
||||
- `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License.
|
||||
- `rarfile.py` script © 2005-2011 **Marko Kreen** <markokr@gmail.com>. Released with ISC License.
|
||||
- `image.py` class from **Alex Yatskov**'s [Mangle](http://foosoft.net/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches.
|
||||
- `image.py` class from **Alex Yatskov**'s [Mangle](https://github.com/FooSoft/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches.
|
||||
- Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License.
|
||||
|
||||
## SAMPLE FILES CREATED BY KCC
|
||||
* [Kindle Paperwhite](http://kcc.vulturis.eu/Samples/Ubunchu!-KPW.mobi)
|
||||
* [Kindle](http://kcc.vulturis.eu/Samples/Ubunchu!-K345.mobi)
|
||||
* [Kindle DX/DXG](http://kcc.vulturis.eu/Samples/Ubunchu!-KDX.mobi)
|
||||
* [Kindle Fire HD](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHD.mobi)
|
||||
* [Kindle Fire HD 8.9"](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHD8.mobi)
|
||||
* [Kindle Fire HDX](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHDX.mobi)
|
||||
* [Kindle Fire HDX 8.9"](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHDX8.mobi)
|
||||
* [Kobo Mini/Touch](http://kcc.vulturis.eu/Samples/Ubunchu!-KoMT.cbz)
|
||||
* [Kobo Glow](http://kcc.vulturis.eu/Samples/Ubunchu!-KoG.cbz)
|
||||
* [Kobo Aura](http://kcc.vulturis.eu/Samples/Ubunchu!-KoA.cbz)
|
||||
* [Kobo Aura HD](http://kcc.vulturis.eu/Samples/Ubunchu!-KoAHD.cbz)
|
||||
* [Kindle Voyage](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||
* [Kindle Paperwhite](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
||||
* [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K345.mobi)
|
||||
* [Kindle DX/DXG](http://kcc.iosphe.re/Samples/Ubunchu!-KDX.cbz)
|
||||
* [Kobo Mini/Touch](http://kcc.iosphe.re/Samples/Ubunchu!-KoMT.cbz)
|
||||
* [Kobo Glow](http://kcc.iosphe.re/Samples/Ubunchu!-KoG.cbz)
|
||||
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu!-KoA.cbz)
|
||||
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu!-KoAHD.cbz)
|
||||
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu!-KoAH2O.cbz)
|
||||
|
||||
## CHANGELOG
|
||||
####1.0
|
||||
@@ -184,11 +192,11 @@ The app relies and includes the following scripts/binaries:
|
||||
* Added basic error reporting
|
||||
|
||||
####2.2:
|
||||
* Added (valid!) ePub 2.0 output
|
||||
* Added (valid!) EPUB 2.0 output
|
||||
* Rename .zip files to .cbz to avoid overwriting
|
||||
|
||||
####2.3
|
||||
* Fixed win32 ePub generation, folder handling, filenames with spaces and subfolders
|
||||
* Fixed win32 EPUB generation, folder handling, filenames with spaces and subfolders
|
||||
|
||||
####2.4
|
||||
* Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
|
||||
@@ -196,22 +204,22 @@ The app relies and includes the following scripts/binaries:
|
||||
|
||||
####2.5
|
||||
* Added --black-borders option to set added borders black when page's ratio is not the device's one (#11).
|
||||
* Fixes epub containing zipped itself (#10)
|
||||
* Fixes EPUB containing zipped itself (#10)
|
||||
|
||||
####2.6
|
||||
* Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
|
||||
* Added --output option to customize ePub output dir/file (#22)
|
||||
* Add rendition:layout and rendition:orientation ePub meta tags (supported by new kindlegen 2.8)
|
||||
* Added --output option to customize EPUB output dir/file (#22)
|
||||
* Add rendition:layout and rendition:orientation EPUB meta tags (supported by new kindlegen 2.8)
|
||||
* Fixed natural sorting for files (#18)
|
||||
|
||||
####2.7
|
||||
* Lots of GUI improvements (#27, #13)
|
||||
* Added gamma support within --gamma option (defaults to profile-specified gamma) (#26, #27)
|
||||
* Added --nodithering option to prevent dithering optimizations (#27)
|
||||
* Epub margins support (#30)
|
||||
* EPUB margins support (#30)
|
||||
* Fixed no file added if file has no spaces on Windows (#25)
|
||||
* Gracefully exit if unrar missing (#15)
|
||||
* Do not call kindlegen if source epub is bigger than 320MB (#17)
|
||||
* Do not call kindlegen if source EPUB is bigger than 320MB (#17)
|
||||
* Get filetype from magic number (#14)
|
||||
* PDF conversion works again
|
||||
|
||||
@@ -225,7 +233,7 @@ The app relies and includes the following scripts/binaries:
|
||||
* Optimized archive extraction for zip/rar files (#40)
|
||||
|
||||
####2.9
|
||||
* Added support for generating a plain CBZ (skipping all the EPUB/Mobi generation) (#45)
|
||||
* Added support for generating a plain CBZ (skipping all the EPUB/MOBI generation) (#45)
|
||||
* Prevent output file overwriting the source one: if a duplicate name is detected, append _kcc to the name
|
||||
* Rarfile library updated to 2.6
|
||||
* Added GIF, TIFF and BMP to supported formats (#42)
|
||||
@@ -233,7 +241,7 @@ The app relies and includes the following scripts/binaries:
|
||||
|
||||
####2.10:
|
||||
* Multiprocessing support
|
||||
* Kindle Fire support (color ePub/Mobi)
|
||||
* Kindle Fire support (color EPUB/MOBI)
|
||||
* Panel View support for horizontal content
|
||||
* Fixed panel order for horizontal pages when --rotate is enabled
|
||||
* Disabled cropping and page number cutting for blank pages
|
||||
@@ -339,6 +347,43 @@ The app relies and includes the following scripts/binaries:
|
||||
* Improved performance of WebToon splitter
|
||||
* Tweaked margin color detection
|
||||
|
||||
####4.0.2:
|
||||
* Fixed some Windows and OSX specific bugs
|
||||
* Fixed problem with marigns when using HQ mode
|
||||
|
||||
####4.1:
|
||||
* Thanks to code contributed by Kevin Hendricks speed of MOBI creation was greatly increased
|
||||
* Improved performance on Windows
|
||||
* Improved MOBI splitting and changed maximal size of output file
|
||||
* Fixed _No optimization_ mode
|
||||
* Multiple small tweaks nad minor bug fixes
|
||||
|
||||
####4.2:
|
||||
* Added [Manga Cover Database](http://manga.joentjuh.nl/) support
|
||||
* Officially dropped Windows XP support
|
||||
* Fixed _Other_ profile
|
||||
* Fixed problems with page order on stock KOBO CBZ reader
|
||||
* Many other small bug fixes and tweaks
|
||||
|
||||
####4.2.1:
|
||||
* Improved margin color detection
|
||||
* Fixed random crashes of MOBI processing step
|
||||
* Fixed resizing problems in high quality mode
|
||||
* Fixed some MCD support bugs
|
||||
* Default output format for Kindle DX is now CBZ
|
||||
|
||||
####4.3:
|
||||
* Added profiles for Kindle Voyage and Kobo Aura H2O
|
||||
* Added missing features to CLI version
|
||||
* Other minor bug fixes
|
||||
|
||||
####4.3.1:
|
||||
* Fixed Kindle Voyage profile
|
||||
* Fixed some bugs in OS X release
|
||||
* CLI version now support multiple input files at once
|
||||
* Disabled MCB support
|
||||
* Other minor tweaks
|
||||
|
||||
## KNOWN ISSUES
|
||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||
|
||||
|
||||
34
kcc-c2e.py
34
kcc-c2e.py
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -18,9 +18,9 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
__version__ = '4.0.1'
|
||||
__version__ = '4.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys
|
||||
@@ -28,25 +28,27 @@ if sys.version_info[0] != 3:
|
||||
print('ERROR: This is Python 3 script!')
|
||||
exit(1)
|
||||
|
||||
# Dependiences check
|
||||
# Dependency check
|
||||
missing = []
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from psutil import TOTAL_PHYMEM, Popen
|
||||
import psutil
|
||||
if tuple(map(int, ('2.0.0'.split(".")))) > tuple(map(int, psutil.version_info)):
|
||||
missing.append('psutil 2.0.0+')
|
||||
except ImportError:
|
||||
missing.append('psutil')
|
||||
missing.append('psutil 2.0.0+')
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from slugify import slugify
|
||||
import PIL
|
||||
if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))):
|
||||
missing.append('Pillow 2.5.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 2.5.0+')
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
import slugify
|
||||
except ImportError:
|
||||
missing.append('python-slugify')
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops
|
||||
if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))):
|
||||
missing.append('Pillow 2.3.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 2.3.0+')
|
||||
if len(missing) > 0:
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
@@ -61,10 +63,10 @@ if len(missing) > 0:
|
||||
exit(1)
|
||||
|
||||
from multiprocessing import freeze_support
|
||||
from kcc.comic2ebook import main, Copyright
|
||||
from kcc.comic2ebook import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
freeze_support()
|
||||
Copyright()
|
||||
print(('comic2ebook v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals()))
|
||||
main(sys.argv[1:])
|
||||
sys.exit(0)
|
||||
26
kcc-c2p.py
26
kcc-c2p.py
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -18,25 +18,25 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
__version__ = '4.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys
|
||||
if sys.version_info[0] != 3:
|
||||
print('ERROR: This is Python 3 script!')
|
||||
exit(1)
|
||||
|
||||
__version__ = '4.0.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
# Dependiences check
|
||||
# Dependency check
|
||||
missing = []
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops
|
||||
if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))):
|
||||
missing.append('Pillow 2.3.0+')
|
||||
import PIL
|
||||
if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))):
|
||||
missing.append('Pillow 2.5.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 2.3.0+')
|
||||
missing.append('Pillow 2.5.0+')
|
||||
if len(missing) > 0:
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
@@ -51,10 +51,10 @@ if len(missing) > 0:
|
||||
exit(1)
|
||||
|
||||
from multiprocessing import freeze_support
|
||||
from kcc.comic2panel import main, Copyright
|
||||
from kcc.comic2panel import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
freeze_support()
|
||||
Copyright()
|
||||
print(('comic2ebook v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals()))
|
||||
main(sys.argv[1:])
|
||||
sys.exit(0)
|
||||
21
kcc.iss
21
kcc.iss
@@ -1,7 +1,7 @@
|
||||
#define MyAppName "Kindle Comic Converter"
|
||||
#define MyAppVersion "4.0.1"
|
||||
#define MyAppVersion "4.3.1"
|
||||
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
|
||||
#define MyAppURL "http://kcc.vulturis.eu/"
|
||||
#define MyAppURL "http://kcc.iosphe.re/"
|
||||
#define MyAppExeName "KCC.exe"
|
||||
|
||||
[Setup]
|
||||
@@ -30,6 +30,7 @@ UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
ChangesAssociations=True
|
||||
InfoAfterFile=other\InstallWarning.rtf
|
||||
SignTool=SignTool /d $q{#MyAppName}$q /du $q{#MyAppURL}$q $f
|
||||
MinVersion=0,6.0
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
@@ -42,18 +43,14 @@ Name: "CB7association"; Description: "CB7"; GroupDescription: "File associations
|
||||
|
||||
[Files]
|
||||
; x64 files
|
||||
Source: "build\exe.win-amd64-3.3\imageformats\*"; DestDir: "{app}\imageformats\"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "build\exe.win-amd64-3.3\platforms\*"; DestDir: "{app}\platforms\"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "build\exe.win-amd64-3.3\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "build\exe.win-amd64-3.3\*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "build\exe.win-amd64-3.3\*.dll"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "dist_64\platforms\*"; DestDir: "{app}\platforms\"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "dist_64\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "dist_64\*.dll"; DestDir: "{app}"; Flags: ignoreversion; Check: Is64BitInstallMode
|
||||
Source: "other\vcredist_x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall; Check: Is64BitInstallMode
|
||||
; x86 files
|
||||
Source: "build\exe.win32-3.3\imageformats\*"; DestDir: "{app}\imageformats\"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "build\exe.win32-3.3\platforms\*"; DestDir: "{app}\platforms\"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "build\exe.win32-3.3\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "build\exe.win32-3.3\*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "build\exe.win32-3.3\*.dll"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "dist\platforms\*"; DestDir: "{app}\platforms\"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "dist\*.dll"; DestDir: "{app}"; Flags: ignoreversion; Check: not Is64BitInstallMode
|
||||
Source: "other\vcredist_x86.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall; Check: not Is64BitInstallMode
|
||||
; Common files
|
||||
Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak
|
||||
|
||||
95
kcc.py
95
kcc.py
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -18,9 +18,9 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
__version__ = '4.0.1'
|
||||
__version__ = '4.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys
|
||||
@@ -28,30 +28,34 @@ if sys.version_info[0] != 3:
|
||||
print('ERROR: This is Python 3 script!')
|
||||
exit(1)
|
||||
|
||||
# Dependiences check
|
||||
# Dependency check
|
||||
missing = []
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
|
||||
from PyQt5 import QtCore, QtNetwork, QtWidgets
|
||||
if tuple(map(int, ('5.2.0'.split(".")))) > tuple(map(int, (QtCore.qVersion().split(".")))):
|
||||
missing.append('PyQt5 5.2.0+')
|
||||
except ImportError:
|
||||
missing.append('PyQt5')
|
||||
missing.append('PyQt5 5.2.0+')
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from psutil import TOTAL_PHYMEM, Popen
|
||||
import psutil
|
||||
if tuple(map(int, ('2.0.0'.split(".")))) > tuple(map(int, psutil.version_info)):
|
||||
missing.append('psutil 2.0.0+')
|
||||
except ImportError:
|
||||
missing.append('psutil')
|
||||
missing.append('psutil 2.0.0+')
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from slugify import slugify
|
||||
import PIL
|
||||
if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))):
|
||||
missing.append('Pillow 2.5.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 2.5.0+')
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
import slugify
|
||||
except ImportError:
|
||||
missing.append('python-slugify')
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops
|
||||
if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))):
|
||||
missing.append('Pillow 2.3.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 2.3.0+')
|
||||
if len(missing) > 0:
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
@@ -70,14 +74,21 @@ from multiprocessing import freeze_support
|
||||
from kcc import KCC_gui
|
||||
|
||||
# OS specific PATH variable workarounds
|
||||
if sys.platform.startswith('darwin'):
|
||||
if 'RESOURCEPATH' in os.environ:
|
||||
os.environ['PATH'] = os.environ['RESOURCEPATH'] + ':' + os.environ['PATH']
|
||||
else:
|
||||
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/:' + os.environ['PATH']
|
||||
if sys.platform.startswith('darwin') and 'RESOURCEPATH' not in os.environ:
|
||||
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/:' + os.environ['PATH']
|
||||
elif sys.platform.startswith('win'):
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
|
||||
# Implementing dummy stdout and stderr for frozen Windows release
|
||||
class FakeSTD(object):
|
||||
def write(self, string):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
sys.stdout = FakeSTD()
|
||||
sys.stderr = FakeSTD()
|
||||
else:
|
||||
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/;' + os.environ['PATH']
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -89,35 +100,33 @@ class QApplicationMessaging(QtWidgets.QApplication):
|
||||
|
||||
def __init__(self, argv):
|
||||
QtWidgets.QApplication.__init__(self, argv)
|
||||
self._memory = QtCore.QSharedMemory(self)
|
||||
self._memory.setKey('KCC')
|
||||
if self._memory.attach():
|
||||
self._running = True
|
||||
else:
|
||||
self._running = False
|
||||
self._memory.create(1)
|
||||
self._key = 'KCC'
|
||||
self._timeout = 1000
|
||||
self._server = QtNetwork.QLocalServer(self)
|
||||
if not self.isRunning():
|
||||
self._locked = False
|
||||
socket = QtNetwork.QLocalSocket(self)
|
||||
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
|
||||
if not socket.waitForConnected(self._timeout):
|
||||
self._server = QtNetwork.QLocalServer(self)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self._server.newConnection.connect(self.handleMessage)
|
||||
self._server.listen(self._key)
|
||||
else:
|
||||
self._locked = True
|
||||
socket.disconnectFromServer()
|
||||
|
||||
def __del__(self):
|
||||
if self._memory.isAttached():
|
||||
self._memory.detach()
|
||||
if not self._locked:
|
||||
self._server.close()
|
||||
|
||||
def event(self, e):
|
||||
if e.type() == QtCore.QEvent.FileOpen:
|
||||
# noinspection PyArgumentList
|
||||
self.messageFromOtherInstance.emit(bytes(e.file(), 'UTF-8'))
|
||||
return True
|
||||
else:
|
||||
return QtWidgets.QApplication.event(self, e)
|
||||
|
||||
def isRunning(self):
|
||||
return self._running
|
||||
return self._locked
|
||||
|
||||
def handleMessage(self):
|
||||
socket = self._server.nextPendingConnection()
|
||||
@@ -125,18 +134,12 @@ class QApplicationMessaging(QtWidgets.QApplication):
|
||||
self.messageFromOtherInstance.emit(socket.readAll().data())
|
||||
|
||||
def sendMessage(self, message):
|
||||
if self.isRunning():
|
||||
socket = QtNetwork.QLocalSocket(self)
|
||||
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
|
||||
if not socket.waitForConnected(self._timeout):
|
||||
return False
|
||||
# noinspection PyArgumentList
|
||||
socket.write(bytes(message, 'UTF-8'))
|
||||
if not socket.waitForBytesWritten(self._timeout):
|
||||
return False
|
||||
socket.disconnectFromServer()
|
||||
return True
|
||||
return False
|
||||
socket = QtNetwork.QLocalSocket(self)
|
||||
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
|
||||
socket.waitForConnected(self._timeout)
|
||||
socket.write(bytes(message, 'UTF-8'))
|
||||
socket.waitForBytesWritten(self._timeout)
|
||||
socket.disconnectFromServer()
|
||||
|
||||
|
||||
# Adding signals to QMainWindow
|
||||
|
||||
329
kcc/KCC_gui.py
329
kcc/KCC_gui.py
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -17,9 +17,9 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
__version__ = '4.0.1'
|
||||
__version__ = '4.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
@@ -36,10 +36,10 @@ from subprocess import STDOUT, PIPE
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from xml.dom.minidom import parse
|
||||
from html.parser import HTMLParser
|
||||
from psutil import TOTAL_PHYMEM, Popen
|
||||
from psutil import Popen, Process
|
||||
from copy import copy
|
||||
from .shared import md5Checksum
|
||||
from . import comic2ebook
|
||||
from . import kindlesplit
|
||||
from . import KCC_rc_web
|
||||
if sys.platform.startswith('darwin'):
|
||||
from . import KCC_ui_osx as KCC_ui
|
||||
@@ -111,15 +111,15 @@ class WebServerHandler(BaseHTTPRequestHandler):
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes('<!DOCTYPE html>\n'
|
||||
'<html lang="en">\n'
|
||||
'<head><meta charset="utf-8">\n'
|
||||
'<link href="' + GUI.webContent.favicon + '" rel="icon" type="image/x-icon" />\n'
|
||||
'<title>Kindle Comic Converter</title>\n'
|
||||
'</head>\n'
|
||||
'<body>\n'
|
||||
'<div style="text-align: center; font-size:25px">\n'
|
||||
'<p style="font-size:50px">- <img style="vertical-align: middle" '
|
||||
'alt="KCC Logo" src="' + GUI.webContent.logo + '" /> -</p>\n', 'UTF-8'))
|
||||
'<html lang="en">\n'
|
||||
'<head><meta charset="utf-8">\n'
|
||||
'<link href="' + GUI.webContent.favicon + '" rel="icon" type="image/x-icon" />\n'
|
||||
'<title>Kindle Comic Converter</title>\n'
|
||||
'</head>\n'
|
||||
'<body>\n'
|
||||
'<div style="text-align: center; font-size:25px">\n'
|
||||
'<p style="font-size:50px">- <img style="vertical-align: middle" '
|
||||
'alt="KCC Logo" src="' + GUI.webContent.logo + '" /> -</p>\n', 'UTF-8'))
|
||||
if len(GUI.completedWork) > 0 and not GUI.conversionAlive:
|
||||
for key in sorted(GUI.completedWork.keys()):
|
||||
self.wfile.write(bytes('<p><a href="' + key + '">' + key.split('.')[0] + '</a></p>\n', 'UTF-8'))
|
||||
@@ -127,8 +127,8 @@ class WebServerHandler(BaseHTTPRequestHandler):
|
||||
self.wfile.write(bytes('<p style="font-weight: bold">No downloads are available.<br/>'
|
||||
'Convert some files and refresh this page.</p>\n', 'UTF-8'))
|
||||
self.wfile.write(bytes('</div>\n'
|
||||
'</body>\n'
|
||||
'</html>\n', 'UTF-8'))
|
||||
'</body>\n'
|
||||
'</html>\n', 'UTF-8'))
|
||||
elif sendReply:
|
||||
outputFile = GUI.completedWork[unquote(self.path[1:])]
|
||||
fp = open(outputFile, 'rb')
|
||||
@@ -195,7 +195,7 @@ class VersionThread(QtCore.QThread):
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
XML = urlopen('http://kcc.vulturis.eu/Version.php')
|
||||
XML = urlopen('http://kcc.iosphe.re/Version.php')
|
||||
XML = parse(XML)
|
||||
except Exception:
|
||||
return
|
||||
@@ -209,7 +209,7 @@ class VersionThread(QtCore.QThread):
|
||||
'<br/>Current version: ' + latestVersion +
|
||||
'<br/><br/>Would you like to start automatic update?', 'question')
|
||||
else:
|
||||
MW.addMessage.emit('<a href="http://kcc.vulturis.eu/">'
|
||||
MW.addMessage.emit('<a href="http://kcc.iosphe.re/">'
|
||||
'<b>New version is available!</b></a> '
|
||||
'(<a href="https://github.com/ciromattia/kcc/releases/">'
|
||||
'Changelog</a>)', 'warning', False)
|
||||
@@ -219,7 +219,7 @@ class VersionThread(QtCore.QThread):
|
||||
try:
|
||||
MW.modeConvert.emit(-1)
|
||||
MW.progressBarTick.emit('Downloading update')
|
||||
path = urlretrieve('http://kcc.vulturis.eu/Windows/KindleComicConverter_win_'
|
||||
path = urlretrieve('http://kcc.iosphe.re/Windows/KindleComicConverter_win_'
|
||||
+ self.newVersion + '.exe', reporthook=self.getNewVersionTick)
|
||||
if self.md5 != md5Checksum(path[0]):
|
||||
raise Exception
|
||||
@@ -263,90 +263,14 @@ class ProgressThread(QtCore.QThread):
|
||||
self.running = False
|
||||
|
||||
|
||||
class WorkerSignals(QtCore.QObject):
|
||||
result = QtCore.pyqtSignal(list)
|
||||
|
||||
|
||||
class KindleGenThread(QtCore.QRunnable):
|
||||
def __init__(self, batch):
|
||||
super(KindleGenThread, self).__init__()
|
||||
self.signals = WorkerSignals()
|
||||
self.work = batch
|
||||
|
||||
def run(self):
|
||||
kindlegenErrorCode = 0
|
||||
kindlegenError = ''
|
||||
try:
|
||||
if os.path.getsize(self.work) < 367001600:
|
||||
output = Popen('kindlegen -locale en "' + self.work + '"', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
for line in output.stdout:
|
||||
line = line.decode('utf-8')
|
||||
# ERROR: Generic error
|
||||
if "Error(" in line:
|
||||
kindlegenErrorCode = 1
|
||||
kindlegenError = line
|
||||
# ERROR: EPUB too big
|
||||
if ":E23026:" in line:
|
||||
kindlegenErrorCode = 23026
|
||||
if kindlegenErrorCode > 0:
|
||||
break
|
||||
else:
|
||||
# ERROR: EPUB too big
|
||||
kindlegenErrorCode = 23026
|
||||
self.signals.result.emit([kindlegenErrorCode, kindlegenError, self.work])
|
||||
except Exception as err:
|
||||
# ERROR: KCC unknown generic error
|
||||
kindlegenErrorCode = 1
|
||||
kindlegenError = format(err)
|
||||
self.signals.result.emit([kindlegenErrorCode, kindlegenError, self.work])
|
||||
|
||||
|
||||
class KindleUnpackThread(QtCore.QRunnable):
|
||||
def __init__(self, batch):
|
||||
super(KindleUnpackThread, self).__init__()
|
||||
self.signals = WorkerSignals()
|
||||
self.work = batch
|
||||
|
||||
def run(self):
|
||||
item = self.work[0]
|
||||
profile = self.work[1]
|
||||
os.remove(item)
|
||||
mobiPath = item.replace('.epub', '.mobi')
|
||||
move(mobiPath, mobiPath + '_toclean')
|
||||
try:
|
||||
# MOBI file produced by KindleGen is hybrid. KF8 + M7 + Source header
|
||||
# KindleSplit is removing redundant data as we need only KF8 part for new Kindle models
|
||||
if profile in ['K345', 'KHD', 'KF', 'KFHD', 'KFHD8', 'KFHDX', 'KFHDX8', 'KFA']:
|
||||
newKindle = True
|
||||
else:
|
||||
newKindle = False
|
||||
mobisplit = kindlesplit.mobi_split(mobiPath + '_toclean', newKindle)
|
||||
open(mobiPath, 'wb').write(mobisplit.getResult())
|
||||
self.signals.result.emit([True])
|
||||
except Exception as err:
|
||||
self.signals.result.emit([False, format(err)])
|
||||
|
||||
|
||||
class WorkerThread(QtCore.QThread):
|
||||
#noinspection PyArgumentList
|
||||
# noinspection PyArgumentList
|
||||
def __init__(self):
|
||||
QtCore.QThread.__init__(self)
|
||||
self.pool = QtCore.QThreadPool()
|
||||
self.conversionAlive = False
|
||||
self.errors = False
|
||||
self.kindlegenErrorCode = [0]
|
||||
self.workerOutput = []
|
||||
# Let's make sure that we don't fill the memory
|
||||
availableMemory = TOTAL_PHYMEM/1000000000
|
||||
if availableMemory <= 2:
|
||||
self.threadNumber = 1
|
||||
elif 2 < availableMemory <= 4:
|
||||
self.threadNumber = 2
|
||||
else:
|
||||
self.threadNumber = 4
|
||||
# Let's make sure that we don't use too many threads
|
||||
if self.threadNumber > QtCore.QThread.idealThreadCount():
|
||||
self.threadNumber = QtCore.QThread.idealThreadCount()
|
||||
self.progressBarTick = MW.progressBarTick
|
||||
self.addMessage = MW.addMessage
|
||||
|
||||
@@ -365,55 +289,65 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.addTrayMessage.emit('Conversion interrupted.', 'Critical')
|
||||
MW.modeConvert.emit(1)
|
||||
|
||||
def addResult(self, output):
|
||||
MW.progressBarTick.emit('tick')
|
||||
self.workerOutput.append(output)
|
||||
def sanitizeTrace(self, traceback):
|
||||
return ''.join(format_tb(traceback))\
|
||||
.replace('C:\\Users\\AcidWeb\\Documents\\Projekty\\KCC\\', '')\
|
||||
.replace('C:\\Python34\\', '')\
|
||||
.replace('C:\\Python34_64\\', '')
|
||||
|
||||
def run(self):
|
||||
MW.modeConvert.emit(0)
|
||||
|
||||
parser = comic2ebook.makeParser()
|
||||
options, _ = parser.parse_args()
|
||||
|
||||
profile = GUI.profiles[str(GUI.DeviceBox.currentText())]['Label']
|
||||
argv = ["--profile=" + profile]
|
||||
options.profile = profile
|
||||
argv = ''
|
||||
currentJobs = []
|
||||
|
||||
# Basic mode settings
|
||||
if GUI.MangaBox.isChecked():
|
||||
argv.append("--manga-style")
|
||||
options.righttoleft = True
|
||||
if GUI.RotateBox.isChecked():
|
||||
argv.append("--rotate")
|
||||
options.rotate = True
|
||||
if GUI.QualityBox.checkState() == 1:
|
||||
argv.append("--quality=1")
|
||||
options.quality = 1
|
||||
elif GUI.QualityBox.checkState() == 2:
|
||||
argv.append("--quality=2")
|
||||
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
||||
argv.append("--cbz-output")
|
||||
options.quality = 2
|
||||
options.format = str(GUI.FormatBox.currentText())
|
||||
if GUI.currentMode == 1:
|
||||
if profile in ['KFHD', 'KFHD8', 'KFHDX', 'KFHDX8']:
|
||||
argv.append("--upscale")
|
||||
if 'KFH' in profile:
|
||||
options.upscale = True
|
||||
|
||||
# Advanced mode settings
|
||||
if GUI.currentMode > 1:
|
||||
if GUI.ProcessingBox.isChecked():
|
||||
argv.append("--noprocessing")
|
||||
options.imgproc = False
|
||||
if GUI.NoRotateBox.isChecked():
|
||||
argv.append("--nosplitrotate")
|
||||
options.nosplitrotate = True
|
||||
if GUI.UpscaleBox.checkState() == 1:
|
||||
argv.append("--stretch")
|
||||
options.stretch = True
|
||||
elif GUI.UpscaleBox.checkState() == 2:
|
||||
argv.append("--upscale")
|
||||
options.upscale = True
|
||||
if GUI.BorderBox.checkState() == 1:
|
||||
argv.append("--whiteborders")
|
||||
options.white_borders = True
|
||||
elif GUI.BorderBox.checkState() == 2:
|
||||
argv.append("--blackborders")
|
||||
options.black_borders = True
|
||||
if GUI.NoDitheringBox.isChecked():
|
||||
argv.append("--forcepng")
|
||||
options.forcepng = True
|
||||
if GUI.WebtoonBox.isChecked():
|
||||
argv.append("--webtoon")
|
||||
options.webtoon = True
|
||||
if float(GUI.GammaValue) > 0.09:
|
||||
# noinspection PyTypeChecker
|
||||
argv.append("--gamma=" + GUI.GammaValue)
|
||||
if str(GUI.FormatBox.currentText()) == 'MOBI':
|
||||
argv.append("--batchsplit")
|
||||
options.gamma = float(GUI.GammaValue)
|
||||
|
||||
# Other/custom settings.
|
||||
if GUI.currentMode > 2:
|
||||
argv.append("--customwidth=" + str(GUI.customWidth.text()))
|
||||
argv.append("--customheight=" + str(GUI.customHeight.text()))
|
||||
options.customwidth = str(GUI.customWidth.text())
|
||||
options.customheight = str(GUI.customHeight.text())
|
||||
if GUI.ColorBox.isChecked():
|
||||
argv.append("--forcecolor")
|
||||
options.forcecolor = True
|
||||
|
||||
for i in range(GUI.JobList.count()):
|
||||
# Make sure that we don't consider any system message as job to do
|
||||
if GUI.JobList.item(i).icon().isNull():
|
||||
@@ -435,7 +369,9 @@ class WorkerThread(QtCore.QThread):
|
||||
jobargv = list(argv)
|
||||
jobargv.append(job)
|
||||
try:
|
||||
outputPath = comic2ebook.main(jobargv, self)
|
||||
comic2ebook.options = copy(options)
|
||||
comic2ebook.checkOptions()
|
||||
outputPath = comic2ebook.makeBook(job, self)
|
||||
MW.hideProgressBar.emit()
|
||||
except UserWarning as warn:
|
||||
if not self.conversionAlive:
|
||||
@@ -452,7 +388,7 @@ class WorkerThread(QtCore.QThread):
|
||||
self.errors = True
|
||||
_, _, traceback = sys.exc_info()
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), "".join(format_tb(traceback))), 'error')
|
||||
% (jobargv[-1], str(err), self.sanitizeTrace(traceback)), 'error')
|
||||
MW.addMessage.emit('Failed to create EPUB!', 'error', False)
|
||||
MW.addTrayMessage.emit('Failed to create EPUB!', 'Critical')
|
||||
if not self.conversionAlive:
|
||||
@@ -473,15 +409,10 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.progressBarTick.emit('tick')
|
||||
MW.addMessage.emit('Creating MOBI files', 'info', False)
|
||||
GUI.progress.content = 'Creating MOBI files'
|
||||
self.workerOutput = []
|
||||
# Number of KindleGen threads depends on the size of RAM
|
||||
self.pool.setMaxThreadCount(self.threadNumber)
|
||||
work = []
|
||||
for item in outputPath:
|
||||
worker = KindleGenThread(item)
|
||||
worker.signals.result.connect(self.addResult)
|
||||
self.pool.start(worker)
|
||||
self.pool.waitForDone()
|
||||
sleep(0.5)
|
||||
work.append([item])
|
||||
self.workerOutput = comic2ebook.makeMOBI(work, self)
|
||||
self.kindlegenErrorCode = [0]
|
||||
for errors in self.workerOutput:
|
||||
if errors[0] != 0:
|
||||
@@ -498,20 +429,14 @@ class WorkerThread(QtCore.QThread):
|
||||
if self.kindlegenErrorCode[0] == 0:
|
||||
GUI.progress.content = ''
|
||||
MW.addMessage.emit('Creating MOBI files... <b>Done!</b>', 'info', True)
|
||||
MW.addMessage.emit('Cleaning MOBI files', 'info', False)
|
||||
GUI.progress.content = 'Cleaning MOBI files'
|
||||
MW.addMessage.emit('Processing MOBI files', 'info', False)
|
||||
GUI.progress.content = 'Processing MOBI files'
|
||||
self.workerOutput = []
|
||||
# Multithreading KindleUnpack in current form is a waste of resources.
|
||||
# Unless we higly optimise KindleUnpack or drop 32bit support this will not change.
|
||||
self.pool.setMaxThreadCount(1)
|
||||
for item in outputPath:
|
||||
worker = KindleUnpackThread([item, profile])
|
||||
worker.signals.result.connect(self.addResult)
|
||||
self.pool.start(worker)
|
||||
self.pool.waitForDone()
|
||||
sleep(0.5)
|
||||
self.workerOutput.append(comic2ebook.makeMOBIFix(item))
|
||||
MW.progressBarTick.emit('tick')
|
||||
for success in self.workerOutput:
|
||||
if not success:
|
||||
if not success[0]:
|
||||
self.errors = True
|
||||
break
|
||||
if not self.errors:
|
||||
@@ -526,7 +451,7 @@ class WorkerThread(QtCore.QThread):
|
||||
except Exception:
|
||||
pass
|
||||
GUI.completedWork[os.path.basename(mobiPath)] = mobiPath
|
||||
MW.addMessage.emit('Cleaning MOBI files... <b>Done!</b>', 'info', True)
|
||||
MW.addMessage.emit('Processing MOBI files... <b>Done!</b>', 'info', True)
|
||||
else:
|
||||
GUI.progress.content = ''
|
||||
for item in outputPath:
|
||||
@@ -535,11 +460,11 @@ class WorkerThread(QtCore.QThread):
|
||||
os.remove(mobiPath)
|
||||
if os.path.exists(mobiPath + '_toclean'):
|
||||
os.remove(mobiPath + '_toclean')
|
||||
MW.addMessage.emit('KindleUnpack failed to clean MOBI file!', 'error', False)
|
||||
MW.addTrayMessage.emit('KindleUnpack failed to clean MOBI file!', 'Critical')
|
||||
MW.addMessage.emit('Failed to process MOBI file!', 'error', False)
|
||||
MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical')
|
||||
else:
|
||||
GUI.progress.content = ''
|
||||
epubSize = (os.path.getsize(self.kindlegenErrorCode[2]))/1024/1024
|
||||
epubSize = (os.path.getsize(self.kindlegenErrorCode[2]))//1024//1024
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
os.remove(item)
|
||||
@@ -551,7 +476,7 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error')
|
||||
if self.kindlegenErrorCode[0] == 23026:
|
||||
MW.addMessage.emit('Created EPUB file was too big.', 'error', False)
|
||||
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~300MB.', 'error',
|
||||
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
||||
False)
|
||||
else:
|
||||
for item in outputPath:
|
||||
@@ -575,6 +500,7 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
||||
def __init__(self):
|
||||
if self.isSystemTrayAvailable():
|
||||
QtWidgets.QSystemTrayIcon.__init__(self, GUI.icons.programIcon, MW)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.activated.connect(self.catchClicks)
|
||||
|
||||
def catchClicks(self):
|
||||
@@ -820,6 +746,9 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
if value == 2 and 'Kobo' in str(GUI.DeviceBox.currentText()):
|
||||
self.addMessage('Kobo devices can\'t use ultra quality mode!', 'warning')
|
||||
GUI.QualityBox.setCheckState(0)
|
||||
elif value == 2 and 'CBZ' in str(GUI.FormatBox.currentText()):
|
||||
self.addMessage('CBZ format don\'t support ultra quality mode!', 'warning')
|
||||
GUI.QualityBox.setCheckState(0)
|
||||
|
||||
def changeGamma(self, value):
|
||||
value = float(value)
|
||||
@@ -847,7 +776,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
GUI.AdvModeButton.setEnabled(True)
|
||||
if self.currentMode == 3:
|
||||
self.modeBasic()
|
||||
self.changeFormat()
|
||||
self.changeFormat(event=False)
|
||||
GUI.GammaSlider.setValue(0)
|
||||
self.changeGamma(0)
|
||||
if profile['DefaultUpscale']:
|
||||
@@ -856,19 +785,12 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
|
||||
'List of supported Non-Kindle devices.</a>', 'info')
|
||||
|
||||
def changeFormat(self, outputFormat=None):
|
||||
def changeFormat(self, outputFormat=None, event=True):
|
||||
profile = GUI.profiles[str(GUI.DeviceBox.currentText())]
|
||||
if outputFormat is not None:
|
||||
GUI.FormatBox.setCurrentIndex(outputFormat)
|
||||
else:
|
||||
if GUI.FormatBox.count() == 3:
|
||||
GUI.FormatBox.setCurrentIndex(profile['DefaultFormat'])
|
||||
else:
|
||||
if profile['DefaultFormat'] != 0:
|
||||
tmpFormat = profile['DefaultFormat'] - 1
|
||||
else:
|
||||
tmpFormat = 0
|
||||
GUI.FormatBox.setCurrentIndex(tmpFormat)
|
||||
GUI.FormatBox.setCurrentIndex(profile['DefaultFormat'])
|
||||
if GUI.WebtoonBox.isChecked():
|
||||
GUI.MangaBox.setEnabled(False)
|
||||
GUI.QualityBox.setEnabled(False)
|
||||
@@ -881,6 +803,10 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
if GUI.ProcessingBox.isChecked():
|
||||
GUI.QualityBox.setEnabled(False)
|
||||
GUI.QualityBox.setChecked(False)
|
||||
if event and GUI.QualityBox.isEnabled() and 'CBZ' in str(GUI.FormatBox.currentText()) and\
|
||||
GUI.QualityBox.checkState() == 2:
|
||||
self.addMessage('CBZ format don\'t support ultra quality mode!', 'warning')
|
||||
GUI.QualityBox.setCheckState(0)
|
||||
|
||||
def stripTags(self, html):
|
||||
s = HTMLStripper()
|
||||
@@ -959,6 +885,15 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.addMessage('Target resolution is not set!', 'error')
|
||||
self.needClean = True
|
||||
return
|
||||
if str(GUI.FormatBox.currentText()) == 'MOBI' and not GUI.KindleGen:
|
||||
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">'
|
||||
'<b>KindleGen</b></a>! MOBI conversion is not possible!', 'error')
|
||||
if sys.platform.startswith('win'):
|
||||
self.addMessage('Download it and place EXE in KCC directory.', 'error')
|
||||
else:
|
||||
self.addMessage('Download it, and place executable in /usr/local/bin directory.', 'error')
|
||||
self.needClean = True
|
||||
return
|
||||
self.worker.start()
|
||||
|
||||
def hideProgressBar(self):
|
||||
@@ -996,8 +931,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
'customHeight': GUI.customHeight.text(),
|
||||
'GammaSlider': float(self.GammaValue)*100})
|
||||
self.settings.sync()
|
||||
if not sys.platform.startswith('linux'):
|
||||
self.tray.hide()
|
||||
self.tray.hide()
|
||||
|
||||
def handleMessage(self, message):
|
||||
MW.raise_()
|
||||
@@ -1072,6 +1006,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.versionCheck = VersionThread()
|
||||
self.contentServer = WebServerThread()
|
||||
self.progress = ProgressThread()
|
||||
self.tray = SystemTrayIcon()
|
||||
self.conversionAlive = False
|
||||
self.needClean = True
|
||||
self.GammaValue = 1.0
|
||||
@@ -1081,10 +1016,9 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.listFontSize = 11
|
||||
self.statusBarFontSize = 10
|
||||
self.statusBarStyle = 'QLabel{padding-top:2px;padding-bottom:3px;}'
|
||||
self.ProgressBar.setStyleSheet('QProgressBar{padding-top:5px;text-align:center;}')
|
||||
self.tray = SystemTrayIcon()
|
||||
self.tray.show()
|
||||
MW.addTrayMessage.connect(self.tray.addTrayMessage)
|
||||
self.ProgressBar.setStyleSheet('QProgressBar{font-size:13px;text-align:center;'
|
||||
'border:2px solid grey;border-radius:5px;}'
|
||||
'QProgressBar::chunk{background-color:steelblue;width:20px;}')
|
||||
elif sys.platform.startswith('linux'):
|
||||
self.listFontSize = 8
|
||||
self.statusBarFontSize = 8
|
||||
@@ -1095,27 +1029,27 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.statusBarFontSize = 8
|
||||
self.statusBarStyle = 'QLabel{padding-top:3px;padding-bottom:3px}'
|
||||
self.statusBar.setStyleSheet('QStatusBar::item{border:0px;border-top:2px solid #C2C7CB;}')
|
||||
self.tray = SystemTrayIcon()
|
||||
self.tray.show()
|
||||
MW.addTrayMessage.connect(self.tray.addTrayMessage)
|
||||
# Decrease priority to increase system responsiveness during conversion
|
||||
from psutil import BELOW_NORMAL_PRIORITY_CLASS
|
||||
self.p = Process(os.getpid())
|
||||
self.p.nice(BELOW_NORMAL_PRIORITY_CLASS)
|
||||
self.p.ionice(1)
|
||||
|
||||
self.profiles = {
|
||||
"Kindle Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KV'},
|
||||
"Kindle Paperwhite": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KHD'},
|
||||
'DefaultUpscale': False, 'Label': 'KPW'},
|
||||
"Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'K345'},
|
||||
"Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
"Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
'DefaultUpscale': False, 'Label': 'KDX'},
|
||||
"Kindle Fire": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'Label': 'KF'},
|
||||
"K. Fire HD 7\"": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHD'},
|
||||
"K. Fire HD 8.9\"": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHD8'},
|
||||
"K. Fire HDX 7\"": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHDX'},
|
||||
"K. Fire HDX 8.9\"": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHDX8'},
|
||||
"K. Fire HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHD'},
|
||||
"K. Fire HDX": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHDX'},
|
||||
"K. Fire HDX 8.9": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'Label': 'KFHDX8'},
|
||||
"Kobo Mini/Touch": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
'DefaultUpscale': False, 'Label': 'KoMT'},
|
||||
"Kobo Glow": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
@@ -1124,6 +1058,8 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
'DefaultUpscale': False, 'Label': 'KoA'},
|
||||
"Kobo Aura HD": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
'DefaultUpscale': False, 'Label': 'KoAHD'},
|
||||
"Kobo Aura H2O": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
'DefaultUpscale': False, 'Label': 'KoAH2O'},
|
||||
"Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1,
|
||||
'DefaultUpscale': False, 'Label': 'OTHER'},
|
||||
"Kindle for Android": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 0,
|
||||
@@ -1134,29 +1070,29 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
'DefaultUpscale': False, 'Label': 'K2'}
|
||||
}
|
||||
profilesGUI = [
|
||||
"Kindle Voyage",
|
||||
"Kindle Paperwhite",
|
||||
"Kindle",
|
||||
"Kindle DX/DXG",
|
||||
"Separator",
|
||||
"Kindle Fire",
|
||||
"K. Fire HD 7\"",
|
||||
"K. Fire HD 8.9\"",
|
||||
"K. Fire HDX 7\"",
|
||||
"K. Fire HDX 8.9\"",
|
||||
"K. Fire HD",
|
||||
"K. Fire HDX",
|
||||
"K. Fire HDX 8.9",
|
||||
"Separator",
|
||||
"Kobo Mini/Touch",
|
||||
"Kobo Glow",
|
||||
"Kobo Aura",
|
||||
"Kobo Aura HD",
|
||||
"Kobo Aura H2O",
|
||||
"Separator",
|
||||
"Other",
|
||||
"Separator",
|
||||
"Kindle for Android",
|
||||
"Kindle 1",
|
||||
"Kindle 2",
|
||||
"Kindle DX/DXG",
|
||||
]
|
||||
|
||||
statusBarLabel = QtWidgets.QLabel('<b><a href="http://kcc.vulturis.eu/">HOMEPAGE</a> - <a href="https://github.'
|
||||
statusBarLabel = QtWidgets.QLabel('<b><a href="http://kcc.iosphe.re/">HOMEPAGE</a> - <a href="https://github.'
|
||||
'com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations">DO'
|
||||
'NATE</a> - <a href="https://github.com/ciromattia/kcc/wiki">WIKI</a> - <a hr'
|
||||
'ef="http://www.mobileread.com/forums/showthread.php?t=207461">FORUM</a></b>')
|
||||
@@ -1174,10 +1110,14 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.addMessage('Since you are new user of <b>KCC</b> please see few '
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||
'info')
|
||||
if not sys.platform.startswith('win'):
|
||||
try:
|
||||
os.chmod('/usr/local/bin/kindlegen', 0o755)
|
||||
except Exception:
|
||||
pass
|
||||
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
if kindleGenExitCode.wait() == 0:
|
||||
self.KindleGen = True
|
||||
formats = ['MOBI', 'EPUB', 'CBZ']
|
||||
versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
for line in versionCheck.stdout:
|
||||
line = line.decode("utf-8")
|
||||
@@ -1191,13 +1131,6 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
break
|
||||
else:
|
||||
self.KindleGen = False
|
||||
formats = ['EPUB', 'CBZ']
|
||||
if sys.platform.startswith('win'):
|
||||
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">'
|
||||
'kindlegen</a> in KCC directory! MOBI creation will be disabled.', 'warning')
|
||||
else:
|
||||
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">'
|
||||
'kindlegen</a> in PATH! MOBI creation will be disabled.', 'warning')
|
||||
rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
rarExitCode = rarExitCode.wait()
|
||||
if rarExitCode == 0 or rarExitCode == 7:
|
||||
@@ -1237,6 +1170,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
MW.forceShutdown.connect(self.forceShutdown)
|
||||
MW.dialogAnswer.connect(self.versionCheck.getNewVersion)
|
||||
MW.closeEvent = self.saveSettings
|
||||
MW.addTrayMessage.connect(self.tray.addTrayMessage)
|
||||
|
||||
GUI.Form.setAcceptDrops(True)
|
||||
GUI.Form.dragEnterEvent = self.dragAndDrop
|
||||
@@ -1251,7 +1185,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
GUI.DeviceBox.addItem(self.icons.deviceKobo, profile)
|
||||
else:
|
||||
GUI.DeviceBox.addItem(self.icons.deviceKindle, profile)
|
||||
for f in formats:
|
||||
for f in ['MOBI', 'EPUB', 'CBZ']:
|
||||
GUI.FormatBox.addItem(eval('self.icons.' + f + 'Format'), f)
|
||||
if self.lastDevice > GUI.DeviceBox.count():
|
||||
self.lastDevice = 0
|
||||
@@ -1262,7 +1196,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
GUI.DeviceBox.setCurrentIndex(self.lastDevice)
|
||||
self.changeDevice()
|
||||
if self.currentFormat != self.profiles[str(GUI.DeviceBox.currentText())]['DefaultFormat']:
|
||||
self.changeFormat(self.currentFormat)
|
||||
self.changeFormat(self.currentFormat, False)
|
||||
for option in self.options:
|
||||
if str(option) == "customWidth":
|
||||
GUI.customWidth.setText(str(self.options[option]))
|
||||
@@ -1279,6 +1213,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
|
||||
self.worker.sync()
|
||||
self.versionCheck.start()
|
||||
self.contentServer.start()
|
||||
self.tray.show()
|
||||
MW.setWindowTitle("Kindle Comic Converter " + __version__)
|
||||
MW.show()
|
||||
MW.raise_()
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'KCC.ui'
|
||||
#
|
||||
# Created: Sat Jan 25 17:36:53 2014
|
||||
# by: PyQt5 UI code generator 5.2
|
||||
# Created: Sun May 18 09:08:27 2014
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -264,7 +264,7 @@ class Ui_KCC(object):
|
||||
self.ProcessingBox.setText(_translate("KCC", "No optimisation"))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>"))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale"))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>"))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\"white-space:pre\">Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>This mode is created for pages with a low width, high height and vertical panel flow.</p></body></html>"))
|
||||
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode"))
|
||||
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Create PNG files instead JPEG.<br/>Quality increase is not noticeable on most of devices.<br/>Output files <span style=\" font-weight:600;\">might</span> be smaller.<br/><span style=\" font-weight:600;\">MOBI conversion will be much slower.</span></p></body></html>"))
|
||||
self.NoDitheringBox.setText(_translate("KCC", "PNG output"))
|
||||
@@ -281,7 +281,7 @@ class Ui_KCC(object):
|
||||
self.ClearButton.setText(_translate("KCC", "Clear list"))
|
||||
self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
|
||||
self.MangaBox.setText(_translate("KCC", "Manga mode"))
|
||||
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode<br/></span><span style=\" font-style:italic;\">Use it when Panel View support is not needed.</span><span style=\" font-weight:600; text-decoration: underline;\"><br/></span>- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode<br/></span><span style=\" font-style:italic;\">Not zoomed images </span><span style=\" font-weight:600; font-style:italic;\">might </span><span style=\" font-style:italic;\">be blurry.</span><span style=\" font-weight:600; text-decoration: underline;\"><br/></span>- High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode<br/></span><span style=\" font-style:italic;\">Maximum possible quality.</span><span style=\" font-weight:600; text-decoration: underline;\"><br/></span>- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</p></body></html>"))
|
||||
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\"white-space:pre\"><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode<br/></span>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p style=\"white-space:pre\"><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode<br/></span>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.</p><p style=\"white-space:pre\"><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode<br/></span>Highest possible quality. Output files will be big.</p></body></html>"))
|
||||
self.QualityBox.setText(_translate("KCC", "High/Ultra quality"))
|
||||
self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>"))
|
||||
self.RotateBox.setText(_translate("KCC", "Horizontal mode"))
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'KCC-Linux.ui'
|
||||
#
|
||||
# Created: Sat Jan 25 17:37:02 2014
|
||||
# by: PyQt5 UI code generator 5.2
|
||||
# Created: Sun May 18 09:08:37 2014
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -333,7 +333,7 @@ class Ui_KCC(object):
|
||||
self.ProcessingBox.setText(_translate("KCC", "No optimisation"))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>"))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale"))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>"))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>This mode was created for pages with a low width, high height and vertical panel flow.</p></body></html>"))
|
||||
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode"))
|
||||
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Create PNG files instead JPEG.<br/>Quality increase is not noticeable on most of devices.<br/>Output files <span style=\" font-weight:600;\">might</span> be smaller.<br/><span style=\" font-weight:600;\">MOBI conversion will be much slower.</span></p></body></html>"))
|
||||
self.NoDitheringBox.setText(_translate("KCC", "PNG output"))
|
||||
@@ -350,7 +350,7 @@ class Ui_KCC(object):
|
||||
self.ClearButton.setText(_translate("KCC", "Clear list"))
|
||||
self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
|
||||
self.MangaBox.setText(_translate("KCC", "Manga mode"))
|
||||
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode<br/></span><span style=\" font-style:italic;\">Use it when Panel View support is not needed.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode<br/></span><span style=\" font-style:italic;\">Not zoomed images </span><span style=\" font-weight:600; font-style:italic;\">might</span><span style=\" font-style:italic;\"> be blurry.<br/></span>- High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode<br/></span><span style=\" font-style:italic;\">Maximum possible quality.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</p></body></html>"))
|
||||
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode</span><br/>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode</span><br/>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.<br/><br/><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode</span><br/>Highest possible quality. Output files will be big.</p></body></html>"))
|
||||
self.QualityBox.setText(_translate("KCC", "High/Ultra quality"))
|
||||
self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>"))
|
||||
self.RotateBox.setText(_translate("KCC", "Horizontal mode"))
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'KCC-OSX.ui'
|
||||
#
|
||||
# Created: Sat Jan 25 17:37:10 2014
|
||||
# by: PyQt5 UI code generator 5.2
|
||||
# Created: Sun May 18 09:08:44 2014
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -356,7 +356,7 @@ class Ui_KCC(object):
|
||||
self.ProcessingBox.setText(_translate("KCC", "No optimisation"))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>"))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale"))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>"))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God </span>or <span style=\" font-style:italic;\">Noblesse</span>.<br/>This mode was created for pages with a low width, high height and vertical panel flow.</p><p><br/></p></body></html>"))
|
||||
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode"))
|
||||
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Create PNG files instead JPEG.<br/>Quality increase is not noticeable on most of devices.<br/>Output files <span style=\" font-weight:600;\">might</span> be smaller.<br/><span style=\" font-weight:600;\">MOBI conversion will be much slower.</span></p></body></html>"))
|
||||
self.NoDitheringBox.setText(_translate("KCC", "PNG output"))
|
||||
@@ -373,7 +373,7 @@ class Ui_KCC(object):
|
||||
self.ClearButton.setText(_translate("KCC", "Clear list"))
|
||||
self.MangaBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
|
||||
self.MangaBox.setText(_translate("KCC", "Manga mode"))
|
||||
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode<br/></span><span style=\" font-style:italic;\">Use it when Panel View support is not needed.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode<br/></span><span style=\" font-style:italic;\">Not zoomed image </span><span style=\" font-weight:600; font-style:italic;\">might</span><span style=\" font-style:italic;\"> be a little blurry.<br/></span>- High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode<br/></span><span style=\" font-style:italic;\">Maximum possible quality.<br/></span>- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</p></body></html>"))
|
||||
self.QualityBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Normal quality mode</span><br/>Maximal quality of images but very poor magnification quality.<br/>Use it only when zoom is not needed or output files needs to be small.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - High quality mode</span><br/>In most cases high quality of images and magnification.<br/>Overall quality highly depends on the resolution of source files.<br/>On Kindle models older than Paperwhite non-zoomed images might be a little blurred.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Ultra quality mode</span><br/>Highest possible quality. Output files will be big.</p></body></html>"))
|
||||
self.QualityBox.setText(_translate("KCC", "High/Ultra quality"))
|
||||
self.RotateBox.setToolTip(_translate("KCC", "<html><head/><body><p style=\'white-space:pre\'>Disable splitting of two-page spreads.<br/>They will be rotated instead.</p></body></html>"))
|
||||
self.RotateBox.setText(_translate("KCC", "Horizontal mode"))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '4.0.1'
|
||||
__version__ = '4.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys
|
||||
@@ -104,7 +104,7 @@ class CBxArchive:
|
||||
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)):
|
||||
os.rename(os.path.join(targetdir, adir[0], f), os.path.join(targetdir, adir[0], f + '-A'))
|
||||
os.replace(os.path.join(targetdir, adir[0], f), os.path.join(targetdir, adir[0], f + '-A'))
|
||||
f += '-A'
|
||||
move(os.path.join(targetdir, adir[0], f), targetdir)
|
||||
os.rmdir(os.path.join(targetdir, adir[0]))
|
||||
|
||||
1186
kcc/comic2ebook.py
1186
kcc/comic2ebook.py
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -18,9 +18,9 @@
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
|
||||
__version__ = '4.0.1'
|
||||
__version__ = '4.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
@@ -36,7 +36,7 @@ except ImportError:
|
||||
QtCore = None
|
||||
|
||||
|
||||
def mergeDirectory_tick(output):
|
||||
def mergeDirectoryTick(output):
|
||||
if output:
|
||||
mergeWorkerOutput.append(output)
|
||||
mergeWorkerPool.terminate()
|
||||
@@ -108,7 +108,7 @@ def sanitizePanelSize(panel, opt):
|
||||
return newPanels
|
||||
|
||||
|
||||
def splitImage_tick(output):
|
||||
def splitImageTick(output):
|
||||
if output:
|
||||
splitWorkerOutput.append(output)
|
||||
splitWorkerPool.terminate()
|
||||
@@ -118,13 +118,12 @@ def splitImage_tick(output):
|
||||
splitWorkerPool.terminate()
|
||||
|
||||
|
||||
#noinspection PyUnboundLocalVariable
|
||||
def splitImage(work):
|
||||
try:
|
||||
path = work[0]
|
||||
name = work[1]
|
||||
opt = work[2]
|
||||
# Harcoded opttions
|
||||
# Hardcoded options
|
||||
threshold = 1.0
|
||||
delta = 15
|
||||
fileExpanded = os.path.splitext(name)
|
||||
@@ -165,6 +164,7 @@ def splitImage(work):
|
||||
for panel in panelsCleaned:
|
||||
panels.append(panel)
|
||||
if opt.debug:
|
||||
# noinspection PyUnboundLocalVariable
|
||||
debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG')
|
||||
|
||||
# Create virtual pages
|
||||
@@ -207,13 +207,9 @@ def splitImage(work):
|
||||
return str(sys.exc_info()[1])
|
||||
|
||||
|
||||
def Copyright():
|
||||
print(('comic2panel v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals()))
|
||||
|
||||
|
||||
def main(argv=None, qtGUI=None):
|
||||
global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
|
||||
parser = OptionParser(usage="Usage: %prog [options] comic_folder", add_help_option=False)
|
||||
parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False)
|
||||
mainOptions = OptionGroup(parser, "MANDATORY")
|
||||
otherOptions = OptionGroup(parser, "OTHER")
|
||||
mainOptions.add_option("-y", "--height", type="int", dest="height", default=0,
|
||||
@@ -261,7 +257,7 @@ def main(argv=None, qtGUI=None):
|
||||
GUI.progressBarTick.emit('Combining images')
|
||||
GUI.progressBarTick.emit(str(directoryNumer))
|
||||
for i in mergeWork:
|
||||
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectory_tick)
|
||||
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
|
||||
mergeWorkerPool.close()
|
||||
mergeWorkerPool.join()
|
||||
if GUI and not GUI.conversionAlive:
|
||||
@@ -284,7 +280,7 @@ def main(argv=None, qtGUI=None):
|
||||
GUI.progressBarTick.emit('tick')
|
||||
if len(work) > 0:
|
||||
for i in work:
|
||||
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImage_tick)
|
||||
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
|
||||
splitWorkerPool.close()
|
||||
splitWorkerPool.join()
|
||||
if GUI and not GUI.conversionAlive:
|
||||
|
||||
184
kcc/dualmetafix.py
Normal file
184
kcc/dualmetafix.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks
|
||||
# Changes for KCC Copyright (C) 2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import struct
|
||||
import mmap
|
||||
import shutil
|
||||
|
||||
|
||||
class DualMetaFixException(Exception):
|
||||
pass
|
||||
|
||||
# palm database offset constants
|
||||
number_of_pdb_records = 76
|
||||
first_pdb_record = 78
|
||||
|
||||
# important rec0 offsets
|
||||
mobi_header_base = 16
|
||||
mobi_header_length = 20
|
||||
mobi_version = 36
|
||||
title_offset = 84
|
||||
|
||||
|
||||
def getint(data, ofs, sz='L'):
|
||||
i, = struct.unpack_from('>'+sz, data, ofs)
|
||||
return i
|
||||
|
||||
|
||||
def writeint(data, ofs, n, slen='L'):
|
||||
if slen == 'L':
|
||||
return data[:ofs]+struct.pack('>L', n)+data[ofs+4:]
|
||||
else:
|
||||
return data[:ofs]+struct.pack('>H', n)+data[ofs+2:]
|
||||
|
||||
|
||||
def getsecaddr(datain, secno):
|
||||
nsec = getint(datain, number_of_pdb_records, 'H')
|
||||
if (secno < 0) | (secno >= nsec):
|
||||
emsg = 'requested section number %d out of range (nsec=%d)' % (secno, nsec)
|
||||
raise DualMetaFixException(emsg)
|
||||
secstart = getint(datain, first_pdb_record+secno*8)
|
||||
if secno == nsec-1:
|
||||
secend = len(datain)
|
||||
else:
|
||||
secend = getint(datain, first_pdb_record+(secno+1)*8)
|
||||
return secstart, secend
|
||||
|
||||
|
||||
def readsection(datain, secno):
|
||||
secstart, secend = getsecaddr(datain, secno)
|
||||
return datain[secstart:secend]
|
||||
|
||||
|
||||
# overwrite section - must be exact same length as original
|
||||
def replacesection(datain, secno, secdata):
|
||||
secstart, secend = getsecaddr(datain, secno)
|
||||
seclen = secend - secstart
|
||||
if len(secdata) != seclen:
|
||||
raise DualMetaFixException('section length change in replacesection')
|
||||
datain[secstart:secstart+seclen] = secdata
|
||||
|
||||
|
||||
def get_exth_params(rec0):
|
||||
ebase = mobi_header_base + getint(rec0, mobi_header_length)
|
||||
if rec0[ebase:ebase+4] != b'EXTH':
|
||||
raise DualMetaFixException('EXTH tag not found where expected')
|
||||
elen = getint(rec0, ebase+4)
|
||||
enum = getint(rec0, ebase+8)
|
||||
rlen = len(rec0)
|
||||
return ebase, elen, enum, rlen
|
||||
|
||||
|
||||
def add_exth(rec0, exth_num, exth_bytes):
|
||||
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||
newrecsize = 8+len(exth_bytes)
|
||||
newrec0 = rec0[0:ebase+4]+struct.pack('>L', elen+newrecsize)+struct.pack('>L', enum+1)+struct.pack('>L', exth_num)\
|
||||
+ struct.pack('>L', newrecsize)+exth_bytes+rec0[ebase+12:]
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)+newrecsize)
|
||||
# keep constant record length by removing newrecsize null bytes from end
|
||||
sectail = newrec0[-newrecsize:]
|
||||
if sectail != b'\0'*newrecsize:
|
||||
raise DualMetaFixException('add_exth: trimmed non-null bytes at end of section')
|
||||
newrec0 = newrec0[0:rlen]
|
||||
return newrec0
|
||||
|
||||
|
||||
def read_exth(rec0, exth_num):
|
||||
exth_values = []
|
||||
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||
ebase += 12
|
||||
while enum > 0:
|
||||
exth_id = getint(rec0, ebase)
|
||||
if exth_id == exth_num:
|
||||
# We might have multiple exths, so build a list.
|
||||
exth_values.append(rec0[ebase+8:ebase+getint(rec0, ebase+4)])
|
||||
enum -= 1
|
||||
ebase = ebase+getint(rec0, ebase+4)
|
||||
return exth_values
|
||||
|
||||
|
||||
def del_exth(rec0, exth_num):
|
||||
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||
ebase_idx = ebase+12
|
||||
enum_idx = 0
|
||||
while enum_idx < enum:
|
||||
exth_id = getint(rec0, ebase_idx)
|
||||
exth_size = getint(rec0, ebase_idx+4)
|
||||
if exth_id == exth_num:
|
||||
newrec0 = rec0
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)-exth_size)
|
||||
newrec0 = newrec0[:ebase_idx]+newrec0[ebase_idx+exth_size:]
|
||||
newrec0 = newrec0[0:ebase+4]+struct.pack('>L', elen-exth_size)+struct.pack('>L', enum-1)+newrec0[ebase+12:]
|
||||
newrec0 += b'\0'*exth_size
|
||||
if rlen != len(newrec0):
|
||||
raise DualMetaFixException('del_exth: incorrect section size change')
|
||||
return newrec0
|
||||
enum_idx += 1
|
||||
ebase_idx = ebase_idx+exth_size
|
||||
return rec0
|
||||
|
||||
|
||||
class DualMobiMetaFix:
|
||||
def __init__(self, infile, outfile, asin):
|
||||
shutil.copyfile(infile, outfile)
|
||||
f = open(outfile, "r+b")
|
||||
self.datain = mmap.mmap(f.fileno(), 0)
|
||||
self.datain_rec0 = readsection(self.datain, 0)
|
||||
|
||||
# in the first mobi header
|
||||
# add 501 to "EBOK", add 113 as asin, add 504 as asin
|
||||
rec0 = self.datain_rec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = del_exth(rec0, 504)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
rec0 = add_exth(rec0, 504, asin)
|
||||
replacesection(self.datain, 0, rec0)
|
||||
|
||||
ver = getint(self.datain_rec0, mobi_version)
|
||||
self.combo = (ver != 8)
|
||||
if not self.combo:
|
||||
return
|
||||
|
||||
exth121 = read_exth(self.datain_rec0, 121)
|
||||
if len(exth121) == 0:
|
||||
self.combo = False
|
||||
return
|
||||
else:
|
||||
# only pay attention to first exth121
|
||||
# (there should only be one)
|
||||
datain_kf8, = struct.unpack_from('>L', exth121[0], 0)
|
||||
if datain_kf8 == 0xffffffff:
|
||||
self.combo = False
|
||||
return
|
||||
self.datain_kfrec0 = readsection(self.datain, datain_kf8)
|
||||
|
||||
# in the second header
|
||||
# add 501 to "EBOK", add 113 as asin, add 504 as asin
|
||||
rec0 = self.datain_kfrec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = del_exth(rec0, 504)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
rec0 = add_exth(rec0, 504, asin)
|
||||
replacesection(self.datain, datain_kf8, rec0)
|
||||
|
||||
self.datain.flush()
|
||||
self.datain.close()
|
||||
164
kcc/image.py
164
kcc/image.py
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2010 Alex Yatskov
|
||||
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -16,11 +16,15 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
__version__ = '4.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import quote
|
||||
from functools import reduce
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops
|
||||
from .shared import md5Checksum
|
||||
@@ -81,18 +85,18 @@ class ProfileData:
|
||||
'K1': ("Kindle 1", (600, 670), Palette4, 1.8, (900, 1005)),
|
||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)),
|
||||
'K345': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'KHD': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)),
|
||||
'KF': ("Kindle Fire", (600, 1024), PalleteNull, 1.0, (900, 1536)),
|
||||
'KFHD': ("K. Fire HD 7\"", (800, 1280), PalleteNull, 1.0, (1200, 1920)),
|
||||
'KFHD8': ("K. Fire HD 8.9\"", (1200, 1920), PalleteNull, 1.0, (1800, 2880)),
|
||||
'KFHDX': ("K. Fire HDX 7\"", (1200, 1920), PalleteNull, 1.0, (1800, 2880)),
|
||||
'KFHDX8': ("K. Fire HDX 8.9\"", (1600, 2560), PalleteNull, 1.0, (2400, 3840)),
|
||||
'KPW': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.8, (1608, 2172)),
|
||||
'KFHD': ("K. Fire HD", (800, 1280), PalleteNull, 1.0, (1200, 1920)),
|
||||
'KFHDX': ("K. Fire HDX", (1200, 1920), PalleteNull, 1.0, (1800, 2880)),
|
||||
'KFHDX8': ("K. Fire HDX 8.9", (1600, 2560), PalleteNull, 1.0, (2400, 3840)),
|
||||
'KFA': ("Kindle for Android", (0, 0), PalleteNull, 1.0, (0, 0)),
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'KoG': ("Kobo Glow", (768, 1024), Palette16, 1.8, (1152, 1536)),
|
||||
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)),
|
||||
'KFA': ("Kindle for Android", (0, 0), PalleteNull, 1.0, (0, 0)),
|
||||
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8, (1620, 2145)),
|
||||
'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)),
|
||||
}
|
||||
|
||||
@@ -144,7 +148,7 @@ class ComicPage:
|
||||
+ str(self.border[2]) + "-" + str(self.border[3]))
|
||||
if forcepng:
|
||||
filename += ".png"
|
||||
self.image.save(filename, "PNG", optimize=1)
|
||||
self.image.save(filename, "PNG", optimize=1)
|
||||
else:
|
||||
filename += ".jpg"
|
||||
self.image.save(filename, "JPEG", optimize=1)
|
||||
@@ -223,6 +227,9 @@ class ComicPage:
|
||||
self.size[0] and self.image.size[1] <= self.size[1]:
|
||||
size = (self.size[0], self.size[1])
|
||||
elif qualityMode == 1:
|
||||
# Forcing upscale to make sure that margins will be not too big
|
||||
if not stretch:
|
||||
upscale = True
|
||||
size = (self.panelviewsize[0], self.panelviewsize[1])
|
||||
elif qualityMode == 2 and not stretch and not upscale and self.image.size[0] <=\
|
||||
self.size[0] and self.image.size[1] <= self.size[1]:
|
||||
@@ -359,27 +366,28 @@ class ComicPage:
|
||||
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
|
||||
return self.image
|
||||
|
||||
def cropWhiteSpace(self, threshold):
|
||||
def cropWhiteSpace(self):
|
||||
if ImageChops.invert(self.image).getbbox() is not None:
|
||||
widthImg, heightImg = self.image.size
|
||||
delta = 10
|
||||
diff = delta
|
||||
fixedThreshold = 0.1
|
||||
# top
|
||||
while ImageStat.Stat(self.image.crop((0, 0, widthImg, diff))).var[0] < threshold and diff < heightImg:
|
||||
while ImageStat.Stat(self.image.crop((0, 0, widthImg, diff))).var[0] < fixedThreshold and diff < heightImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
self.image = self.image.crop((0, diff, widthImg, heightImg))
|
||||
widthImg, heightImg = self.image.size
|
||||
diff = delta
|
||||
# left
|
||||
while ImageStat.Stat(self.image.crop((0, 0, diff, heightImg))).var[0] < threshold and diff < widthImg:
|
||||
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] < threshold\
|
||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\
|
||||
and diff < heightImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
@@ -387,7 +395,7 @@ class ComicPage:
|
||||
widthImg, heightImg = self.image.size
|
||||
diff = delta
|
||||
# right
|
||||
while ImageStat.Stat(self.image.crop((widthImg - diff, 0, widthImg, heightImg))).var[0] < threshold\
|
||||
while ImageStat.Stat(self.image.crop((widthImg - diff, 0, widthImg, heightImg))).var[0] < fixedThreshold\
|
||||
and diff < widthImg:
|
||||
diff += delta
|
||||
diff -= delta
|
||||
@@ -396,50 +404,44 @@ class ComicPage:
|
||||
|
||||
def getImageHistogram(self, image):
|
||||
histogram = image.histogram()
|
||||
RBGW = []
|
||||
pixelCount = 0
|
||||
for i in range(256):
|
||||
pixelCount += histogram[i] + histogram[256 + i] + histogram[512 + i]
|
||||
RBGW.append(histogram[i] + histogram[256 + i] + histogram[512 + i])
|
||||
white = 0
|
||||
black = 0
|
||||
for i in range(251, 256):
|
||||
white += RBGW[i]
|
||||
for i in range(5):
|
||||
black += RBGW[i]
|
||||
if black > pixelCount*0.8 and white == 0:
|
||||
return 1
|
||||
elif white > pixelCount*0.8 and black == 0:
|
||||
if histogram[0] == 0:
|
||||
return -1
|
||||
elif histogram[255] == 0:
|
||||
return 1
|
||||
else:
|
||||
return False
|
||||
return 0
|
||||
|
||||
def getImageFill(self, webtoon):
|
||||
fill = 0
|
||||
if not webtoon and not self.rotated:
|
||||
# Search for horizontal solid lines
|
||||
startY = 0
|
||||
while startY < self.image.size[1]:
|
||||
if startY + 5 > self.image.size[1]:
|
||||
startY = self.image.size[1] - 5
|
||||
checkSolid = self.getImageHistogram(self.image.crop((0, startY, self.image.size[0], startY+5)))
|
||||
if checkSolid:
|
||||
fill += checkSolid
|
||||
startY += 5
|
||||
def getImageFill(self):
|
||||
bw = self.image.convert('L').point(lambda x: 0 if x < 128 else 255, '1')
|
||||
imageBoxA = bw.getbbox()
|
||||
imageBoxB = ImageChops.invert(bw).getbbox()
|
||||
if imageBoxA is None or imageBoxB is None:
|
||||
surfaceB, surfaceW = 0, 0
|
||||
else:
|
||||
# Search for vertical solid lines
|
||||
startX = 0
|
||||
while startX < self.image.size[0]:
|
||||
if startX + 5 > self.image.size[0]:
|
||||
startX = self.image.size[0] - 5
|
||||
checkSolid = self.getImageHistogram(self.image.crop((startX, 0, startX+5, self.image.size[1])))
|
||||
if checkSolid:
|
||||
fill += checkSolid
|
||||
startX += 5
|
||||
if fill > 0:
|
||||
surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1])
|
||||
surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1])
|
||||
if surfaceW < surfaceB:
|
||||
self.fill = 'white'
|
||||
elif surfaceW > surfaceB:
|
||||
self.fill = 'black'
|
||||
else:
|
||||
self.fill = 'white'
|
||||
fill = 0
|
||||
startY = 0
|
||||
while startY < bw.size[1]:
|
||||
if startY + 5 > bw.size[1]:
|
||||
startY = bw.size[1] - 5
|
||||
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY+5)))
|
||||
startY += 5
|
||||
startX = 0
|
||||
while startX < bw.size[0]:
|
||||
if startX + 5 > bw.size[0]:
|
||||
startX = bw.size[0] - 5
|
||||
fill += self.getImageHistogram(bw.crop((startX, 0, startX+5, bw.size[1])))
|
||||
startX += 5
|
||||
if fill > 0:
|
||||
self.fill = 'black'
|
||||
else:
|
||||
self.fill = 'white'
|
||||
|
||||
def isImageColor(self):
|
||||
v = ImageStat.Stat(self.image).var
|
||||
@@ -465,3 +467,59 @@ class ComicPage:
|
||||
else:
|
||||
# Detection failed
|
||||
return False
|
||||
|
||||
|
||||
class Cover:
|
||||
def __init__(self, source, target, opt, tomeNumber):
|
||||
self.options = opt
|
||||
self.source = source
|
||||
self.target = target
|
||||
if tomeNumber == 0:
|
||||
self.tomeNumber = 1
|
||||
else:
|
||||
self.tomeNumber = tomeNumber
|
||||
if self.tomeNumber in self.options.remoteCovers:
|
||||
try:
|
||||
source = urlopen(Request(quote(self.options.remoteCovers[self.tomeNumber]).replace('%3A', ':', 1),
|
||||
headers={'User-Agent': 'KindleComicConverter/' + __version__})).read()
|
||||
self.image = Image.open(BytesIO(source))
|
||||
self.processExternal()
|
||||
except Exception:
|
||||
self.image = Image.open(source)
|
||||
self.processInternal()
|
||||
else:
|
||||
self.image = Image.open(source)
|
||||
self.processInternal()
|
||||
|
||||
def processInternal(self):
|
||||
self.image = self.image.convert('RGB')
|
||||
self.image = self.trim()
|
||||
self.save()
|
||||
|
||||
def processExternal(self):
|
||||
self.image = self.image.convert('RGB')
|
||||
self.image.thumbnail(self.options.profileData[1], Image.ANTIALIAS)
|
||||
self.save(True)
|
||||
|
||||
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, external=False):
|
||||
if external:
|
||||
source = self.options.remoteCovers[self.tomeNumber].split('/')[-1]
|
||||
else:
|
||||
source = self.source
|
||||
try:
|
||||
if os.path.splitext(source)[1].lower() == '.png':
|
||||
self.image.save(self.target, "PNG", optimize=1)
|
||||
else:
|
||||
self.image.save(self.target, "JPEG", optimize=1)
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to save cover')
|
||||
|
||||
@@ -1,382 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Based on initial version of KindleUnpack. Copyright (C) 2009 Charles M. Hannum <root@ihack.net>
|
||||
# Improvements Copyright (C) 2009-2012 P. Durrant, K. Hendricks, S. Siebert, fandrieu, DiapDealer, nickredding
|
||||
# Changes for KCC Copyright (C) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import struct
|
||||
# from uuid import uuid4
|
||||
|
||||
# important pdb header offsets
|
||||
unique_id_seed = 68
|
||||
number_of_pdb_records = 76
|
||||
|
||||
# important palmdoc header offsets
|
||||
book_length = 4
|
||||
book_record_count = 8
|
||||
first_pdb_record = 78
|
||||
|
||||
# important rec0 offsets
|
||||
length_of_book = 4
|
||||
mobi_header_base = 16
|
||||
mobi_header_length = 20
|
||||
mobi_type = 24
|
||||
mobi_version = 36
|
||||
first_non_text = 80
|
||||
title_offset = 84
|
||||
first_image_record = 108
|
||||
first_content_index = 192
|
||||
last_content_index = 194
|
||||
kf8_last_content_index = 192 # for KF8 mobi headers
|
||||
fcis_index = 200
|
||||
flis_index = 208
|
||||
srcs_index = 224
|
||||
srcs_count = 228
|
||||
primary_index = 244
|
||||
datp_index = 256
|
||||
huffoff = 112
|
||||
hufftbloff = 120
|
||||
|
||||
|
||||
def getint(datain, ofs, sz='L'):
|
||||
i, = struct.unpack_from('>'+sz, datain, ofs)
|
||||
return i
|
||||
|
||||
|
||||
def writeint(datain, ofs, n, length='L'):
|
||||
if length == 'L':
|
||||
return datain[:ofs]+struct.pack('>L', n)+datain[ofs+4:]
|
||||
else:
|
||||
return datain[:ofs]+struct.pack('>H', n)+datain[ofs+2:]
|
||||
|
||||
|
||||
def getsecaddr(datain, secno):
|
||||
nsec = getint(datain, number_of_pdb_records, 'H')
|
||||
assert secno >= 0 & secno < nsec, 'secno %d out of range (nsec=%d)' % (secno, nsec)
|
||||
secstart = getint(datain, first_pdb_record+secno*8)
|
||||
if secno == nsec-1:
|
||||
secend = len(datain)
|
||||
else:
|
||||
secend = getint(datain, first_pdb_record+(secno+1)*8)
|
||||
return secstart, secend
|
||||
|
||||
|
||||
def readsection(datain, secno):
|
||||
secstart, secend = getsecaddr(datain, secno)
|
||||
return datain[secstart:secend]
|
||||
|
||||
|
||||
def writesection(datain, secno, secdata): # overwrite, accounting for different length
|
||||
dataout = deletesectionrange(datain, secno, secno)
|
||||
return insertsection(dataout, secno, secdata)
|
||||
|
||||
|
||||
def nullsection(datain, secno): # make it zero-length without deleting it
|
||||
datalst = []
|
||||
nsec = getint(datain, number_of_pdb_records, 'H')
|
||||
secstart, secend = getsecaddr(datain, secno)
|
||||
zerosecstart, zerosecend = getsecaddr(datain, 0)
|
||||
dif = secend-secstart
|
||||
datalst.append(datain[:first_pdb_record])
|
||||
for i in range(0, secno+1):
|
||||
ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8)
|
||||
datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval))
|
||||
for i in range(secno+1, nsec):
|
||||
ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8)
|
||||
ofs -= dif
|
||||
datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval))
|
||||
lpad = zerosecstart - (first_pdb_record + 8*nsec)
|
||||
if lpad > 0:
|
||||
datalst.append(b'\0' * lpad)
|
||||
datalst.append(datain[zerosecstart: secstart])
|
||||
datalst.append(datain[secend:])
|
||||
dataout = b"".join(datalst)
|
||||
return dataout
|
||||
|
||||
|
||||
def deletesectionrange(datain, firstsec, lastsec): # delete a range of sections
|
||||
datalst = []
|
||||
firstsecstart, firstsecend = getsecaddr(datain, firstsec)
|
||||
lastsecstart, lastsecend = getsecaddr(datain, lastsec)
|
||||
zerosecstart, zerosecend = getsecaddr(datain, 0)
|
||||
dif = lastsecend - firstsecstart + 8*(lastsec-firstsec+1)
|
||||
nsec = getint(datain, number_of_pdb_records, 'H')
|
||||
datalst.append(datain[:unique_id_seed])
|
||||
datalst.append(struct.pack('>L', 2*(nsec-(lastsec-firstsec+1))+1))
|
||||
datalst.append(datain[unique_id_seed+4:number_of_pdb_records])
|
||||
datalst.append(struct.pack('>H', nsec-(lastsec-firstsec+1)))
|
||||
newstart = zerosecstart - 8*(lastsec-firstsec+1)
|
||||
for i in range(0, firstsec):
|
||||
ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8)
|
||||
ofs -= 8 * (lastsec - firstsec + 1)
|
||||
datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval))
|
||||
for i in range(lastsec+1, nsec):
|
||||
ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8)
|
||||
ofs -= dif
|
||||
flgval = 2*(i-(lastsec-firstsec+1))
|
||||
datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval))
|
||||
lpad = newstart - (first_pdb_record + 8*(nsec - (lastsec - firstsec + 1)))
|
||||
if lpad > 0:
|
||||
datalst.append(b'\0' * lpad)
|
||||
datalst.append(datain[zerosecstart:firstsecstart])
|
||||
datalst.append(datain[lastsecend:])
|
||||
dataout = b"".join(datalst)
|
||||
return dataout
|
||||
|
||||
|
||||
def insertsection(datain, secno, secdata): # insert a new section
|
||||
datalst = []
|
||||
nsec = getint(datain, number_of_pdb_records, 'H')
|
||||
secstart, secend = getsecaddr(datain, secno)
|
||||
zerosecstart, zerosecend = getsecaddr(datain, 0)
|
||||
dif = len(secdata)
|
||||
datalst.append(datain[:unique_id_seed])
|
||||
datalst.append(struct.pack('>L', 2*(nsec+1)+1))
|
||||
datalst.append(datain[unique_id_seed+4:number_of_pdb_records])
|
||||
datalst.append(struct.pack('>H', nsec+1))
|
||||
newstart = zerosecstart + 8
|
||||
for i in range(0, secno):
|
||||
ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8)
|
||||
ofs += 8
|
||||
datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval))
|
||||
datalst.append(struct.pack('>L', secstart + 8) + struct.pack('>L', (2*secno)))
|
||||
for i in range(secno, nsec):
|
||||
ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8)
|
||||
ofs = ofs + dif + 8
|
||||
flgval = 2*(i+1)
|
||||
datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval))
|
||||
lpad = newstart - (first_pdb_record + 8*(nsec + 1))
|
||||
if lpad > 0:
|
||||
datalst.append(b'\0' * lpad)
|
||||
datalst.append(datain[zerosecstart:secstart])
|
||||
datalst.append(secdata)
|
||||
datalst.append(datain[secstart:])
|
||||
dataout = b"".join(datalst)
|
||||
return dataout
|
||||
|
||||
|
||||
def insertsectionrange(sectionsource, firstsec, lastsec, sectiontarget, targetsec): # insert a range of sections
|
||||
dataout = sectiontarget
|
||||
for idx in range(lastsec, firstsec-1, -1):
|
||||
dataout = insertsection(dataout, targetsec, readsection(sectionsource, idx))
|
||||
return dataout
|
||||
|
||||
|
||||
def get_exth_params(rec0):
|
||||
ebase = mobi_header_base + getint(rec0, mobi_header_length)
|
||||
elen = getint(rec0, ebase+4)
|
||||
enum = getint(rec0, ebase+8)
|
||||
return ebase, elen, enum
|
||||
|
||||
|
||||
def add_exth(rec0, exth_num, exth_bytes):
|
||||
ebase, elen, enum = get_exth_params(rec0)
|
||||
newrecsize = 8+len(exth_bytes)
|
||||
newrec0 = rec0[0:ebase+4]+struct.pack('>L', elen+newrecsize)+struct.pack('>L', enum+1) +\
|
||||
struct.pack('>L', exth_num) + struct.pack('>L', newrecsize)+exth_bytes+rec0[ebase+12:]
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)+newrecsize)
|
||||
return newrec0
|
||||
|
||||
|
||||
def read_exth(rec0, exth_num):
|
||||
exth_values = []
|
||||
ebase, elen, enum = get_exth_params(rec0)
|
||||
ebase += 12
|
||||
while enum > 0:
|
||||
exth_id = getint(rec0, ebase)
|
||||
if exth_id == exth_num:
|
||||
# We might have multiple exths, so build a list.
|
||||
exth_values.append(rec0[ebase+8:ebase+getint(rec0, ebase+4)])
|
||||
enum -= 1
|
||||
ebase = ebase+getint(rec0, ebase+4)
|
||||
return exth_values
|
||||
|
||||
|
||||
def write_exth(rec0, exth_num, exth_bytes):
|
||||
ebase, elen, enum = get_exth_params(rec0)
|
||||
ebase_idx = ebase+12
|
||||
enum_idx = enum
|
||||
while enum_idx > 0:
|
||||
exth_id = getint(rec0, ebase_idx)
|
||||
if exth_id == exth_num:
|
||||
dif = len(exth_bytes)+8-getint(rec0, ebase_idx+4)
|
||||
newrec0 = rec0
|
||||
if dif != 0:
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)+dif)
|
||||
return newrec0[:ebase+4]+struct.pack('>L', elen+len(exth_bytes)+8-getint(rec0, ebase_idx+4)) +\
|
||||
struct.pack('>L', enum)+rec0[ebase+12:ebase_idx+4] +\
|
||||
struct.pack('>L', len(exth_bytes)+8)+exth_bytes +\
|
||||
rec0[ebase_idx+getint(rec0, ebase_idx+4):]
|
||||
enum_idx -= 1
|
||||
ebase_idx = ebase_idx+getint(rec0, ebase_idx+4)
|
||||
return rec0
|
||||
|
||||
|
||||
def del_exth(rec0, exth_num):
|
||||
ebase, elen, enum = get_exth_params(rec0)
|
||||
ebase_idx = ebase+12
|
||||
enum_idx = 0
|
||||
while enum_idx < enum:
|
||||
exth_id = getint(rec0, ebase_idx)
|
||||
exth_size = getint(rec0, ebase_idx+4)
|
||||
if exth_id == exth_num:
|
||||
newrec0 = rec0
|
||||
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)-exth_size)
|
||||
newrec0 = newrec0[:ebase_idx]+newrec0[ebase_idx+exth_size:]
|
||||
newrec0 = newrec0[0:ebase+4]+struct.pack('>L', elen-exth_size)+struct.pack('>L', enum-1)+newrec0[ebase+12:]
|
||||
return newrec0
|
||||
enum_idx += 1
|
||||
ebase_idx = ebase_idx+exth_size
|
||||
return rec0
|
||||
|
||||
|
||||
class mobi_split:
|
||||
def __init__(self, infile, newKindle):
|
||||
try:
|
||||
datain = open(infile, 'rb').read()
|
||||
datain_rec0 = readsection(datain, 0)
|
||||
ver = getint(datain_rec0, mobi_version)
|
||||
# fake_asin = str(uuid4())
|
||||
self.combo = (ver != 8)
|
||||
if not self.combo:
|
||||
return
|
||||
exth121 = read_exth(datain_rec0, 121)
|
||||
if len(exth121) == 0:
|
||||
self.combo = False
|
||||
return
|
||||
else:
|
||||
# only pay attention to first exth121
|
||||
# (there should only be one)
|
||||
datain_kf8, = struct.unpack_from('>L', exth121[0], 0)
|
||||
if datain_kf8 == 0xffffffff:
|
||||
self.combo = False
|
||||
return
|
||||
datain_kfrec0 = readsection(datain, datain_kf8)
|
||||
firstimage = getint(datain_rec0, first_image_record)
|
||||
lastimage = getint(datain_rec0, last_content_index, 'H')
|
||||
|
||||
if not newKindle:
|
||||
# create the standalone mobi7
|
||||
num_sec = getint(datain, number_of_pdb_records, 'H')
|
||||
# remove BOUNDARY up to but not including ELF record
|
||||
self.result_file = deletesectionrange(datain, datain_kf8-1, num_sec-2)
|
||||
# check if there are SRCS records and delete them
|
||||
srcs = getint(datain_rec0, srcs_index)
|
||||
num_srcs = getint(datain_rec0, srcs_count)
|
||||
if srcs != 0xffffffff and num_srcs > 0:
|
||||
self.result_file = deletesectionrange(self.result_file, srcs, srcs+num_srcs-1)
|
||||
datain_rec0 = writeint(datain_rec0, srcs_index, 0xffffffff)
|
||||
datain_rec0 = writeint(datain_rec0, srcs_count, 0)
|
||||
# reset the EXTH 121 KF8 Boundary meta data to 0xffffffff
|
||||
datain_rec0 = write_exth(datain_rec0, 121, struct.pack('>L', 0xffffffff))
|
||||
# datain_rec0 = del_exth(datain_rec0,121)
|
||||
# datain_rec0 = del_exth(datain_rec0,534)
|
||||
# don't remove the EXTH 125 KF8 Count of Resources, seems to be present in mobi6 files as well
|
||||
# set the EXTH 129 KF8 Masthead / Cover Image string to the null string
|
||||
datain_rec0 = write_exth(datain_rec0, 129, b'')
|
||||
# don't remove the EXTH 131 KF8 Unidentified Count, seems to be present in mobi6 files as well
|
||||
|
||||
# Make sure we have an ASIN & cdeType set...
|
||||
# if len(read_exth(datain_rec0, 113)) == 0:
|
||||
# datain_rec0 = add_exth(datain_rec0, 113, fake_asin)
|
||||
# if len(read_exth(datain_rec0, 504)) == 0:
|
||||
# datain_rec0 = add_exth(datain_rec0, 504, fake_asin)
|
||||
if len(read_exth(datain_rec0, 501)) == 0:
|
||||
datain_rec0 = add_exth(datain_rec0, 501, b'EBOK')
|
||||
|
||||
# need to reset flags stored in 0x80-0x83
|
||||
# old mobi with exth: 0x50, mobi7 part with exth: 0x1850, mobi8 part with exth: 0x1050
|
||||
# Bit Flags
|
||||
# 0x1000 = Bit 12 indicates if embedded fonts are used or not
|
||||
# 0x0800 = means this Header points to *shared* images/resource/fonts ??
|
||||
# 0x0080 = unknown new flag, why is this now being set by Kindlegen 2.8?
|
||||
# 0x0040 = exth exists
|
||||
# 0x0010 = Not sure but this is always set so far
|
||||
fval, = struct.unpack_from('>L', datain_rec0, 0x80)
|
||||
# need to remove flag 0x0800 for KindlePreviewer 2.8 and unset Bit 12 for embedded fonts
|
||||
fval &= 0x07FF
|
||||
datain_rec0 = datain_rec0[:0x80] + struct.pack('>L', fval) + datain_rec0[0x84:]
|
||||
self.result_file = writesection(self.result_file, 0, datain_rec0)
|
||||
if lastimage == 0xffff:
|
||||
# find the lowest of the next sections and copy up to that.
|
||||
ofs_list = [(kf8_last_content_index, 'L'), (fcis_index, 'L'), (flis_index, 'L'), (datp_index, 'L'),
|
||||
(hufftbloff, 'L')]
|
||||
for ofs, sz in ofs_list:
|
||||
n = getint(datain_kfrec0, ofs, sz)
|
||||
if 0 < n < lastimage:
|
||||
lastimage = n-1
|
||||
|
||||
# Try to null out FONT and RES, but leave the (empty) PDB record so image refs remain valid
|
||||
for i in range(firstimage, lastimage):
|
||||
imgsec = readsection(self.result_file, i)
|
||||
if imgsec[0:4] in ['RESC', 'FONT']:
|
||||
self.result_file = nullsection(self.result_file, i)
|
||||
# mobi7 finished
|
||||
else:
|
||||
# create standalone mobi8
|
||||
self.result_file = deletesectionrange(datain, 0, datain_kf8-1)
|
||||
target = getint(datain_kfrec0, first_image_record)
|
||||
self.result_file = insertsectionrange(datain, firstimage, lastimage, self.result_file, target)
|
||||
datain_kfrec0 = readsection(self.result_file, 0)
|
||||
|
||||
# Only keep the correct EXTH 116 StartOffset, KG 2.5 carries over the one from the mobi7 part,
|
||||
# which then points at garbage in the mobi8 part, and confuses FW 3.4
|
||||
kf8starts = read_exth(datain_kfrec0, 116)
|
||||
# If we have multiple StartOffset, keep only the last one
|
||||
kf8start_count = len(kf8starts)
|
||||
while kf8start_count > 1:
|
||||
kf8start_count -= 1
|
||||
datain_kfrec0 = del_exth(datain_kfrec0, 116)
|
||||
|
||||
# update the EXTH 125 KF8 Count of Images/Fonts/Resources
|
||||
datain_kfrec0 = write_exth(datain_kfrec0, 125, struct.pack('>L', lastimage-firstimage+1))
|
||||
|
||||
# Same dance for the KF8, we want an ASIN & cdeType :)
|
||||
# if len(read_exth(datain_kfrec0, 113)) == 0:
|
||||
# datain_kfrec0 = add_exth(datain_kfrec0, 113, fake_asin)
|
||||
# if len(read_exth(datain_kfrec0, 504)) == 0:
|
||||
# datain_kfrec0 = add_exth(datain_kfrec0, 504, fake_asin)
|
||||
if len(read_exth(datain_kfrec0, 501)) == 0:
|
||||
datain_kfrec0 = add_exth(datain_kfrec0, 501, b'EBOK')
|
||||
|
||||
# need to reset flags stored in 0x80-0x83
|
||||
# old mobi with exth: 0x50, mobi7 part with exth: 0x1850, mobi8 part with exth: 0x1050
|
||||
# standalone mobi8 with exth: 0x0050
|
||||
# Bit Flags
|
||||
# 0x1000 = Bit 12 indicates if embedded fonts are used or not
|
||||
# 0x0800 = means this Header points to *shared* images/resource/fonts ??
|
||||
# 0x0080 = unknown new flag, why is this now being set by Kindlegen 2.8?
|
||||
# 0x0040 = exth exists
|
||||
# 0x0010 = Not sure but this is always set so far
|
||||
fval, = struct.unpack_from('>L', datain_kfrec0, 0x80)
|
||||
fval &= 0x1FFF
|
||||
fval |= 0x0800
|
||||
datain_kfrec0 = datain_kfrec0[:0x80] + struct.pack('>L', fval) + datain_kfrec0[0x84:]
|
||||
|
||||
# properly update other index pointers that have been shifted by the insertion of images
|
||||
ofs_list = [(kf8_last_content_index, 'L'), (fcis_index, 'L'), (flis_index, 'L'), (datp_index, 'L'),
|
||||
(hufftbloff, 'L')]
|
||||
for ofs, sz in ofs_list:
|
||||
n = getint(datain_kfrec0, ofs, sz)
|
||||
if n != 0xffffffff:
|
||||
datain_kfrec0 = writeint(datain_kfrec0, ofs, n+lastimage-firstimage+1, sz)
|
||||
self.result_file = writesection(self.result_file, 0, datain_kfrec0)
|
||||
# mobi8 finished
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def getResult(self):
|
||||
return self.result_file
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Based upon the code snippet by Ned Batchelder
|
||||
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
|
||||
@@ -20,7 +20,7 @@
|
||||
#
|
||||
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
@@ -178,6 +178,23 @@ EXTRACT_ARGS = ('x', '-y', '-idq')
|
||||
#: args for testrar()
|
||||
TEST_ARGS = ('t', '-idq')
|
||||
|
||||
#
|
||||
# Allow use of tool that is not compatible with unrar.
|
||||
#
|
||||
# By default use 'bsdtar' which is 'tar' program that
|
||||
# sits on top of libarchive.
|
||||
#
|
||||
# Problems with libarchive RAR backend:
|
||||
# - Does not support solid archives.
|
||||
# - Does not support password-protected archives.
|
||||
#
|
||||
|
||||
ALT_TOOL = 'bsdtar'
|
||||
ALT_OPEN_ARGS = ('-x', '--to-stdout', '-f')
|
||||
ALT_EXTRACT_ARGS = ('-x', '-f')
|
||||
ALT_TEST_ARGS = ('-t', '-f')
|
||||
ALT_CHECK_ARGS = ('--help',)
|
||||
|
||||
#: whether to speed up decompression by using tmp archive
|
||||
USE_EXTRACT_HACK = 1
|
||||
|
||||
@@ -336,6 +353,8 @@ class RarUnknownError(RarExecError):
|
||||
"""Unknown exit code"""
|
||||
class RarSignalExit(RarExecError):
|
||||
"""Unrar exited with signal"""
|
||||
class RarCannotExec(RarExecError):
|
||||
"""Executable not found."""
|
||||
|
||||
|
||||
def is_rarfile(xfile):
|
||||
@@ -693,10 +712,7 @@ class RarFile(object):
|
||||
"""Let 'unrar' test the archive.
|
||||
"""
|
||||
cmd = [UNRAR_TOOL] + list(TEST_ARGS)
|
||||
if self._password is not None:
|
||||
cmd.append('-p' + self._password)
|
||||
else:
|
||||
cmd.append('-p-')
|
||||
add_password_arg(cmd, self._password)
|
||||
cmd.append(self.rarfile)
|
||||
p = custom_popen(cmd)
|
||||
output = p.communicate()[0]
|
||||
@@ -1172,8 +1188,7 @@ class RarFile(object):
|
||||
if is_filelike(rarfile):
|
||||
raise ValueError("Cannot use unrar directly on memory buffer")
|
||||
cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
|
||||
if psw is not None:
|
||||
cmd.append("-p" + psw)
|
||||
add_password_arg(cmd, psw)
|
||||
cmd.append(rarfile)
|
||||
|
||||
# not giving filename avoids encoding related problems
|
||||
@@ -1205,10 +1220,7 @@ class RarFile(object):
|
||||
|
||||
# pasoword
|
||||
psw = psw or self._password
|
||||
if psw is not None:
|
||||
cmd.append('-p' + psw)
|
||||
else:
|
||||
cmd.append('-p-')
|
||||
add_password_arg(cmd, psw)
|
||||
|
||||
# rar file
|
||||
cmd.append(self.rarfile)
|
||||
@@ -1830,10 +1842,7 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No
|
||||
tmpf.close()
|
||||
|
||||
cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
|
||||
if psw is not None and (flags & RAR_FILE_PASSWORD):
|
||||
cmd.append("-p" + psw)
|
||||
else:
|
||||
cmd.append("-p-")
|
||||
add_password_arg(cmd, psw, (flags & RAR_FILE_PASSWORD))
|
||||
cmd.append(tmpname)
|
||||
|
||||
p = custom_popen(cmd)
|
||||
@@ -1902,10 +1911,27 @@ def custom_popen(cmd):
|
||||
except OSError:
|
||||
ex = sys.exc_info()[1]
|
||||
if ex.errno == errno.ENOENT:
|
||||
raise RarExecError("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
|
||||
raise RarCannotExec("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
|
||||
raise
|
||||
return p
|
||||
|
||||
def custom_check(cmd, ignore_retcode=False):
|
||||
"""Run command, collect output, raise error if needed."""
|
||||
p = custom_popen(cmd)
|
||||
out, err = p.communicate()
|
||||
if p.returncode and not ignore_retcode:
|
||||
raise RarExecError("Check-run failed")
|
||||
return out
|
||||
|
||||
def add_password_arg(cmd, psw, required=False):
|
||||
"""Append password switch to commandline."""
|
||||
if UNRAR_TOOL == ALT_TOOL:
|
||||
return
|
||||
if psw is not None:
|
||||
cmd.append('-p' + psw)
|
||||
else:
|
||||
cmd.append('-p-')
|
||||
|
||||
def check_returncode(p, out):
|
||||
"""Raise exception according to unrar exit code"""
|
||||
|
||||
@@ -1920,6 +1946,8 @@ def check_returncode(p, out):
|
||||
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
|
||||
RarWriteError, RarOpenError, RarUserError, RarMemoryError,
|
||||
RarCreateError, RarNoFilesError] # codes from rar.txt
|
||||
if UNRAR_TOOL == ALT_TOOL:
|
||||
errmap = [None]
|
||||
if code > 0 and code < len(errmap):
|
||||
exc = errmap[code]
|
||||
elif code == 255:
|
||||
@@ -1936,3 +1964,24 @@ def check_returncode(p, out):
|
||||
msg = "%s [%d]" % (exc.__doc__, p.returncode)
|
||||
|
||||
raise exc(msg)
|
||||
|
||||
#
|
||||
# Check if unrar works
|
||||
#
|
||||
|
||||
try:
|
||||
# does UNRAR_TOOL work?
|
||||
custom_check([UNRAR_TOOL], True)
|
||||
except RarCannotExec:
|
||||
try:
|
||||
# does ALT_TOOL work?
|
||||
custom_check([ALT_TOOL] + list(ALT_CHECK_ARGS), True)
|
||||
# replace config
|
||||
UNRAR_TOOL = ALT_TOOL
|
||||
OPEN_ARGS = ALT_OPEN_ARGS
|
||||
EXTRACT_ARGS = ALT_EXTRACT_ARGS
|
||||
TEST_ARGS = ALT_TEST_ARGS
|
||||
except RarCannotExec:
|
||||
# no usable tool, only uncompressed archives work
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@vulturis.eu>
|
||||
# Copyright (c) 2013-2014 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
Binary file not shown.
Binary file not shown.
69
setup.py
69
setup.py
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
cx_Freeze build script for KCC.
|
||||
py2exe/py2app build script for KCC.
|
||||
|
||||
Usage (Windows):
|
||||
python setup.py py2exe
|
||||
|
||||
Usage (Mac OS X):
|
||||
python setup.py py2app
|
||||
|
||||
Usage (Windows):
|
||||
python setup.py build
|
||||
"""
|
||||
from sys import platform, version_info
|
||||
if version_info[0] != 3:
|
||||
@@ -14,7 +14,7 @@ if version_info[0] != 3:
|
||||
exit(1)
|
||||
|
||||
NAME = "KindleComicConverter"
|
||||
VERSION = "4.0.1"
|
||||
VERSION = "4.3.1"
|
||||
MAIN = "kcc.py"
|
||||
|
||||
if platform == "darwin":
|
||||
@@ -47,7 +47,7 @@ if platform == "darwin":
|
||||
],
|
||||
LSMinimumSystemVersion='10.8.0',
|
||||
LSEnvironment=dict(
|
||||
PATH='/usr/local/bin:/usr/bin:/bin'
|
||||
PATH='./../Resources:/usr/local/bin:/usr/bin:/bin'
|
||||
),
|
||||
NSHumanReadableCopyright='ISC License (ISCL)'
|
||||
)
|
||||
@@ -55,42 +55,51 @@ if platform == "darwin":
|
||||
)
|
||||
)
|
||||
elif platform == "win32":
|
||||
# noinspection PyUnresolvedReferences
|
||||
import py2exe
|
||||
import platform as arch
|
||||
from cx_Freeze import setup, Executable
|
||||
from distutils.core import setup
|
||||
if arch.architecture()[0] == '64bit':
|
||||
library = 'libEGL64.dll'
|
||||
suffix = '_64'
|
||||
else:
|
||||
library = 'libEGL32.dll'
|
||||
base = "Win32GUI"
|
||||
suffix = ''
|
||||
additional_files = [('platforms', ['C:\Python34' + suffix +
|
||||
'\Lib\site-packages\PyQt5\plugins\platforms\qwindows.dll']),
|
||||
('', ['LICENSE.txt',
|
||||
'other\\7za.exe',
|
||||
'other\\UnRAR.exe',
|
||||
'other\\Additional-LICENSE.txt',
|
||||
'C:\Python34' + suffix + '\Lib\site-packages\PyQt5\libEGL.dll'])]
|
||||
extra_options = dict(
|
||||
options={"build_exe": {"optimize": 2,
|
||||
"include_files": ['LICENSE.txt',
|
||||
['other/UnRAR.exe', 'UnRAR.exe'],
|
||||
['other/7za.exe', '7za.exe'],
|
||||
['other/Additional-LICENSE.txt', 'Additional-LICENSE.txt'],
|
||||
['other/' + library, 'libEGL.dll']
|
||||
],
|
||||
"copy_dependent_files": True,
|
||||
"create_shared_zip": False,
|
||||
"append_script_to_exe": True,
|
||||
"replace_paths": '*=',
|
||||
"excludes": ['tkinter']}},
|
||||
executables=[Executable(MAIN,
|
||||
base=base,
|
||||
targetName="KCC.exe",
|
||||
icon="icons/comic2ebook.ico",
|
||||
compress=False)])
|
||||
options={'py2exe': {"bundle_files": 1,
|
||||
"dll_excludes": ["tcl85.dll", "tk85.dll"],
|
||||
"dist_dir": "dist" + suffix,
|
||||
"compressed": True,
|
||||
"includes": ["sip"],
|
||||
"excludes": ["tkinter"],
|
||||
"optimize": 2}},
|
||||
windows=[{"script": MAIN,
|
||||
"dest_base": "KCC",
|
||||
"version": VERSION,
|
||||
"copyright": "Ciro Mattia Gonano, Pawel Jastrzebski © 2014",
|
||||
"legal_copyright": "ISC License (ISCL)",
|
||||
"product_version": VERSION,
|
||||
"product_name": "Kindle Comic Converter",
|
||||
"file_description": "Kindle Comic Converter",
|
||||
"icon_resources": [(1, "icons\comic2ebook.ico")]}],
|
||||
zipfile=None,
|
||||
data_files=additional_files)
|
||||
else:
|
||||
print('Please use setup.sh to build Linux package.')
|
||||
exit()
|
||||
|
||||
#noinspection PyUnboundLocalVariable
|
||||
# noinspection PyUnboundLocalVariable
|
||||
setup(
|
||||
name=NAME,
|
||||
version=VERSION,
|
||||
author="Ciro Mattia Gonano, Pawel Jastrzebski",
|
||||
author_email="ciromattia@gmail.com, pawelj@vulturis.eu",
|
||||
description="A tool to convert comics (CBR/CBZ/PDFs/image folders) to MOBI.",
|
||||
author_email="ciromattia@gmail.com, pawelj@iosphe.re",
|
||||
description="Kindle Comic Converter",
|
||||
license="ISC License (ISCL)",
|
||||
keywords="kindle comic mobipocket mobi cbz cbr manga",
|
||||
url="http://github.com/ciromattia/kcc",
|
||||
|
||||
Reference in New Issue
Block a user