mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 05:28:49 +00:00
Compare commits
199 Commits
v7.2.1
...
revert-100
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89cd8390da | ||
|
|
90c9ba7539 | ||
|
|
84da718167 | ||
|
|
fe7559e6a9 | ||
|
|
a79c740387 | ||
|
|
bc98eecae9 | ||
|
|
e7a07377ef | ||
|
|
07ef11013a | ||
|
|
551fe6edbf | ||
|
|
dbf8a3ddbd | ||
|
|
8f9e230b62 | ||
|
|
36d9a4151e | ||
|
|
3e88dabd1a | ||
|
|
3b7d949128 | ||
|
|
68186285bd | ||
|
|
0abf620698 | ||
|
|
69d3bf3278 | ||
|
|
793992f408 | ||
|
|
f41d5327e0 | ||
|
|
6f960aa1d0 | ||
|
|
17c0a73f9f | ||
|
|
1fa5a5b19b | ||
|
|
e8d05c16aa | ||
|
|
74187b0d77 | ||
|
|
f39e0caad0 | ||
|
|
6299c45790 | ||
|
|
c7ebb230c2 | ||
|
|
3e4b729a30 | ||
|
|
16a1d9b45f | ||
|
|
b7aef324aa | ||
|
|
1a42730ea0 | ||
|
|
217a18b7b5 | ||
|
|
2ecbf7d2e9 | ||
|
|
a1cf9c5c7d | ||
|
|
32020d6b07 | ||
|
|
221f964f14 | ||
|
|
e9f0310b94 | ||
|
|
2fa90c9f59 | ||
|
|
cb0520dcab | ||
|
|
623bce6ae3 | ||
|
|
ad60894d19 | ||
|
|
4229b79c42 | ||
|
|
5fa6a59672 | ||
|
|
f171314a49 | ||
|
|
6b28e313e6 | ||
|
|
5a17435f7d | ||
|
|
0d573eb0a1 | ||
|
|
30a3f90907 | ||
|
|
9a7de0f5d9 | ||
|
|
f1db31205b | ||
|
|
eef5a85fa6 | ||
|
|
271200d29f | ||
|
|
ee375abfc5 | ||
|
|
94257c396a | ||
|
|
a05111b64a | ||
|
|
96e3ba7482 | ||
|
|
eb0abb538c | ||
|
|
87c2ef8033 | ||
|
|
ae7f56c81b | ||
|
|
70c73a82eb | ||
|
|
dbf4e45d25 | ||
|
|
1c6fe0cb22 | ||
|
|
2f7f6ebf0a | ||
|
|
21159c4328 | ||
|
|
4bb6ba55d3 | ||
|
|
06ae4ec25f | ||
|
|
3ac5709e73 | ||
|
|
fe7255a2d9 | ||
|
|
4712eac3c2 | ||
|
|
8ef5bf14ac | ||
|
|
c7e69f5bdb | ||
|
|
51d0be4379 | ||
|
|
ddc0ca2ff5 | ||
|
|
bd6dfa1e33 | ||
|
|
a95dde4cba | ||
|
|
2882d0f707 | ||
|
|
a1fa8e0ec3 | ||
|
|
ae4e063e09 | ||
|
|
8e0deff5ae | ||
|
|
3bd752537d | ||
|
|
4319f64815 | ||
|
|
82d2f7f4bf | ||
|
|
5a1e614a5d | ||
|
|
75e05a0ef0 | ||
|
|
e0f5bff527 | ||
|
|
a8316737be | ||
|
|
04228d100b | ||
|
|
8cc44c99f7 | ||
|
|
60f7902edd | ||
|
|
34bea98ca0 | ||
|
|
734b179e8a | ||
|
|
dcaa7401e7 | ||
|
|
d4d71cdd05 | ||
|
|
ec613cce7b | ||
|
|
ada001eb41 | ||
|
|
c4f845c221 | ||
|
|
ebb59dbc2d | ||
|
|
8da2b4cb96 | ||
|
|
7b8858678f | ||
|
|
be07e0df6a | ||
|
|
dc711e671d | ||
|
|
581ecd0ec2 | ||
|
|
f3a32c6174 | ||
|
|
0ce5f7f186 | ||
|
|
c3d2f89471 | ||
|
|
b1c4cd36f1 | ||
|
|
b7c6281b55 | ||
|
|
559485184d | ||
|
|
75f5274449 | ||
|
|
dfc149d893 | ||
|
|
327b522080 | ||
|
|
7c029b4ba1 | ||
|
|
2db8f5c8fd | ||
|
|
ce72921289 | ||
|
|
f1bbc47798 | ||
|
|
5a39007db6 | ||
|
|
0d7487f8d4 | ||
|
|
8a78f82ff2 | ||
|
|
149f7e5921 | ||
|
|
40d219de4d | ||
|
|
370d1d7392 | ||
|
|
8295f163c2 | ||
|
|
9f9a97afaa | ||
|
|
d113cae154 | ||
|
|
bc4afeb69e | ||
|
|
afb32f6287 | ||
|
|
bc516634cf | ||
|
|
36180f7904 | ||
|
|
3517994d37 | ||
|
|
08acad10ea | ||
|
|
727df0c9d6 | ||
|
|
13e71df172 | ||
|
|
3e7646bbad | ||
|
|
1c81a9d5b3 | ||
|
|
efa831341c | ||
|
|
1dd36e08eb | ||
|
|
d9d5ce2423 | ||
|
|
83c6b7b2d5 | ||
|
|
55dfdd32f8 | ||
|
|
3e3710dd76 | ||
|
|
41f87273ca | ||
|
|
b959739b53 | ||
|
|
d1b66d16fd | ||
|
|
80b01d298f | ||
|
|
de2aad0b9c | ||
|
|
bc7ab0879c | ||
|
|
2ab0135815 | ||
|
|
3882b6ce0a | ||
|
|
5e23e2cac1 | ||
|
|
d36933cb9a | ||
|
|
e40cf29aa3 | ||
|
|
9d1802453c | ||
|
|
4337d6c10d | ||
|
|
9680ff24c2 | ||
|
|
48b5b4f397 | ||
|
|
c712dc12a2 | ||
|
|
d2be9138c4 | ||
|
|
3cb40772a4 | ||
|
|
ef3b756247 | ||
|
|
77af020abb | ||
|
|
1c9c6c13b4 | ||
|
|
85ce39b2c3 | ||
|
|
6a33aee241 | ||
|
|
271a129537 | ||
|
|
23099cee81 | ||
|
|
b957fcf3fe | ||
|
|
187475a424 | ||
|
|
88fd54e2ba | ||
|
|
b23b67bbbe | ||
|
|
9992d895cf | ||
|
|
561951a349 | ||
|
|
b8b7926366 | ||
|
|
92f3308e1c | ||
|
|
9e87ccef4e | ||
|
|
9a2a09eab9 | ||
|
|
88cf2fd21f | ||
|
|
7a3ed262b1 | ||
|
|
9e204aad76 | ||
|
|
e1f9d12676 | ||
|
|
24ab72fcbc | ||
|
|
4af6a75874 | ||
|
|
28b6188a3f | ||
|
|
f000af1207 | ||
|
|
299f916580 | ||
|
|
c39e403595 | ||
|
|
e787dd2897 | ||
|
|
01625904d1 | ||
|
|
5f8526da44 | ||
|
|
1159e737a0 | ||
|
|
5bbdb715e9 | ||
|
|
1a3cd6c916 | ||
|
|
e1e6d587f4 | ||
|
|
ca5c0bdd61 | ||
|
|
c6f491d27e | ||
|
|
c9ed3feef1 | ||
|
|
be147fe7e5 | ||
|
|
62ffa2bc80 | ||
|
|
11186d07c0 | ||
|
|
4b3cd6882a |
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: eink_dude
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
1
.github/workflows/package-linux.yml
vendored
1
.github/workflows/package-linux.yml
vendored
@@ -70,6 +70,5 @@ jobs:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
LICENSE.txt
|
||||
*.AppImage*
|
||||
|
||||
1
.github/workflows/package-macos.yml
vendored
1
.github/workflows/package-macos.yml
vendored
@@ -89,7 +89,6 @@ jobs:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
LICENSE.txt
|
||||
dist/*.dmg
|
||||
- name: Clean up keychain and provisioning profile
|
||||
|
||||
@@ -10,41 +10,47 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
entry: [ kcc-c2e, kcc-c2p ]
|
||||
include:
|
||||
- entry: kcc-c2e
|
||||
capital: KCC_c2e
|
||||
- entry: kcc-c2p
|
||||
capital: KCC_c2p
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# - name: Set up Python
|
||||
# uses: actions/setup-python@v4
|
||||
# with:
|
||||
# python-version: 3.11
|
||||
# cache: 'pip'
|
||||
# - name: Install python dependencies
|
||||
# run: |
|
||||
# python -m pip install --upgrade pip setuptools wheel pyinstaller
|
||||
# pip install -r requirements.txt
|
||||
# - name: build binary
|
||||
# run: |
|
||||
# pyi-makespec -F -i icons\\comic2ebook.ico -n KCC_test -w --noupx kcc.py
|
||||
|
||||
- name: Package Application
|
||||
uses: JackMcKew/pyinstaller-action-windows@main
|
||||
with:
|
||||
path: .
|
||||
spec: ./kcc-c2e.spec
|
||||
- name: Package Application
|
||||
uses: JackMcKew/pyinstaller-action-windows@main
|
||||
with:
|
||||
path: .
|
||||
spec: ./kcc-c2p.spec
|
||||
spec: ./${{ matrix.entry }}.spec
|
||||
- name: rename binaries
|
||||
run: |
|
||||
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
|
||||
mv dist/windows/kcc-c2e.exe dist/windows/KCC_c2e_${version_built}.exe
|
||||
mv dist/windows/kcc-c2p.exe dist/windows/KCC_c2p_${version_built}.exe
|
||||
- name: upload build
|
||||
mv dist/windows/${{ matrix.entry }}.exe dist/windows/${{ matrix.capital }}_${version_built}.exe
|
||||
|
||||
- name: upload-unsigned-artifact
|
||||
id: upload-unsigned-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build
|
||||
name: windows-build-${{ matrix.entry }}
|
||||
path: dist/windows/*.exe
|
||||
|
||||
- id: optional_step_id
|
||||
uses: signpath/github-action-submit-signing-request@v1.2
|
||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
|
||||
project-slug: 'kcc'
|
||||
signing-policy-slug: 'release-signing'
|
||||
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: 'dist/windows/'
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
@@ -52,6 +58,5 @@ jobs:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
LICENSE.txt
|
||||
dist/windows/*.exe
|
||||
|
||||
17
.github/workflows/package-windows.yml
vendored
17
.github/workflows/package-windows.yml
vendored
@@ -41,11 +41,23 @@ jobs:
|
||||
- name: build binary
|
||||
run: |
|
||||
python setup.py build_binary
|
||||
- name: upload build
|
||||
- name: upload-unsigned-artifact
|
||||
id: upload-unsigned-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build
|
||||
path: dist/*.exe
|
||||
- id: optional_step_id
|
||||
uses: signpath/github-action-submit-signing-request@v1.2
|
||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
|
||||
project-slug: 'kcc'
|
||||
signing-policy-slug: 'release-signing'
|
||||
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: 'dist/'
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
@@ -53,6 +65,5 @@ jobs:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
LICENSE.txt
|
||||
dist/*.exe
|
||||
dist/*.exe
|
||||
|
||||
30
.travis.yml
30
.travis.yml
@@ -1,30 +0,0 @@
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
language: generic
|
||||
osx_image: xcode11.1
|
||||
|
||||
before_install:
|
||||
- pip3 install --upgrade pip setuptools wheel
|
||||
|
||||
install:
|
||||
- pip3 install -r requirements.txt
|
||||
- pip3 install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip
|
||||
- npm install -g appdmg
|
||||
|
||||
script: python3 setup.py build_binary
|
||||
|
||||
before_deploy:
|
||||
- shopt -s extglob
|
||||
- rm -r dist/!(*.deb|*.dmg)
|
||||
|
||||
deploy:
|
||||
provider: gcs
|
||||
access_key_id: GOOG1EC62457RKUYFR2TIZUWV4EFSV2EP5LVLPPFXUAKADWJFDYPFW63BQSLA
|
||||
secret_access_key:
|
||||
secure: sxYjeho7U3im0Ezf6cz6TjYDiLvf0kAM2ETQHYoFNbD1VVvhJJyymDCnPH80zpFKmhc1MWTB6ndwsrPfcyZDLR2meSdWGPjZfFPY3RcrfImndKi7ln+mYQDBQ7W1lGit4YcH3Ju7LHceaTbRA7fVTX8pWKOcbXL2oM+lQxTJHH32+crVma+ChhbjzTWsSLRoakt3Nhiveec5p/qSW7AFe4Zq+b3C85IgwjSJI/xVwzaWrs6p915h1zZi7KL7YCMIxfQFrvRPFR2KTbh/DoLCCrqfbD4qh0PVy1li51Ac3hd/u3foiNnTNchzgE3Nv/nbKmtFU6huuLNgzkQGuLA+yn7mKYzBwA3ZmFgoimdH9+yRCMkZ8B5VHpvfN1hgpJcyEl1T98Kv4cdtRYNB4w9iAMy1qSVxhjeI+2rjuWGoXro0lU6L4LIRCOruY3AuLCAKG8Qw5Ak9ksmIKBhZ9soxpoIwu/TYDUQkFj29IrUQucg9TEp7uAoxu8/7EHxB7hWnBRaBAAQbMuIRg7yysT3FT0Os6SB0t9+RBsVMSPuIti9JJZ2Lu0uRI1+Se+g7ItzYtJoPhBJAzAa+J9OONj0RNj2z8Vq2oIBhH4z6b6zTRMVroos3cdfYl5qIKs9SQ7rmeHoPRROcqpCznsUZ/ESa4f2MewFU/7AYcEnCesZV4xg=
|
||||
bucket: kcc-deploy
|
||||
local-dir: dist
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: AcidWeb/KCC
|
||||
@@ -1,8 +1,9 @@
|
||||
ISC LICENSE
|
||||
|
||||
Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
Copyright (c) 2012-2025 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
Copyright (c) 2013-2019 Paweł Jastrzębski <pawelj@iosphe.re>
|
||||
Copyright (c) 2021-2023 Darodi
|
||||
Copyright (c) 2021-2023 Darodi (https://github.com/darodi)
|
||||
Copyright (c) 2023-2025 Alex Xu (https://github.com/axu2)
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
exclude kindlecomicconverter/sentry.py
|
||||
128
README.md
128
README.md
@@ -1,15 +1,48 @@
|
||||
<img src="header.jpg" alt="Header Image" width="400">
|
||||
|
||||
# KCC
|
||||
|
||||
|
||||
|
||||
[](https://github.com/ciromattia/kcc/releases)
|
||||
[](https://github.com/ciromattia/kcc/pkgs/container/kcc)
|
||||
[](https://github.com/ciromattia/kcc/releases)
|
||||
|
||||
|
||||
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
|
||||
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
|
||||
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.
|
||||
**Kindle Comic Converter** optimizes black & white comics and manga for E-ink ereaders
|
||||
like Kindle, Kobo, ReMarkable, and more.
|
||||
Pages display in fullscreen without margins,
|
||||
with proper fixed layout support.
|
||||
Supported input formats include JPG/PNG/GIF image files in folders, archives, or PDFs.
|
||||
Supported output formats include MOBI/AZW3, EPUB, KEPUB, and CBZ.
|
||||
|
||||
If your source are super high resolution DRM-free PDFs from Kodansha/Humble Bundle/Fanatical,
|
||||
you'll need to first [convert the PDFs to CBZ](https://github.com/ciromattia/kcc/issues/680) for use in KCC.
|
||||
|
||||
Its main feature is various optional image processing steps to look good on eink screens,
|
||||
which have different requirements than normal LCD screens.
|
||||
Combining that with downscaling to your specific device's screen resolution
|
||||
can result in filesize reductions of hundreds of MB per volume with no visible quality loss on eink.
|
||||
This can also improve battery life, page turn speed, and general performance
|
||||
on underpowered ereaders with small storage capacities.
|
||||
|
||||
KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as:
|
||||
1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain.
|
||||
2) unneccessary margins at the bottom of the screen
|
||||
3) Not utilizing the full 1860x2480 resolution of the 10" Kindle Scribe
|
||||
4) incorrect page turn direction for manga that's read right to left
|
||||
5) unaligned two page spreads in landscape, where pages are shifted over by 1
|
||||
|
||||
The GUI looks like this, built in Qt6, with my most commonly used settings:
|
||||
|
||||

|
||||
|
||||
Simply drag and drop your files/folders into the KCC window,
|
||||
adjust your settings (hover over each option to see details in a tooltip),
|
||||
and hit convert to create ereader optimized files.
|
||||
You can change the default output directory by holding `Shift` while clicking the convert button.
|
||||
Then just drag and drop the generated output files onto your device's documents folder via USB.
|
||||
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac.
|
||||
|
||||
YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=IR2Fhcm9658
|
||||
|
||||
### 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.
|
||||
@@ -22,15 +55,29 @@ If you have some **technical** problems using KCC please [file an issue here](ht
|
||||
If you can fix an open issue, fork & make a pull request.
|
||||
|
||||
If you find **KCC** valuable you can consider donating to the authors:
|
||||
- Ciro Mattia Gonano (founder, active 2013-2014):
|
||||
- [](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 (active 2013-2019):
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||
- [](https://jastrzeb.ski/donate/)
|
||||
- Alex Xu (active 2023-Present)
|
||||
- [](https://www.paypal.com/donate/?business=QFJVE7A6LCP6U&no_recurring=0&item_name=Kindle+Comic+Converter¤cy_code=USD)
|
||||
- Ciro Mattia Gonano (founder, active 2012-2014):
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||
|
||||
- Paweł Jastrzębski (active 2013-2019):
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||
[](https://jastrzeb.ski/donate/)
|
||||
|
||||
- Alex Xu (active 2023-Present)
|
||||
|
||||
[](https://ko-fi.com/Q5Q41BW8HS)
|
||||
|
||||
## Commissions
|
||||
|
||||
This section is subject to change:
|
||||
|
||||
Email (for commisions and inquiries): `kindle.comic.converter` gmail
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
- Free code signing on Windows provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||
|
||||
## DOWNLOADS
|
||||
|
||||
@@ -52,9 +99,25 @@ On Mac, right click open to get past the security warning.
|
||||
For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation
|
||||
|
||||
## FAQ
|
||||
- All options have additional information in tooltips if you hover over the option.
|
||||
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
|
||||
- Right to left mode not working?
|
||||
- RTL mode only affects splitting order for CBZ output. Your cbz reader itself sets the page turn direction.
|
||||
- Colors inverted?
|
||||
- Disable Kindle dark mode
|
||||
- Cannot connect Kindle Scribe or 2024+ Kindle to macOS
|
||||
- Use official MTP [Amazon USB File Transfer app](https://www.amazon.com/gp/help/customer/display.html/ref=hp_Connect_USB_MTP?nodeId=TCUBEdEkbIhK07ysFu)
|
||||
(no login required). Works much better than previously recommended Android File Transfer. Cannot run simutaneously with other transfer apps.
|
||||
- How to make AZW3 instead of MOBI?
|
||||
- The `.mobi` file generated by KCC is a dual filetype, it's both MOBI and AZW3. The file extension is `.mobi` for compatibility reasons.
|
||||
- [Windows 7 support](https://github.com/ciromattia/kcc/issues/678)
|
||||
- [Combine files/chapters](https://github.com/ciromattia/kcc/issues/612#issuecomment-2117985011)
|
||||
- [Flatpak mobi conversion stuck](https://github.com/ciromattia/kcc/wiki/Installation#linux)
|
||||
- Image too dark?
|
||||
- The default gamma correction of 1.8 makes the image darker, and is useful for faded/gray artwork/text. Disable by setting gamma = 1.0
|
||||
- [Better PDF support (Humble Bundle, Fanatical, etc)](https://github.com/ciromattia/kcc/issues/680)
|
||||
- Huge margins / slow page turns?
|
||||
- You likely modified the file during transfer using a 3rd party app. Try simply dragging and dropping the final mobi/kepub file into the Kindle documents folder via USB.
|
||||
|
||||
## PREREQUISITES
|
||||
|
||||
@@ -68,9 +131,11 @@ If you have issues detecting it, get stuck on the MOBI conversion step, or use L
|
||||
|
||||
### 7-Zip
|
||||
|
||||
This is only required for certain files and advanced features.
|
||||
This is optional but will make conversions much faster.
|
||||
|
||||
KCC will ask you to install if needed.
|
||||
This is required for certain files and advanced features.
|
||||
|
||||
KCC will ask you to install if needed.
|
||||
|
||||
Refer to the wiki to install: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
||||
|
||||
@@ -102,10 +167,12 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
|
||||
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
|
||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
||||
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
||||
'K578': ("Kindle", (600, 800), Palette16, 1.8),
|
||||
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.8),
|
||||
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.8),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
|
||||
'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
|
||||
'KV': ("Kindle Voyage, (1072, 1448), Palette16, 1.8),
|
||||
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.8),
|
||||
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.8),
|
||||
'KO': ("Kindle Oasis 2/3/Paperwhite 12/Colorsoft 12", (1264, 1680), Palette16, 1.8),
|
||||
'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.8),
|
||||
@@ -161,6 +228,7 @@ PROCESSING:
|
||||
Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
|
||||
--cp CROPPINGP, --croppingpower CROPPINGP
|
||||
Set cropping power [Default=1.0]
|
||||
--preservemargin After calculating crop, "back up" a specified percentage amount [Default=0]
|
||||
--cm CROPPINGM, --croppingminimum CROPPINGM
|
||||
Set cropping minimum area ratio [Default=0.0]
|
||||
--ipc INTERPANELCROP, --interpanelcrop INTERPANELCROP
|
||||
@@ -178,6 +246,7 @@ OUTPUT SETTINGS:
|
||||
Output generated file to specified directory or file
|
||||
-t TITLE, --title TITLE
|
||||
Comic title [Default=filename or directory name]
|
||||
--comicinfotitle Write title from ComicInfo.xml
|
||||
-a AUTHOR, --author AUTHOR
|
||||
Author name [Default=KCC]
|
||||
-f FORMAT, --format FORMAT
|
||||
@@ -187,6 +256,7 @@ OUTPUT SETTINGS:
|
||||
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
|
||||
--spreadshift Shift first page to opposite side in landscape for two page spread alignment
|
||||
--norotate Do not rotate double page spreads in spread splitter option.
|
||||
--rotatefirst Put rotated spread first in spread splitter option.
|
||||
--reducerainbow Reduce rainbow effect on color eink by slightly blurring images
|
||||
|
||||
CUSTOM PROFILE:
|
||||
@@ -223,17 +293,25 @@ OTHER:
|
||||
|
||||
This section is for developers who want to contribute to KCC or power users who want to run the latest code without waiting for an official release.
|
||||
|
||||
Easiest to use [GitHub Desktop](https://desktop.github.com) to clone the KCC repo. From GitHub Desktop, click on `Repository` in the toolbar, then `Command Prompt` (Windows)/`Terminal` (Mac) to open a window in the KCC repo.
|
||||
Easiest to use [GitHub Desktop](https://desktop.github.com) to clone your fork of the KCC repo. From GitHub Desktop, click on `Repository` in the toolbar, then `Command Prompt` (Windows)/`Terminal` (Mac) to open a window in the KCC repo.
|
||||
|
||||
Depending on your system [Python](https://www.python.org) may be called either `python` or `python3`. We use virtual environments (venv) to manage dependencies.
|
||||
|
||||
If you want to edit the code, a good code editor is [VS Code](https://code.visualstudio.com).
|
||||
|
||||
If you want to edit the `.ui` files, use [Qt Creator](https://www.qt.io/download-qt-installer-oss), included in **Qt for desktop development**.
|
||||
If you want to edit the `.ui` files, use `pyside6-designer` which is included in the `pip install pyside6`.
|
||||
Then use the `gen_ui_files` scripts to autogenerate the python UI.
|
||||
|
||||
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785
|
||||
|
||||
video of adding a new checkbox: https://youtu.be/g3I8DU74C7g
|
||||
|
||||
Do not use `git merge` to merge master from upstream,
|
||||
use the "Sync fork" button on your fork on GitHub in your branch
|
||||
to avoid weird looking merges in pull requests.
|
||||
|
||||
When making changes, be aware of how your change might affect file splitting/chunking
|
||||
or chapter alignment.
|
||||
|
||||
### Windows install from source
|
||||
|
||||
@@ -260,6 +338,8 @@ python setup.py build_binary
|
||||
|
||||
### macOS install from source
|
||||
|
||||
If the system installed Python gives you issues, please install the latest Python from either brew or the official website.
|
||||
|
||||
One time setup and running for the first time:
|
||||
```
|
||||
python3 -m venv venv
|
||||
@@ -298,6 +378,12 @@ The app relies and includes the following scripts:
|
||||
- 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
|
||||
|
||||
https://www.mediafire.com/folder/ixh40veo6hrc5/kcc_samples
|
||||
|
||||
Older links (dead):
|
||||
|
||||
|
||||
* [Kindle Oasis 2 / 3](http://kcc.iosphe.re/Samples/Ubunchu!-KO.mobi)
|
||||
* [Kindle Paperwhite 3 / 4 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
||||
@@ -317,5 +403,5 @@ The app relies and includes the following scripts:
|
||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||
|
||||
## COPYRIGHT
|
||||
Copyright (c) 2012-2023 Ciro Mattia Gonano, Paweł Jastrzębski and Darodi.
|
||||
Copyright (c) 2012-2025 Ciro Mattia Gonano, Paweł Jastrzębski, Darodi and Alex Xu.
|
||||
**KCC** is released under ISC LICENSE; see [LICENSE.txt](./LICENSE.txt) for further details.
|
||||
|
||||
14
appveyor.yml
14
appveyor.yml
@@ -1,14 +0,0 @@
|
||||
environment:
|
||||
PYTHON: "C:\\Python37-x64"
|
||||
|
||||
install:
|
||||
- set PATH="%PYTHON%\\Scripts";%PATH%
|
||||
- "%PYTHON%\\python.exe -m pip install --upgrade pip setuptools wheel"
|
||||
- "%PYTHON%\\python.exe -m pip install -r requirements.txt"
|
||||
- "%PYTHON%\\python.exe -m pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip"
|
||||
|
||||
build_script:
|
||||
- "%PYTHON%\\python.exe setup.py build_binary"
|
||||
|
||||
artifacts:
|
||||
- path: dist\KCC*
|
||||
@@ -4,7 +4,7 @@ channels:
|
||||
- defaults
|
||||
dependencies:
|
||||
- python=3.11
|
||||
- Pillow>=5.2.0
|
||||
- Pillow>=11.3.0
|
||||
- psutil>=5.9.5
|
||||
- python-slugify>=1.2.1
|
||||
- raven>=6.0.0
|
||||
|
||||
@@ -27,5 +27,6 @@
|
||||
<file>../icons/convert.png</file>
|
||||
<file>../icons/document_new.png</file>
|
||||
<file>../icons/folder_new.png</file>
|
||||
<file>../icons/kofi_symbol.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
1033
gui/KCC.ui
1033
gui/KCC.ui
File diff suppressed because it is too large
Load Diff
BIN
header.jpg
Normal file
BIN
header.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 921 KiB |
BIN
icons/kofi_symbol.png
Normal file
BIN
icons/kofi_symbol.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
@@ -11,7 +11,7 @@ a = Analysis(['kcc-c2e.py'],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
excludes=['pkg_resources'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
|
||||
@@ -11,7 +11,7 @@ a = Analysis(['kcc-c2p.py'],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
excludes=['pkg_resources'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
|
||||
39
kcc.spec
Normal file
39
kcc.spec
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['kcc.py'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=['pkg_resources'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='kcc',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None , icon='icons\\comic2ebook.ico')
|
||||
@@ -16,6 +16,9 @@
|
||||
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import itertools
|
||||
from pathlib import Path
|
||||
from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings)
|
||||
from PySide6.QtGui import (QColor, QIcon, QPixmap, QDesktopServices)
|
||||
from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QApplication, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog)
|
||||
@@ -27,7 +30,7 @@ import sys
|
||||
from urllib.parse import unquote
|
||||
from time import sleep
|
||||
from shutil import move, rmtree
|
||||
from subprocess import STDOUT, PIPE
|
||||
from subprocess import STDOUT, PIPE, CalledProcessError
|
||||
|
||||
import requests
|
||||
from xml.sax.saxutils import escape
|
||||
@@ -38,6 +41,7 @@ from raven import Client
|
||||
from tempfile import gettempdir
|
||||
|
||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
|
||||
from .comicarchive import SEVENZIP, available_archive_tools
|
||||
from . import __version__
|
||||
from . import comic2ebook
|
||||
from . import metadata
|
||||
@@ -119,6 +123,8 @@ class Icons:
|
||||
self.CBZFormat.addPixmap(QPixmap(":/Formats/icons/CBZ.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.EPUBFormat = QIcon()
|
||||
self.EPUBFormat.addPixmap(QPixmap(":/Formats/icons/EPUB.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.KFXFormat = QIcon()
|
||||
self.KFXFormat.addPixmap(QPixmap(":/Formats/icons/KFX.png"), QIcon.Normal, QIcon.Off)
|
||||
|
||||
self.info = QIcon()
|
||||
self.info.addPixmap(QPixmap(":/Status/icons/info.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
@@ -244,6 +250,7 @@ class WorkerThread(QThread):
|
||||
options.cropping = GUI.croppingBox.checkState().value
|
||||
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
|
||||
options.croppingp = float(GUI.croppingPowerValue)
|
||||
options.preservemargin = GUI.preserveMarginBox.value()
|
||||
options.interpanelcrop = GUI.interPanelCropBox.checkState().value
|
||||
if GUI.borderBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
options.white_borders = True
|
||||
@@ -259,12 +266,20 @@ class WorkerThread(QThread):
|
||||
options.maximizestrips = True
|
||||
if GUI.disableProcessingBox.isChecked():
|
||||
options.noprocessing = True
|
||||
if GUI.comicinfoTitleBox.isChecked():
|
||||
options.comicinfotitle = True
|
||||
if GUI.deleteBox.isChecked():
|
||||
options.delete = True
|
||||
if GUI.spreadShiftBox.isChecked():
|
||||
options.spreadshift = True
|
||||
if GUI.fileFusionBox.isChecked():
|
||||
options.filefusion = True
|
||||
else:
|
||||
options.filefusion = False
|
||||
if GUI.noRotateBox.isChecked():
|
||||
options.norotate = True
|
||||
if GUI.rotateFirstBox.isChecked():
|
||||
options.rotatefirst = True
|
||||
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
options.forcepng = True
|
||||
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
|
||||
@@ -276,12 +291,27 @@ class WorkerThread(QThread):
|
||||
options.output = GUI.targetDirectory
|
||||
if GUI.authorEdit.text():
|
||||
options.author = str(GUI.authorEdit.text())
|
||||
if GUI.chunkSizeCheckBox.isChecked():
|
||||
options.targetsize = int(GUI.chunkSizeBox.value())
|
||||
|
||||
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():
|
||||
currentJobs.append(str(GUI.jobList.item(i).text()))
|
||||
GUI.jobList.clear()
|
||||
if options.filefusion:
|
||||
bookDir = []
|
||||
MW.addMessage.emit('Attempting file fusion', 'info', False)
|
||||
for job in currentJobs:
|
||||
bookDir.append(job)
|
||||
try:
|
||||
comic2ebook.options = comic2ebook.checkOptions(copy(options))
|
||||
currentJobs.clear()
|
||||
currentJobs.append(comic2ebook.makeFusion(bookDir))
|
||||
MW.addMessage.emit('Created fusion at ' + currentJobs[0], 'info', False)
|
||||
except Exception as e:
|
||||
print('Fusion Failed. ' + str(e))
|
||||
MW.addMessage.emit('Fusion Failed. ' + str(e), 'error', True)
|
||||
for job in currentJobs:
|
||||
sleep(0.5)
|
||||
if not self.conversionAlive:
|
||||
@@ -317,13 +347,8 @@ class WorkerThread(QThread):
|
||||
GUI.progress.content = ''
|
||||
self.errors = True
|
||||
_, _, traceback = sys.exc_info()
|
||||
if len(err.args) == 1:
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||
else:
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
|
||||
GUI.sentry.extra_context({'realTraceback': err.args[1]})
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||
if ' is corrupted.' not in str(err):
|
||||
GUI.sentry.captureException()
|
||||
MW.addMessage.emit('Error during conversion! Please consult '
|
||||
@@ -423,6 +448,8 @@ class WorkerThread(QThread):
|
||||
MW.addMessage.emit('Created EPUB file was too big.', 'error', False)
|
||||
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
||||
False)
|
||||
if self.kindlegenErrorCode[0] == 3221226505:
|
||||
MW.addMessage.emit('Unknown Windows error. Possibly filepath too long?', 'error', False)
|
||||
else:
|
||||
for item in outputPath:
|
||||
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(item):
|
||||
@@ -430,6 +457,12 @@ class WorkerThread(QThread):
|
||||
move(item, GUI.targetDirectory)
|
||||
except Exception:
|
||||
pass
|
||||
if options.filefusion:
|
||||
for path in currentJobs:
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
elif os.path.isdir(path):
|
||||
rmtree(path)
|
||||
GUI.progress.content = ''
|
||||
GUI.progress.stop()
|
||||
MW.hideProgressBar.emit()
|
||||
@@ -459,17 +492,33 @@ class SystemTrayIcon(QSystemTrayIcon):
|
||||
|
||||
|
||||
class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
def selectDir(self):
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.jobList.clear()
|
||||
dname = QFileDialog.getExistingDirectory(MW, 'Select directory', self.lastPath)
|
||||
def selectDefaultOutputFolder(self):
|
||||
dname = QFileDialog.getExistingDirectory(MW, 'Select default output folder', self.defaultOutputFolder)
|
||||
if self.is_directory_on_kindle(dname):
|
||||
return
|
||||
if dname != '':
|
||||
if sys.platform.startswith('win'):
|
||||
dname = dname.replace('/', '\\')
|
||||
self.lastPath = os.path.abspath(os.path.join(dname, os.pardir))
|
||||
GUI.jobList.addItem(dname)
|
||||
GUI.jobList.scrollToBottom()
|
||||
GUI.defaultOutputFolder = dname
|
||||
|
||||
def is_directory_on_kindle(self, dname):
|
||||
path = Path(dname)
|
||||
for parent in itertools.chain([path], path.parents):
|
||||
if parent.name == 'documents' and parent.parent.joinpath('system').joinpath('thumbnails').is_dir():
|
||||
self.addMessage("Cannot select Kindle as output directory", 'error')
|
||||
return True
|
||||
|
||||
def selectOutputFolder(self):
|
||||
dname = QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
|
||||
if self.is_directory_on_kindle(dname):
|
||||
return
|
||||
if dname != '':
|
||||
if sys.platform.startswith('win'):
|
||||
dname = dname.replace('/', '\\')
|
||||
GUI.targetDirectory = dname
|
||||
else:
|
||||
GUI.targetDirectory = ''
|
||||
return GUI.targetDirectory
|
||||
|
||||
def selectFile(self):
|
||||
if self.needClean:
|
||||
@@ -531,6 +580,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://github.com/ciromattia/kcc/wiki'))
|
||||
|
||||
def openKofi(self):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://ko-fi.com/eink_dude'))
|
||||
|
||||
def modeChange(self, mode):
|
||||
if mode == 1:
|
||||
self.currentMode = 1
|
||||
@@ -553,7 +606,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.editorButton.setEnabled(status)
|
||||
GUI.wikiButton.setEnabled(status)
|
||||
GUI.deviceBox.setEnabled(status)
|
||||
GUI.directoryButton.setEnabled(status)
|
||||
GUI.defaultOutputFolderButton.setEnabled(status)
|
||||
GUI.clearButton.setEnabled(status)
|
||||
GUI.fileButton.setEnabled(status)
|
||||
GUI.formatBox.setEnabled(status)
|
||||
@@ -608,6 +661,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.rotateBox.setChecked(False)
|
||||
GUI.upscaleBox.setEnabled(False)
|
||||
GUI.upscaleBox.setChecked(True)
|
||||
GUI.chunkSizeCheckBox.setEnabled(False)
|
||||
GUI.chunkSizeCheckBox.setChecked(False)
|
||||
else:
|
||||
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
|
||||
if profile['PVOptions']:
|
||||
@@ -615,18 +670,23 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.mangaBox.setEnabled(True)
|
||||
GUI.rotateBox.setEnabled(True)
|
||||
GUI.upscaleBox.setEnabled(True)
|
||||
GUI.chunkSizeCheckBox.setEnabled(True)
|
||||
|
||||
def togglequalityBox(self, value):
|
||||
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
|
||||
if value == 2:
|
||||
if profile['Label'] in ['KV', 'KO']:
|
||||
if profile['Label'] not in ('K57', 'KPW', 'K810') :
|
||||
self.addMessage('This option is intended for older Kindle models.', 'warning')
|
||||
self.addMessage('On this device, quality improvement will be negligible.', 'warning')
|
||||
self.addMessage('On this device, there will be conversion speed and quality issues.', 'warning')
|
||||
self.addMessage('Use the Kindle Scribe profile if you want higher resolution when zooming.', 'warning')
|
||||
GUI.upscaleBox.setEnabled(False)
|
||||
GUI.upscaleBox.setChecked(True)
|
||||
else:
|
||||
GUI.upscaleBox.setEnabled(True)
|
||||
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
|
||||
|
||||
def togglechunkSizeCheckBox(self, value):
|
||||
GUI.chunkSizeWidget.setVisible(value)
|
||||
|
||||
def changeGamma(self, value):
|
||||
valueRaw = int(5 * round(float(value) / 5))
|
||||
@@ -660,7 +720,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if not GUI.webtoonBox.isChecked():
|
||||
GUI.qualityBox.setEnabled(profile['PVOptions'])
|
||||
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
|
||||
GUI.mangaBox.setChecked(True)
|
||||
if profile['Label'] == 'KS':
|
||||
GUI.upscaleBox.setDisabled(True)
|
||||
else:
|
||||
GUI.upscaleBox.setEnabled(True)
|
||||
if not profile['PVOptions']:
|
||||
GUI.qualityBox.setChecked(False)
|
||||
if str(GUI.deviceBox.currentText()) == 'Other':
|
||||
@@ -680,6 +743,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
else:
|
||||
GUI.outputSplit.setEnabled(False)
|
||||
GUI.outputSplit.setChecked(False)
|
||||
if (GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'EPUB-200MB' or
|
||||
GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'MOBI+EPUB-200MB'):
|
||||
GUI.chunkSizeCheckBox.setEnabled(False)
|
||||
GUI.chunkSizeCheckBox.setChecked(False)
|
||||
elif not GUI.webtoonBox.isChecked():
|
||||
GUI.chunkSizeCheckBox.setEnabled(True)
|
||||
|
||||
def stripTags(self, html):
|
||||
s = HTMLStripper()
|
||||
@@ -734,13 +803,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.worker.sync()
|
||||
else:
|
||||
if QApplication.keyboardModifiers() == Qt.KeyboardModifier.ShiftModifier:
|
||||
dname = QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
|
||||
if dname != '':
|
||||
if sys.platform.startswith('win'):
|
||||
dname = dname.replace('/', '\\')
|
||||
GUI.targetDirectory = dname
|
||||
else:
|
||||
GUI.targetDirectory = ''
|
||||
if not self.selectOutputFolder():
|
||||
return
|
||||
elif GUI.defaultOutputFolderBox.isChecked():
|
||||
self.targetDirectory = self.defaultOutputFolder
|
||||
else:
|
||||
GUI.targetDirectory = ''
|
||||
self.progress.start()
|
||||
@@ -751,6 +817,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.addMessage('No files selected! Please choose files to convert.', 'error')
|
||||
self.needClean = True
|
||||
return
|
||||
if GUI.defaultOutputFolderBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
parent = Path(self.jobList.item(0).text()).parent
|
||||
target_path = parent.joinpath(f"{parent.name}")
|
||||
if not target_path.exists():
|
||||
target_path.mkdir()
|
||||
self.targetDirectory = str(target_path)
|
||||
if self.currentMode > 2 and (GUI.widthBox.value() == 0 or GUI.heightBox.value() == 0):
|
||||
GUI.jobList.clear()
|
||||
self.addMessage('Target resolution is not set!', 'error')
|
||||
@@ -782,6 +854,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
event.ignore()
|
||||
self.settings.setValue('settingsVersion', __version__)
|
||||
self.settings.setValue('lastPath', self.lastPath)
|
||||
self.settings.setValue('defaultOutputFolder', self.defaultOutputFolder)
|
||||
self.settings.setValue('lastDevice', GUI.deviceBox.currentIndex())
|
||||
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
|
||||
self.settings.setValue('startNumber', self.startNumber + 1)
|
||||
@@ -792,6 +865,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'gammaBox': GUI.gammaBox.checkState().value,
|
||||
'croppingBox': GUI.croppingBox.checkState().value,
|
||||
'croppingPowerSlider': float(self.croppingPowerValue) * 100,
|
||||
'preserveMarginBox': self.preserveMarginBox.value(),
|
||||
'interPanelCropBox': GUI.interPanelCropBox.checkState().value,
|
||||
'upscaleBox': GUI.upscaleBox.checkState().value,
|
||||
'borderBox': GUI.borderBox.checkState().value,
|
||||
@@ -800,14 +874,20 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'colorBox': GUI.colorBox.checkState().value,
|
||||
'reduceRainbowBox': GUI.reduceRainbowBox.checkState().value,
|
||||
'disableProcessingBox': GUI.disableProcessingBox.checkState().value,
|
||||
'comicinfoTitleBox': GUI.comicinfoTitleBox.checkState().value,
|
||||
'mozJpegBox': GUI.mozJpegBox.checkState().value,
|
||||
'widthBox': GUI.widthBox.value(),
|
||||
'heightBox': GUI.heightBox.value(),
|
||||
'deleteBox': GUI.deleteBox.checkState().value,
|
||||
'spreadShiftBox': GUI.spreadShiftBox.checkState().value,
|
||||
'fileFusionBox': GUI.fileFusionBox.checkState().value,
|
||||
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState().value,
|
||||
'noRotateBox': GUI.noRotateBox.checkState().value,
|
||||
'rotateFirstBox': GUI.rotateFirstBox.checkState().value,
|
||||
'maximizeStrips': GUI.maximizeStrips.checkState().value,
|
||||
'gammaSlider': float(self.gammaValue) * 100})
|
||||
'gammaSlider': float(self.gammaValue) * 100,
|
||||
'chunkSizeCheckBox': GUI.chunkSizeCheckBox.checkState().value,
|
||||
'chunkSizeBox': GUI.chunkSizeBox.value()})
|
||||
self.settings.sync()
|
||||
self.tray.hide()
|
||||
|
||||
@@ -859,7 +939,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
versionCheck = subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
|
||||
versionCheck = subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True)
|
||||
self.kindleGen = True
|
||||
for line in versionCheck.stdout.splitlines():
|
||||
if 'Amazon kindlegen' in line:
|
||||
@@ -868,7 +948,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.addMessage('Your <a href="https://www.amazon.com/b?node=23496309011">KindleGen</a>'
|
||||
' is outdated! MOBI conversion might fail.', 'warning')
|
||||
break
|
||||
except FileNotFoundError:
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
self.kindleGen = False
|
||||
if startup:
|
||||
self.display_kindlegen_missing()
|
||||
@@ -884,6 +964,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.settings = QSettings('ciromattia', 'kcc')
|
||||
self.settingsVersion = self.settings.value('settingsVersion', '', type=str)
|
||||
self.lastPath = self.settings.value('lastPath', '', type=str)
|
||||
self.defaultOutputFolder = str(self.settings.value('defaultOutputFolder', '', type=str))
|
||||
if not os.path.exists(self.defaultOutputFolder):
|
||||
self.defaultOutputFolder = ''
|
||||
self.lastDevice = self.settings.value('lastDevice', 0, type=int)
|
||||
self.currentFormat = self.settings.value('currentFormat', 0, type=int)
|
||||
self.startNumber = self.settings.value('startNumber', 0, type=int)
|
||||
@@ -912,7 +995,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if self.windowSize == '0x0':
|
||||
MW.resize(500, 500)
|
||||
elif sys.platform.startswith('darwin'):
|
||||
for element in ['editorButton', 'wikiButton', 'directoryButton', 'clearButton', 'fileButton', 'deviceBox',
|
||||
for element in ['editorButton', 'wikiButton', 'defaultOutputFolderButton', 'clearButton', 'fileButton', 'deviceBox',
|
||||
'convertButton', 'formatBox']:
|
||||
getattr(GUI, element).setMinimumSize(QSize(0, 0))
|
||||
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
|
||||
@@ -925,17 +1008,20 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"MOBI/AZW3": {'icon': 'MOBI', 'format': 'MOBI'},
|
||||
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
|
||||
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
|
||||
"EPUB (Calibre KFX)": {'icon': 'EPUB', 'format': 'KFX'},
|
||||
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
|
||||
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
|
||||
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'}
|
||||
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
|
||||
"MOBI + EPUB (200MB limit)": {'icon': 'MOBI', 'format': 'MOBI+EPUB-200MB'},
|
||||
}
|
||||
|
||||
|
||||
self.profiles = {
|
||||
"Kindle Oasis 9/10": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KO'},
|
||||
"Kindle 8/10": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'ForceColor': False, 'Label': 'K810'},
|
||||
"Kindle Oasis 8": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KV'},
|
||||
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW34'},
|
||||
"Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KV'},
|
||||
"Kindle Scribe": {
|
||||
@@ -944,21 +1030,21 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"Kindle 11": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'K11',
|
||||
},
|
||||
"Kindle PW 11": {
|
||||
"Kindle Paperwhite 11": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW5',
|
||||
},
|
||||
"Kindle PW 12": {
|
||||
"Kindle Paperwhite 12": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KO',
|
||||
},
|
||||
"Kindle CS 12": {
|
||||
"Kindle Colorsoft": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': True, 'Label': 'KO',
|
||||
},
|
||||
"Kindle PW 7/10": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KV'},
|
||||
"Kindle PW 5/6": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
"Kindle Paperwhite 7/10": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW34'},
|
||||
"Kindle Paperwhite 5/6": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KPW'},
|
||||
"Kindle 4/5/7/8/10": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'ForceColor': False, 'Label': 'K578'},
|
||||
"Kindle 4/5/7": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': False, 'ForceColor': False, 'Label': 'K57'},
|
||||
"Kindle DX": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 2,
|
||||
'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KDX'},
|
||||
"Kobo Mini/Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
|
||||
@@ -1013,10 +1099,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'Label': 'OTHER'},
|
||||
}
|
||||
profilesGUI = [
|
||||
"Kindle CS 12",
|
||||
"Kindle PW 12",
|
||||
"Kindle Colorsoft",
|
||||
"Kindle Paperwhite 12",
|
||||
"Kindle Scribe",
|
||||
"Kindle PW 11",
|
||||
"Kindle Paperwhite 11",
|
||||
"Kindle 11",
|
||||
"Kindle Oasis 9/10",
|
||||
"Separator",
|
||||
@@ -1034,11 +1120,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"Separator",
|
||||
"Other",
|
||||
"Separator",
|
||||
"Kindle 8/10",
|
||||
"Kindle Oasis 8",
|
||||
"Kindle PW 7/10",
|
||||
"Kindle Paperwhite 7/10",
|
||||
"Kindle Voyage",
|
||||
"Kindle PW 5/6",
|
||||
"Kindle 4/5/7/8/10",
|
||||
"Kindle Paperwhite 5/6",
|
||||
"Kindle 4/5/7",
|
||||
"Kindle Touch",
|
||||
"Kindle Keyboard",
|
||||
"Kindle DX",
|
||||
@@ -1057,41 +1144,44 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"Kobo Mini/Touch",
|
||||
]
|
||||
|
||||
statusBarLabel = QLabel('<b><a href="https://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="http://www.mobileread.com/forums/showthread.php?t=207461'
|
||||
'">FORUM</a></b>')
|
||||
link_dict = {
|
||||
'README': "https://github.com/ciromattia/kcc?tab=readme-ov-file#kcc",
|
||||
'FAQ': "https://github.com/ciromattia/kcc/blob/master/README.md#faq",
|
||||
'YOUTUBE': "https://youtu.be/IR2Fhcm9658?si=Z-2zzLaUFjmaEbrj",
|
||||
'COMMISSIONS': "https://github.com/ciromattia/kcc?tab=readme-ov-file#commissions",
|
||||
'DONATE': "https://github.com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations",
|
||||
'FORUM': "http://www.mobileread.com/forums/showthread.php?t=207461",
|
||||
'DISCORD': "https://discord.com/invite/qj7wpnUHav",
|
||||
}
|
||||
|
||||
link_html_list = [f'<a href="{v}">{k}</a>' for k, v in link_dict.items()]
|
||||
statusBarLabel = QLabel(f'<b>{" - ".join(link_html_list)}</b>')
|
||||
statusBarLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
statusBarLabel.setOpenExternalLinks(True)
|
||||
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
|
||||
|
||||
self.addMessage('<b>Welcome!</b>', 'info')
|
||||
self.addMessage('<b>Remember:</b> All options have additional information in tooltips.', 'info')
|
||||
self.addMessage('<b>Tip:</b> Hover mouse over options to see additional information in tooltips.', 'info')
|
||||
self.addMessage('<b>Tip:</b> You can drag and drop image folders or comic files/archives into this window to convert.', 'info')
|
||||
if self.startNumber < 5:
|
||||
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||
'info')
|
||||
try:
|
||||
subprocess_run(['tar'], stdout=PIPE, stderr=STDOUT)
|
||||
self.tar = True
|
||||
except FileNotFoundError:
|
||||
self.tar = False
|
||||
try:
|
||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
||||
self.sevenzip = True
|
||||
except FileNotFoundError:
|
||||
self.sevenzip = False
|
||||
if not self.tar:
|
||||
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
||||
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
||||
|
||||
self.tar = 'tar' in available_archive_tools()
|
||||
self.sevenzip = SEVENZIP in available_archive_tools()
|
||||
if not any([self.tar, self.sevenzip]):
|
||||
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
||||
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
||||
self.detectKindleGen(True)
|
||||
|
||||
APP.messageFromOtherInstance.connect(self.handleMessage)
|
||||
GUI.directoryButton.clicked.connect(self.selectDir)
|
||||
GUI.defaultOutputFolderButton.clicked.connect(self.selectDefaultOutputFolder)
|
||||
GUI.clearButton.clicked.connect(self.clearJobs)
|
||||
GUI.fileButton.clicked.connect(self.selectFile)
|
||||
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
||||
GUI.wikiButton.clicked.connect(self.openWiki)
|
||||
GUI.kofiButton.clicked.connect(self.openKofi)
|
||||
GUI.convertButton.clicked.connect(self.convertStart)
|
||||
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
||||
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
|
||||
@@ -1099,6 +1189,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.croppingPowerSlider.valueChanged.connect(self.changeCroppingPower)
|
||||
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
|
||||
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
|
||||
GUI.chunkSizeCheckBox.stateChanged.connect(self.togglechunkSizeCheckBox)
|
||||
GUI.deviceBox.activated.connect(self.changeDevice)
|
||||
GUI.formatBox.activated.connect(self.changeFormat)
|
||||
MW.progressBarTick.connect(self.updateProgressbar)
|
||||
@@ -1151,6 +1242,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if GUI.croppingPowerSlider.isEnabled():
|
||||
GUI.croppingPowerSlider.setValue(int(self.options[option]))
|
||||
self.changeCroppingPower(int(self.options[option]))
|
||||
GUI.preserveMarginBox.setValue(self.options.get('preserveMarginBox', 0))
|
||||
elif str(option) == "chunkSizeBox":
|
||||
GUI.chunkSizeBox.setValue(int(self.options[option]))
|
||||
else:
|
||||
try:
|
||||
if getattr(GUI, option).isEnabled():
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Resource object code (Python 3)
|
||||
# Created by: object code
|
||||
# Created by: The Resource Compiler for Qt version 6.8.1
|
||||
# Created by: The Resource Compiler for Qt version 6.9.1
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide6 import QtCore
|
||||
@@ -6062,6 +6062,588 @@ $\xb8I\x00B\xd9\xcb $]\xa6\x90qE\xb4{\
|
||||
\x8a\xf6\x7f5\x09`\xd3%\x01\xf9'\xc1\xcd\xfa\x01\x0f\
|
||||
\x02L\xdb\x8e|\xe3\xd9\x00\x00\x00\x00IEND\xae\
|
||||
B`\x82\
|
||||
\x00\x00$=\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
\x00\x01A\x00\x00\x01\x02\x08\x06\x00\x00\x00`\xc2e\xf3\
|
||||
\x00\x00\x00\x09pHYs\x00\x00,K\x00\x00,K\
|
||||
\x01\xa5=\x96\xa9\x00\x00\x00\x01sRGB\x00\xae\xce\
|
||||
\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfc\
|
||||
a\x05\x00\x00#\xd2IDATx\x01\xed\x9dOl\
|
||||
U\xe7\x99\xc6_P6\xd3\x11!\x95\x92\x15iq\x16\
|
||||
\xb30\xa9\x94D\x8d\x94(\x15\x89S\xc9\xa8\x15\x8bB\
|
||||
\x89\x94d\xd1\x09Fj\xe8f\x0a\x8c\xc2bfj\xd5\
|
||||
\x94\xa8]\x18M\xec\xaeJ*\x81\x99\x8c\x14\x22AC\
|
||||
\x16\xa8\x11V\x1b\x13\xd4\xaaH\xa9\xea\xa8-,s\x99\
|
||||
\x94U*\x15\x075\xb3\xa4\xe7\xb9\xf7|\xf8\xf8\xe6\xfe\
|
||||
\xbf\xdf\xfb~\x7f\xce\xf3\x93\x0e\xf7\xfa\xda\xd8\xbe\xbe\xf7\
|
||||
<\xe7y\xff|\xef\xb7IH\x14\xdc\xb9sg\xa2\xf2\
|
||||
\xa1\xbb\x7f_yH\x97\x8f\xb7w\xf8V\xed_\xd3\xef\
|
||||
\xf1n\x0c\xfb\xf5!h\xc8\xf0\xdc*\x8fa\xbe\xe7Z\
|
||||
\xdb\xffi\xff\x1e\x8d\x0e\xf7om\xda\xb4\xa9\xd7\xcf!\
|
||||
\x91\xb0I\x88\x17\x0a\x11s\xa2\x81c\xa2r\xdf\x1d[\
|
||||
\xdb>/\x92\x86\xd0\x90\xf1i\xc8F\xe1\xc4\xc7NX\
|
||||
\xab\x9fk~L\xf1\xb4\x85\x22\xd8\x87\x8a\xb8=*\xeb\
|
||||
\x02\xe6\x1c\x98{\x8cbF|\xd3\x90\x8a0\x16\xc7\x0d\
|
||||
\xd9(\x9a\xab\x14K?P\x04K\x0a\xb1\x83\xa0\xe1\x98\
|
||||
\x90\x96\xc8\xb9\xfb\x147\x12+N\x14q|X\x1c\xab\
|
||||
\xd2r\x92\xabB\x06\xa6\x96\x22X\xe6\xdf\xa6\xa4%t\
|
||||
\xcf\x94\xb7\x84\xe4\xc4jy@\x1cW(\x8c\xdd\xa9\x85\
|
||||
\x08\x96!\xed\x1ei\x09\x1en\xe9\xeeH\xddh\x86\xd0\
|
||||
\xc5qYZ\xa2\xb8\x22\xa4I\xb6\x22X\xba=\x08\xde\
|
||||
\xb7\xa4\xe5\xfa\x08!\xeb8Q<#-QlHM\
|
||||
\xc9J\x04K\xc7\xb7_(|\x84\x0cK\xa38\xde)\
|
||||
\x8e\x0bus\x89Y\x88`!~S\xc5\xcd\x0f\x85\xc2\
|
||||
G\x88\x0f\x1a\xc5\xb1R\x1c\x8bu\xc8%&+\x82\x15\
|
||||
\xd7\xf7\x92\xb0\xb0A\x88\x16\x10\xc1E\xc98dNN\
|
||||
\x04K\xf1;T\x1c\x87\x85\x05\x0eB,Y*\x8e3\
|
||||
\xb9\x85\xcb\xc9\x88 \xc5\x8f\x90hh\xba\xc3B\x0c\x97\
|
||||
$\x03\xa2\x17A\x8a\x1f!\xd1\xd2(\x8ec\xa9\x8ba\
|
||||
\xd4\x22X\x16<N\xcb\xfaZ[BH|4$a\
|
||||
1\x8cR\x04\xcb\x1e?\x88\xdf\x94\x10BR\xa1!\x09\
|
||||
\x8a\xe1f\x89\x8cB\x00\x11\xf6\xfeA(\x80\x84\xa4\xc6\
|
||||
Dq\x9c.\xce\xe1\x8f\x8ac\xbf$B4N\xb0t\
|
||||
\x7fo\x0b\xdb]\x08\xc9\x85\x0b\xc5q$\xf6\xd6\x9a(\
|
||||
\x9c`\xc5\xfdQ\x00\x09\xc9\x07,[\x85+\xfc\xa1D\
|
||||
LP'XV~\x91\xfb\xdb#\x84\x90\x9ci\x14\xc7\
|
||||
L\x8c=\x86\xc1\x9c`9\xbf\x0f\xee\x8f\x02HH\xfe\
|
||||
L\x14\xc7{\xc5y\xffZi~\xa2!\x88\x13,\xfe\
|
||||
\x08X\xea\xb6 \xec\xfb\x1b\x88\xbf\xfc\xe5/]?\xfe\
|
||||
\xf4\xd3O\x9bG\xbf\xffS\xe5\xf6\xed\xdb\xb2\xb6\xb6&\
|
||||
\xe3\x80\xef\xd1\xe9\xe7\xfa\xe0\xde{\xef\x95-[\xb6\x88\
|
||||
/\x1e|\xf0\xc1\x91\xbe\x06\xbf\x07\x8en\x8fu\xfa<\
|
||||
\x19\x88Fq<\x1bK\xae\xd0\x5c\x04\xcb\xfc\xc0\x9c\xd4\
|
||||
\x00'D\xb8ub\xe5\x8e\xaa\x10\xdd\xbcys\xc3\xd7\
|
||||
\xb7\xdf'\xf1S\x15D'\xa8N\xcc\xb7n\xdd\xda\xbc\
|
||||
u_\xe3>\x8f\xdb\x9a\x0b\xe9\x5c!\x84\xc7$0\xa6\
|
||||
\x22X\x08 \xf2\x7f\xfb%a `\x10('l\xee\
|
||||
\xbesFU\xc1#dP \x88N\x1c\xb7m\xdbv\
|
||||
W \xab\xb7\x838\xda\x04\x09^A6\x11\xc12\x07\
|
||||
\x80\xf6\x97)I\x00\x08\xd9\xb5k\xd7\x9a\xb7pi\xee\
|
||||
c\x8a\x1b\x09\x8d\x13\xc3\x1d;v4\xc5\x12\xb78\x12\
|
||||
w\x93\x0d\x09\x18\x1e\xab\x8b`)\x80\xefI\xc4\xed/\
|
||||
\xbf\xfb\xdd\xef\x9a\x22w\xf5\xea\xd5\xe6}\x0a\x1dI\x0d\
|
||||
\x88 \xc4\xf0\x89'\x9eh\xde>\xf9\xe4\x93\xa9\x09#\
|
||||
&]\xa3z|A\x8cQ\x15\xc1X\x05\x10\x22w\xee\
|
||||
\xdc9Y^^\xbe\xeb\xf0\x08\xc9\x0d\xe7\x12\xa7\xa7\xa7\
|
||||
S\x12E\xf3<\xa1\x9a\x08\xc6&\x80U\xe1\x83\xdb#\
|
||||
\xa4n@\x08\x9d B\x1c#\xc6T\x08UD0&\
|
||||
\x01\x84\xe0\x9d:u\xaa\x19\xea\xd2\xf1\x11\xd2\x02y\xc5\
|
||||
]\xbbv\xc9\xcc\xccL\xac\x05\x97\x85B\x08\x8f\x88\x01\
|
||||
Z\x22\x88\x22H\xd0&h\x88\xdf\xe2\xe2\x22]\x1f!\
|
||||
}\x803\xdc\xb7o_S\x14#\x0b\x99\x97\x0a!\x9c\
|
||||
\x11e\xbc\x8b`\xe8>@\x84\xbc\x10?\xf6\xd9\x112\
|
||||
\x1c\x10@\x08\xe1\xa1C\x87br\x87\xeaB\xe8U\x04\
|
||||
C\x0a \x9d\x1f!\xfe\x80;\x84\x18\xe26\x02T\x85\
|
||||
\xd0\x9b\x08\x16\x02\x88\xf0\xf7m1\x06\x8e\xef\xe8\xd1\xa3\
|
||||
\x14?B\x14p\xa1\xf2s\xcf='\x81Q\x13B/\
|
||||
\x22X\xce\x02D!dB\x0cA\xc1\x03\xee\x8f\x05\x0f\
|
||||
Bt\x81\x18\xce\xce\xce\x86\xae*\xabT\x8d}\x89\xe0\
|
||||
Gb(\x80t\x7f\x84\x84\x01\x8e0p\xce\xd0\xbb\x10\
|
||||
\x8e-\x82\xd6y@\x14>\x8e\x1f?N\xf7GH@\
|
||||
\x0e\x1f>\xdc\x14\xc3@\xec/\x84\xf0\x8cxb,\x11\
|
||||
,\xf7\x118-F@\xfc\x10\x02\x13B\xc2\x037\xf8\
|
||||
\xe6\x9bo\x86p\x85Xb\x87\xb5\xc6\xab\xe2\x81\x91E\
|
||||
\xd02\x0f\x88\xf0\xf7\xe0\xc1\x83\xcd%n\x84\x90\xb8\x08\
|
||||
\xe4\x0a\x1b\xc5\xf1X!\x84\xb7dL\xc6\x11\xc1\xa5\xe2\
|
||||
\xe6%Q\x06\x02\xf8\xe2\x8b/\xb2\xef\x8f\x90\x88\x09\xe4\
|
||||
\x0aW\x0a\x11|V\xc6d\xa4\xf1\xfae\x18\xac.\x80\
|
||||
p~\x14@B\xe2\x07\xe7\xe8\xee\xdd\xbb\x9b9{C\
|
||||
\xa6|l\xe24\xb4\x13,\xd7\x05co\x90\x09Q\xc4\
|
||||
\x09 \x0b \x84\xa4E\x80\xf0\xf8\xd9q6p\x1a\xc5\
|
||||
\x09\xe2\xd9M\x88\x22\x14@B\xd2eaa\xa1\x99\xc3\
|
||||
7<\x7fO\x8f\xb3y\xd3PN\xb0,\x86|$\x8a\
|
||||
0\x07HH\x1e\x18\xe7\x09G\x9e:3\xac\x13\x9c\x13\
|
||||
E(\x80\x84\xe4\x83\xf1\xf9|\xb80iS2\x02\x03\
|
||||
;\xc1\xf2\x07\xbc'J\xc0:#\xb1J\x01\x5c\xdf\xad\
|
||||
\xcc\xedPV}\x0c\xb4\xefP\xd6m\xc7\xb2^W`\
|
||||
\x9fWg\x1f\xdfK\xebu\x1fd_\x98A\xb7-m\
|
||||
\xff\xba\xf6\xadO\xddf[L\xe3l\xc4\xd0\x116d\
|
||||
\x84\xb6\x99aD\x10\x028%J`\x19\x9cqeI\
|
||||
\x1d'N\xd5\xdd\xc3\xbam\xb9\xc8=l\xf3\xa3\xba\xe5\
|
||||
\xaa\xbb\xadn\xbbZ\xfd\x18\x02\x9a\xb3\x010\x14\xc2c\
|
||||
\x85\x08\xce\x0d\xf3\x1f\x06\x12A\xed\x95!H\xa4b\x10\
|
||||
Bj\xe0\x05\x85[\xc3\xa2r\xb7\x0b\x98;(jd\
|
||||
\x14\xaa\xe2\xe8v<t\xfb\xe0\x5c\xbf~]R\xc6P\
|
||||
\x08\x1f\x1af\xe7\xbaAE\x10-1*\xa3\xf2\xf1\x22\
|
||||
\xef\xdc\xb9Sb\x06b699yw\xe3\x1a'z\
|
||||
\x149b\x89\x13\xc6\xea\x91\x9a0\xe2\xbc\xb9x\xf1\xa2\
|
||||
\xf6\xb93T\x13u_\x11\xd4\xcc\x05\xc6\x9a\x07\xc4\x0b\
|
||||
\x84\xad\x0b1>\x08\x93v3\xdd\xf4\x9ad\x00\xce!\
|
||||
LSrG\x0a\xa2\x88\xf3\x0a\x8eP\x99\x81{\x07\x07\
|
||||
\x11A\xb5\x5c`L\x03\x11 |n\x9f\x85H\xa6\xe9\
|
||||
\x12240\x14\x10C\xe4\xd7\xb1\xb9X\xac\x1c8p\
|
||||
\xa09\x9fP\x91\x81\xdd`O\x11\xd4\xec\x0b\xc4\x0b\x85\
|
||||
\xf2yh\xe0\xf8\xd0\xe1N\xe1#\xb9\xe1\x04\x11F#\
|
||||
F\x878??\xaf=\xb1z\xef \x9b\xb9\xf7\x13\xc1\
|
||||
%QZ#\x8c<`\xc80\xd8\x8d\x0c\xa7\xf8\x91:\
|
||||
\x80s\x0d\x05\xc8\xf3\xe7\xcfK, \xfaB~P1\
|
||||
\xdd\xd4(D\xf0\xa1~_\xd4U\x045]`\xc8j\
|
||||
0\x9c\xdf\x89\x13'\x98\xe7#\xb5\x04b\x88P\x19b\
|
||||
\x18C.\x1eEF\xe4\x07\x15\x0b%}s\x83\xbdV\
|
||||
\x8cL\x89\x02\xf8\xc3\x87\xb8\x1a\xb9\xf2\xfc\xd9\xb3g)\
|
||||
\x80\xa4\xb6\xe0\xbd\x8f\xf4\xcf\x95+W\xa2\xd8Z\x13\x15\
|
||||
neC\xd4w\xcaL/'\xa8\xd2\x16\x13\xa2)\x1a\
|
||||
IX\xbc\xe0li!d#\xb1\x84\xc90(\x8a\xa9\
|
||||
\xa9\x9en\xb0\xa3\x08\x16\x02\x08\xf1\xfb\x83x\xc6\xba'\
|
||||
\x10\xa2\x87\x0aT\x04\xdb\x05\x12\x125\xa1\xd7\xed#,\
|
||||
F~P\x89\x9e\x95\xe2n\xe1\xf0aQ\xc02\x0f\xe8\
|
||||
\xc2_\x0a !\xfd\xc1\xf9\xe2B\xe4\x10(\x87\xc5S\
|
||||
e\x8d\xa3#\xdd\x9c\xa0\xf7-4-]`\xc0\x0d`\
|
||||
\x08I\x9eP\xae\x10\x91\x1b\x84X)m\xd5uM\xf1\
|
||||
\xe7\x9c`\xb9BdB<c\xe5\x02)\x80\x84\x8c\x87\
|
||||
;\x87\xa6\xa7\xa7\xc5\x12\xac~Q\xd4\x89C\xdd\x06\xaf\
|
||||
v\x0a\x87\xf7\x88\x02\x16\x1b\xa5\xe3\x0aB\x01$d|\
|
||||
p\x0e\xbd\xfe\xfa\xeb\xe6\xe11\x1a\xbb\x95\x1c(\x04\xb0\
|
||||
c\xa1\xb7\x93\x08>#\x9eA5\xd8\xc2Z\xa3\x03\x9d\
|
||||
\x02H\x88?Bl\xa7\xa9\xe8\x06;\xb6\xcbl\x10\xc1\
|
||||
2y\xe8\xbd-\xc6\xa2%\x06/\x16\xd6\xfd\x12B\xfc\
|
||||
b-\x84\x8a\xa6\xe9\xd1N!q\xbb\x13\x9c\x12\xcf\xe0\
|
||||
\xc9h/\xe4\x86\xfb\x0bU\xd5\x22\xa4\x0eX\x0b\xe1\xe9\
|
||||
\xd3*\xe3K!\x80\xfb\xdb\x1fl\x17A\xef\xf9@\x0b\
|
||||
\x17h0\x96\x87\x90\xda\x03!\xb4j9\x83n(m\
|
||||
S\xf0\xad\xf6\x07\xdaE\xd0{>P\xbb\x13\x1d\x83\x10\
|
||||
\x98\x07$\xc4\x06,>@c\xb36\x10@%7\xf8\
|
||||
\xb9\x90\xf8\xae\x08\x96\xabDF\xde\xbb\xb3\x13n<\xb8\
|
||||
&\xb8:\x11Bl@\x07\xc6\xc9\x93'M\x96\xa0*\
|
||||
\xcd\x1a\xfd\x5c\x95\xb8\xea\x04\xa7\xc43\xdam1t\x81\
|
||||
\x84\xd8c\x95\x83wS\xb3\x15\xd8\x90\xf6S\x15\xc1K\
|
||||
\x97.\x89&t\x81\x84\x84\x01CI0\x96N\x9b\xe5\
|
||||
\xe5eQ`C^\xb0*\x82\xdb\xc5#Pq\xcd\xaa\
|
||||
0] !a\xc1\x5cN\xed\xb0X\xa9@2Q]\
|
||||
K\xdc\x14\xc12Q\xe8\xb5?\x10\xf9@M8\x18\x81\
|
||||
\x90\xb0\xc0\x84\xcc\xcc\xcc\x88&n\x87=\x05\xa6\xdc\x1d\
|
||||
\xe7\x04\xbd7Hk\x86\xc2\xf8\xe3s,>\xc9\x0dw\
|
||||
\xc2\xbb\x9d\xe3,\x0a\x8b\xe3\x82\xb0X\xdb\x0d*\x85\xc4\
|
||||
w;a\xee)o\xbd\x8b\xa0\xa6\x13\xb4\xc8E\x10b\
|
||||
\x81k\x05\xe9\xb7J\x02m)\xd5\xad`c1\x01\xf8\
|
||||
\x9d\xe0\x065\x07\xa4\xe0o\xa3\xb03\xdd\x94\xbb\xd3\x1c\
|
||||
\xa5U\x84\xc3\x0b\xc5\x8d\xd7r\xcf#\x8f<\xa2\xd5\xec\
|
||||
\xa8=\x856N\xfe\xfa\xf1\xc6\x8f\xef\xff\x92d\xc1g\
|
||||
\xc5{\xe4\xb3\xb5\xf5\x8fsy^\x03\x00\xa3p\xf0\xe0\
|
||||
\xc1\x91\xdc\x1e\xc4\x07\xcbD\x91\x1b\x0f}.\xe0<\xc7\
|
||||
\x98<\xad\xf3\x1d`\xe0\xaaB\x7f\xe2\x177m\xdat\
|
||||
\xcb9\xc1G\xc4#xq5\xff \xd9\x0a \x84\xee\
|
||||
\xdaoDn\xfcY\xe4\x93\xe2\xfe\x8d?\xb5\x04\xe2\xb3\
|
||||
.\x7f\xcb/\x14a\xc8\x03_n\x09\xc7\xf6\x87\x0b\xbb\
|
||||
\xf0\xb5\xe2\xf6+\xad\xc7c\x03\xcf\x09\xcf\xed\xff\xfe\xd4\
|
||||
\xba\xff\xf7\xb5\xcf\x0b{\x15<\xa7\x07\xbe\xd4z>\xb8\
|
||||
\xdf|n\x0fK.@\xf8F\x15@\x80\xf3\x0b\x0e\x09\
|
||||
\x87kY\x09\x95'w{v+577Az@\
|
||||
A\x04\x11\x01\xaf8'\xf87\xf1\xd8(\x8d| ^\
|
||||
`\x0d\x8cv\xaf\xb7\xe3\xfaoE>\xf8e\xeb\xe8%\
|
||||
\x0a\xc30\xf9\x94\xc8\xd3/\x14\xc2\xf1TXg\xe5\x9e\
|
||||
\xdb\xfbg\xbb\x0b\xf90@\xdc'\x0b1|\xfc\x9b\xad\
|
||||
#F\xb1\x1f\x10\x8d\xbdvpn\x84\x9a\xa4\xa4\xbd\x8f\
|
||||
8f\x1bb\xb4\x97g\x8e\x14Np\xe1\x9e\xb22\xec\
|
||||
u\xa5\x88f\x93t\x16\xf9@\x08\xc2\xbb\xc5\x0b\xfa\xcb\
|
||||
\x93~\xc4\xa1\x1d\x88\x0f\x0e\xf0\xf4\xf3\x22\xfb\x8e\xda\x89\
|
||||
\xa1\xe6s\xc3\xf7\xfb\xfd/[\xc7\x1b\xf7\xb6\x84\xd0\xf2\
|
||||
\xb9yDcM=\xce\xbb\xdd\xbbw7]!\x0a\x16\
|
||||
\x96@\x80qnj\xb5\xc5)}\xdff\x04\x8c\xea\xf0\
|
||||
\x84x\xe6\xe6\xcd\x9b\xa2\x85\xc5\xbaE5\xe0\xf4N~\
|
||||
_\xe4\xbb\xff\x22r~^G\x00\xdby\xff\xad\x22\xdb\
|
||||
\xfbx\xeb\xe7\xfar\x9a\x9d\xc0s\xf9\xc5\x89\xe2g}\
|
||||
\xd5\xe6\xb9\xe1\xfb\xbb\xe7\xf6\xea\xdeu\xd1O\x00\xcd\xa2\
|
||||
!\xc2\xe4\xe3\xc7\x8f\x07\xd9\xd7[3M\x85\xe7\xa5P\
|
||||
)o\x16\x84!\x82^] \xd0,\xeb')\x82N\
|
||||
\xfe\xe3\xeb\xad\x137\x04N0\xf0{\xf8\x06\x02\xf4\
|
||||
\x9f_\xb7\x13\xf6N?\x1fB\xa8-\xf4\x9e\xd0\xcc\x97\
|
||||
;\xb0\x8d\xa6\xb5\x10j\xe7$\x15\x22\xcc\x09\xfc\xa3\xe2\
|
||||
\x04\xb5\xaetH\xc0&\xb7J$\xb4@\xb4\x83\xdf\xe3\
|
||||
\xf0\xe3~\xc4\x02\xcf\xe7\x8d\xd9\x96\x00}\x12\x81\xf8h\
|
||||
\x0a}\x82@\x08\x95\x86\x10t\x04\xe7\xa6\xe6\xf9y\xfd\
|
||||
\xfau\xf1\xcc}H\x07z\x17A\xcd\xab\x5cR.0\
|
||||
6\x81\xa8\x82\xdf\x07b\xf1\xee\xcfed \xa2xn\
|
||||
\xefzOV\x8f\x8fO\xa1O\x1c\x84\xc6\xda\xab\xb7\xaa\
|
||||
h\xe6\xec?\xfeX\xe5\xf5\x9c\xf0\x1e\x0ek\xfe\xc1\xb7\
|
||||
l\xd9\x22I\x10\xb3@Ty\xe3\x07\xa3\xb9&\xb4\xee\
|
||||
\xb4\xc7m\xac|R\xbe\x06(\xa2\xd4\x1cT\xa2\xad\
|
||||
\xd0\xcc\x0b*8A\xf0\xa8J8\xacE\x12N\xd0\x09\
|
||||
`\xcc\x02Q\x05\xae\xe9\x8d!\xba\xf1\xaf\xbc\xd5z~\
|
||||
1\x84\xf6\xfd\x80\x10\xfe\xf7\xfe\xda\x87\xc70&Va\
|
||||
\xb1vqD\x81f8\xbcU<\xa2Y\x14\xb1\x18\xe4\
|
||||
8\x16\xce!\xc5\x16\xfe\xf6\x03\x8eu\x10!\x84\x00\xfe\
|
||||
\xec\xfbi\x08`\x15\x08}\xcd\x85\x10E\x12\x8b\x82\x0c\
|
||||
r\x82Z\xe7\xa9R\x85xb\xb3xFS\x04\xa3.\
|
||||
\x8a8\x07\x98\x9a@8 \x84\xbd\x84\xe2\xf7\xef\xb6\x04\
|
||||
0Uj.\x84\x10\x10\xed\xf9\x9e\x0e\xcd\xf3TA\xc8\
|
||||
\xb7B\x04\x1f\x92D\x88\xd6\x09\xa6.\x80\x0e\x08E\xa7\
|
||||
b\x09\x9e\xdf\xcf\xfeM\x92\xa7\xdb\xf3\xab\x09\xda\xfb\xfd\
|
||||
8&''E\x0b\x85\x9a\xc3\x17\x93r\x82Q\x8a \
|
||||
\x84/\xc6\x0a\xf0\xa8\xa0X\x82\xb5\xbd\x8e\x5c\x04\xde\x81\
|
||||
\xe7\x97Pc\xb5O\xd0gg\x15\x12'\x84\xff\x9c\xa0\
|
||||
&Q\x8a N\xaa\x5c\x04\xd0\xf1\xdaK\xeb\xa2\x97\x93\
|
||||
\xc0;P,\xa9i\xfb\x8c\xc5\x16\xb8\x9a\x22\xa8`\xb2\
|
||||
\xb6\xab\xac\x18\xa9\x0d\xc8\xa3\x85Z\x01\xa2\x09D\xaf\x99\
|
||||
C\x9b\xcfO\x00\x01&\xf3@\x08k\x88E\xcf\xa0\xa6\
|
||||
Y\xb9}\xfb\xb6\xf8\xc6{8\xac\xf1KF\xc9_K\
|
||||
\xa1\xc8\x15\x08\xfc\xf9\x8c\x0b\x09\xa8\xe4\xe7\xfc\xfc\xba\xa0\
|
||||
\xb9\xef\x8fCS\x04\xd7\xd6\xd6\xc47\xdeEP\xe3\x97\
|
||||
tD\x95k\x88e\x19\x1c\x19\x1d8\xdd\x9a\x85\xc5\x16\
|
||||
\xe3\xfa\x13\xcb\x09\xfao\x91\xa9\x05\xcd\x19y\x19\x86\xc1\
|
||||
u$\xe5\xb6\x9f\x11\x89}\xdf\x12k(\x82\xa3\xf0\xbf\
|
||||
\xde\xf7; \xa1\xa8\xce^\xac\x09\x14\xc1\x8dP\x04\x87\
|
||||
\x05\x0e0\xc7bA\x9d\xc99\xb7\xdb\x01\x8b6\x19-\
|
||||
4f\x95&%\x82Q\x5c\xc1\xde=)$3j\xe6\
|
||||
\x06S\x16A\x0d\xe8\x04\x87\xe1\xfao66\x12\x93|\
|
||||
\xa8\x99\x1b$\xebP\x04\x87\x81\xc5\x90|\x81\x13\xe4\x05\
|
||||
\xae\x96x\x17\xc1\xad[\x93Y\x802<\x1fp6]\
|
||||
\xd6\xd4\xe4\xf5Mn:\xbb2\xdeE0\x99\xc1\xa7\xc3\
|
||||
\x82\x13\x84}\x81y\x13\xfb\x10\x5c\xa2\x02\xc3\xe1A\xe1\
|
||||
\x84\xe2\xfc\xc1r\xba\x1a\x14HRv\x82\xdb\xb6m\x13\
|
||||
\xdfP\x04\x07\xa5\xa6\x93Gj\xc75\x8a`\xdd\xa0\x08\
|
||||
\x0e\x02\xd6\x99\xb27\xb0\x1e\xa0\x03 c,\xb6\xa8H\
|
||||
\xac\x05\xe7\x16D\xf0\x86\x90\xde\xb0jX\x1f2\x7f\xad\
|
||||
5\x07\x9e:4EP\xa1\xf0z\x8bNp\x10n\xfc\
|
||||
QHM@^0c\xd7\xbfk\xd7.\xd1FS\x04\
|
||||
5\x0a\xaf\x14\xc1A\xa0\x13\xac\x17\x19\x87\xc4\x9a\xbb\xc1\
|
||||
94g\x16*\xe43o$%\x82\xc1r\x0d\xcc\x07\
|
||||
\xd6\x8bL_\xef\xe9\xe9i\x93\xe9\xecJ\xfb\x037\xd1\
|
||||
\xf8\xfd)\x82\x83P\xd3Q\xec\xb5%\xd3\xd7\xfb\xb9\xe7\
|
||||
\x9e\x13\x0b4g\x8a*\x88\xe0G\xdeE0\xbb\xf2\xfb\
|
||||
gz/(\x89\x94\xbf\xe7\xd7\x14\x8f\xf3\xd2\x22\x1f\x08\
|
||||
4\xa7W+T\xb7\xd3\x0a\x87\x83\x90\xe1\x09A\xfa\x90\
|
||||
\xe1\x85\xef\xd0\xa1Cb\x01v\xb4\xd3\x02.P\xc1\x09\
|
||||
\xb2:\xdc\x17:\xc1\xfa\xf1\xf7\xbc^\xf3}\xfb\xf6\x99\
|
||||
\x85\xc2\x9aE\x11\xa5\x1e\xc7U\x88\xe0G\x92\x08Ar\
|
||||
\x82\x14\xc1\xfa\xf1\xff\xf9\xb8\x7f\x84\xc1\x87\x0f\x1f\x16+\
|
||||
.]\xba$Z(\x89`ZN0\x88\x08~!\xe3\
|
||||
\xa98\xa43\xff\x14\xe1\xfe\xd6#\x80\xd0\xf1\xe4\xc9\x93\
|
||||
fyz\x9c\x9f\x9a\xf9@\x8du\xc3\x9b6mZe\
|
||||
a\xa4\x1f\x14\xc1\xfa\xf1\xcf\xba\xaf\xb9E\x9b\x0a~\xc6\
|
||||
\xfc\xfc\xbc\xc929\x87\xa6\x0b\x04\x0a\xcfe\x15\xff\xdc\
|
||||
#\x84\x10S\xb4E\x10\xdf\xff\xcd7\xdf4\x15@\xb0\
|
||||
\xbc\xbc,\x9a(4z7s]I\xad\x1d\x0e\xb2\xc7\
|
||||
\xc8\x03_\x12R3\x94\xdd?\xa2%\xad\x88\xe9\x89'\
|
||||
\x9e\x90\x8b\x17/\x9a\x0b \xceMM'\xa8\xb4\xd2e\
|
||||
\x05\xff\xb0:<\x08_\xc8#GD\x06\xc4\xe0\xc27\
|
||||
33#>\x81\xfb\x9b\x9d\x9d\x95\xb3g\xcf\x06II\
|
||||
i\xb6\xc6\x00\xad\xca0\xfe\xf1\x1e\x0eg9\xab\x0c\xce\
|
||||
\x80S\xa5\xeb\x83\xc1E\xef\xc0\x81\x03\xcd\xe5e\xe7\xce\
|
||||
\x9d\x93q\x80\xf8AP\xf1\xfd,r\x8d\xddX\x5c\x5c\
|
||||
\x14M\xb0\xe4O\x81\x06\xfe\x81\x08\xde\x92D\x08\xb6\xe5\
|
||||
\xe6\xc4W\xb8t\xaeNl\xff\x8aX\x80\xc2\x05*\x9e\
|
||||
\xc3\x0a\x08\x8c\x06D\x01+@,\x06\x22\xf4\x03B\xae\
|
||||
ynB\xdc\x15\x9e\xe7-T\x86q\xc7\xbb\x08\x86\xbc\
|
||||
\x1a\xa9q?'\xf1\xd6\x8a\x07\xbe,V\xa0\x87\x0f\x8d\
|
||||
\xcc\x08'\x91Sko\x03\x83\xe0\xe1\x9cryD\x88\
|
||||
Al\xe7\x98\xb6\x0bD\x9eS\x81\xcb\xee\x8e\xf7pX\
|
||||
\xf3\x05\x0a6@\xc1\xc8\x19\x90H\xd8\xfe\xb0X\x02q\
|
||||
\x83\x10Z\xad\xea\xf0\x89\xb6\x0b\x04Jk\x9eW\xdc\x1d\
|
||||
\x14F\x92\x09\x87o\xdf\xbe-A\x98\xfc\x9a\x90\x9a\xf0\
|
||||
e[\x01L\x1dm\x17\x08\x94B\xfeUw\xc7\xbb\x08\
|
||||
j\x16F\x829AT\x0bY!\xae\x07;x\xc1\x1b\
|
||||
\x94\x85\x85\x05u\x17\x88\xdc\xa7\x82\xa6 \x1f\xb8\xe2>\
|
||||
\xe0\xb2\xb9AaH\x5c\x0f&\x9f\x12\xd2\x1f\x88\x9f\x85\
|
||||
\x0bT\x0a\x85/W?\x80\x086\xc43Zy\xc1\xa0\
|
||||
\x22\xf8\xd5o\x0a\xa9\x01\xbc\xd8\x0d\xc4\x8b/\xbe(\xda\
|
||||
\xb8\x5c\xa9\x02\x17\xaa\x1f\xa88\xc1,E\xf0q\x8a`\
|
||||
\xf6 \x1f\xc8\x15B}\xb1\x08\x83\x81RU\x18\xacT\
|
||||
?H\xca\x09\x82`\xbd\x8289\xee\xe7\x09\x925\xcf\
|
||||
\xbc \xa47h\xe5\xb1\x08\x83\x81\xd2\x08\xb0\xcbE>\
|
||||
\xb0Q}@\xc5\x09jl\x8b\x17\x05O?/$c\
|
||||
\x98\xf2\xe8\x09\x0c\xc8\xd1\xa3G\xc5\x02\x0c\x82U*\xb2\
|
||||
.\xb5?\xb0\xb9]\x15}\xa0Y!\x0e\xe6\x04\xc1\x0e\
|
||||
&\xcd\xb3\x85\xa1pO\x90\x8aB\x1e\xd0\xea\xfcS\xec\
|
||||
\x99\x5ci\x7f \xb9\x01\x0aAE\x10\xfd\x82\xac\x1e\xe6\
|
||||
\xc97_\x16\xd2\x1dK\x01D_\xa0Ro\xe0\xe5N\
|
||||
\xa6\xcf\x89\xa0\xd7qZY\xf6\x0a:\x182\xe5\x09\x1b\
|
||||
\xe2\xbb\x82\x10Xs\xef\x90v\x147\x85Z\xea\xf4\xa0\
|
||||
\x13\xc1\xbf\x89G\xb2\x5c:\xe7x\xe6y6N\xe7\xc6\
|
||||
\xce\xe7\x19\x0aw\x01\x028\xee\xa4\x9ba@.P\xc9\
|
||||
\x05bQ\xc8\x85N\x9f\xd8\x5c\xf9\x02odY\x1dv\
|
||||
`\xac\xd67\x18:e\xc5>\x9bd\x7fJ\xb8\x1c\xa0\
|
||||
\xa5\x00\x02\xc5M\xa1.\x14\xa1pG\x9dc8<\x0a\
|
||||
O\xb3\x95\x22\x1b\x90\xe3\xa5\x0b\xdc\x00\x8c\x06\x04P{\
|
||||
Pj;\x8a\x15a\xd0\xb5\xafG\xc5\x09j\x8a \x06\
|
||||
Q\x06\x07'\xcdN\xb6\xcbd\xc1\xb7\xe9\x02\xab@\xf8\
|
||||
\x80\x969@\xa0\xbc5\xe8e7;\xb0\x13\xc9\x85\
|
||||
\xc3Q8A\xc0\x10*}\xe0\x02\xd9\xf6t\x97S\xa7\
|
||||
N\x99V\x81\xab\xa0\x18\xa2h\x9e\x96z}\xd2\x89`\
|
||||
C<\x02\x11\xd4\x5c:\x17\x85\x10\xd2\x0d\xa6\xcf\xc1\x9f\
|
||||
\x0aY\x0f\x7f\x8f\x1f?.!P\x5c#\x0c\x1a\x85\x0b\
|
||||
\x5c\xea\xf5\x05*N\x10d]\x1cq\xfc\xebqV\x8a\
|
||||
S\x85\x15\xe1&p\x7f\xbbw\xef6\xcf\xff9\xdc\xf6\
|
||||
\xa0\x8a\x1c\xeb\xf7\x05N\x04W\xc53\x9a[\xfeY\xe7\
|
||||
+\xba\xc2Jq\xba\xd4<\x9d\x01\xd1\x83\xf8\xc1\xfd\x85\
|
||||
\x8c\xac\x94\xc3\xe0\xbe.\x10\xb8\xf1\xfa\xde\x9d\xa0\xe6\xfa\
|
||||
\xe1h\xf2\x82\x00+\x0d\xde\x7f\x8b\x1b1\xa5\x04\x8a!\
|
||||
5u\x81n\x00B(\xe7W\x05\xd5`\xec\x92\xa7H\
|
||||
_\x17\x08\x9aN\xb0\xec\x9fI\xa6B\x1c\x8d\x13\x04p\
|
||||
\x83\xdf\xb3\x99\xaaA<\x80I@\xfb^\x91\xba\xe1\xaa\
|
||||
\xbe!Z_:\xa1\x5c\x0d\x06\x03\xb9@P\xddhi\
|
||||
\xad8\xee\x13Oh\x8a\xe0\xcd\x9b7%*\xb0\xe4\x0a\
|
||||
9\xa6+o\x09\x89\x9c\x1f\xbc-u\x01\xb9\xf3\xf3\xe7\
|
||||
\xcf7\xf3~QEO\x05\xc8\x03*\xefQ>\x90\x0b\
|
||||
\x04U\x11D^p\xbbxB3'\x18Ma\xa4\x0a\
|
||||
\x8a$\xd7\x7f\xcb\xb08fj\x10\x06\xc3\xe5]\xbdz\
|
||||
\xb5y\x1b\x83\xe3\xeb\xc4\xec\xec\xac\xb6\x00\x0e\xec\x02A\
|
||||
U\x04\x93Z:\x87+[T\xfb\xaf\xba\xb0\xf8\xd5o\
|
||||
\x0b\x89\x90\x0c\xc3`\x9c\x07H\x0dA\xf4p\x1f\xa2\x17\
|
||||
\x9b\xe3k\x07!\xb0r\x1e\x10\x1c\x19\xe6\x8b\xdb\x9d\xe0\
|
||||
K\xe2\x09\xb7i\xb4\xd6\x8b\x82\x17]\xd3m\x8e\x04\xc2\
|
||||
\xe2o|W\xe4\xdd\x9f\x0b\x89\x08\xb41E\x14\x06C\
|
||||
\xb8\x10\xa2\x0e\x0b\xd2@\xaeO6\xcah\xa8\x0f\xe8\x05\
|
||||
T\x9c\x10\xe3X*\x5c\xe0\x85a\xfe\x83\x9a\x13\x04\x10\
|
||||
B\xad\x22\x06\xaez\xd1\x89 @\xeb\x05\xc2\xe2\x1b\x7f\
|
||||
\x16\x12\x09\xfb\xe2\x0a\x83\xf1\xdeE\xae\xaeN`\xbf\x90\
|
||||
\xf9\xf9yQ\x06\x1a6p.\xd0Q\x1d\xaa\xea\xbdW\
|
||||
prrR\xb4\x88\xae8\xe2@X|\xe4\x0c\x9b\xa8\
|
||||
c\x01}\x9c\x91\xf5r^\xbatI\xea\x04\xcc\xca\xeb\
|
||||
\xaf\xbf.\x06,\x8e2)\xbf*\x82\x0d\xf1\x8c\xa6S\
|
||||
\x8b5\xe9\xdb\x04\xae\x83K\xb2\xc2\x83\xed3\xbf\x13f\
|
||||
)X/\xa2\x18\x02b\x044\x00\x95`\x83\xfc=\x8a\
|
||||
!s2\x02wE\xb0\xec\x15Lf\xa4V\xf49\x11\
|
||||
l\xd1\xf9\xed\xfa\xf5\xa3E\x03\x0a!G\x96$6\x90\
|
||||
\x1e\x8a\xbdx\xe1\x0b\x84\xc0F\x02\x08\x9e\x95\x11i\xdf\
|
||||
c\xc4kH\xac\xe9\x04\xf1F\x8a\xaai\xba\x13\xc8E\
|
||||
q\x87:{\x5c!$\xc2v\x98\xa8#\x18\x8f\xa0\x08\
|
||||
r\xf6\xecY+\x01<6\xce\x86q\xed\x22\xd8\x10\x8f\
|
||||
\xb8\x0a\xb1\x16\xd1\x8b @8\xb6\xfda!\x86|\xef\
|
||||
\xa7\xd1\xf6\x03\xa2\x9d%w\xd0\x06cP\x04q\xac\x8e\
|
||||
\x1a\x06;TE\x10h\x86\xc4I\x5cUQ(\x81+\
|
||||
\xe1\xc6\xed6\xe0\xa2\x13\xf1fX\xb9;A4B\x1b\
|
||||
\xb4\xc18\x1a\xc5\xb1W\xc6D5\x1c\x06\xc8\x0bh\x91\
|
||||
\xccU\x95Bh\x03V\x84D<\xd5'\xe7| \xcc\
|
||||
\xce\xc5\x8b\x17-\x1a\xa1\xab\x8c\x15\x06;\xd4EP{\
|
||||
\xf9\x5c2o*\x84g\x14B= \x80\x91\xaf\x08I\
|
||||
\xb1\xc1y\x10\x5c\x01\xc4\xb8o\xf7\xd80K\xe3z\xb1\
|
||||
A\x045*\xc4\xda\x7f\x98\xa4\xc2\x0b\x0a\xa1\x0e\x09\x08\
|
||||
X^^\x96\x9c@\xbe\x1f\xe1/\x0a \xcak\x81\
|
||||
\xdb\xb9<n\x1e\xb0\xca\xe6\x0e\x8fy\xaf\x10k\x16G\
|
||||
\x92K4S\x08\xfd\x92\x88\x00\x82$\x0ay\x03\x02\xf7\
|
||||
\x17 \xfc\x05\x8d\xe2\xd8/\x1eQ\x17A\xa0\xb9r$\
|
||||
\xc9\xee{\x0a\xa1\x1f\x12\x12@7\xec u\x02\xba?\
|
||||
\x80H\xf5Y\x1fy\xc0*&\x22\xc8\xbc`\x07(\x84\
|
||||
\xe3\x91\x90\x00\x82\x1c\xaa\xc2\x98\x04\x1d\xc8\xfd9\xbc\x0b\
|
||||
\xe8$\x82+\xe2\x99'\x9f|R4Iv-&\
|
||||
\x85p4\xb0$1\xb1\xb1X)\xf7\x07\xba\xc2\xc7\x89\
|
||||
\x13'B\xb8?\xc7L\xaf\xbd\x83\xc7\xe1s\x22\xa8Q\
|
||||
\x1c\xa1\x08\xf6\xc0\x09!\x1b\xaa\xfb\x83\x95 \x10\xc0\x04\
|
||||
W\xe1\xa4\xe8\x04\x9d\xf8!\xf4\xd5>\x87\xfb\xe0\xad\x12\
|
||||
\xdc\x89\xcd]\x1e_\x11\x8f \x8f\xa0\x19\x12\xe3*\x9b\
|
||||
t\xff\x95\x13B\xcc\x22$\x9d\xb9\xbf\xfc\x1b%*\x80\
|
||||
)\xb5\xc7D$~\xe0\x98\xcfJp'\xba\x89`R\
|
||||
M\xd3I\xac#\xee\x07\x1a\xaa\xbf\xf3*\x87.t\xc2\
|
||||
\x09 \xa6\xc2$H\x0a\xb3\x03aT\xb0\xd2\xe3\xca\x95\
|
||||
+\xb1\x88\x1fP\x17@`&\x82\xbbv\xed\x12M\xb2\
|
||||
\xe9\xc1\xc2\xd0\x85\x83\x8b\x9cG\xe8\x98|J\xe4'\xbf\
|
||||
Nzo\x90XCa\x08\x9fs}\x10?\xac\xf9\x0d\
|
||||
\x98\xf3k\xc7D\x00\xc1\xa6n\x9f\xb8s\xe7\xce\xdf\xc4\
|
||||
\xe3\xeespk;w\xeeT\x0b[\xf1\x82~\xf8\xe1\
|
||||
\x87\x92\x0d\x9f|,\xf2\xea\xdezo\xdc\x94X\x05\xb8\
|
||||
\x13n\xab\xcbX\xc0y2==\xddtz0&Q\
|
||||
\xed\xd3\xb3\x8e\x99\x00\x82{z|\x0enpJ<\x81\
|
||||
?6\xfa\x05\xb5\xaad\x10W\xbc\xe1\x22\xb1\xf1\xe3\x03\
|
||||
\xe7\xf3\x93_\x89\xfc\xcfl\xfd\xb6\xf2\x84\x0b\xc6$\x98\
|
||||
\x88\x07!\x0cJ\xe8P\xd8\x9dw\x10<\xe4\xe5\x138\
|
||||
?f4\x8b \x9d\xe8%\x82\xef\x88G\x11\x04x!\
|
||||
4[\x05\xce\x9d;\x97\x8f\x08\x82\xe6\x0ev\x85\x18L\
|
||||
\x14\xb9\xb07f\xa5\x16 \xef\x87a\xa8\x19l\x8d\x89\
|
||||
b\x08\xde\x93\x16@\xec\x9c\xe0!\xa4u\x82\x17Qx\
|
||||
\xdb\x0ft\xa5\xec-\x04pE\x8c\xe9\x15\x0eO\x157\
|
||||
\xef\x89G\xf0\xa6@H\xac\x05\xde\x04\xc8mDj\xf1\
|
||||
\xc7\xa3\x0e\xe11&\xc0 '\x9aI>\x14\x02x\xf4\
|
||||
\xe8Q\xd1\x02\x02\xe7&7'\xfe\x9eo\x88R#\xf4\
|
||||
t+\x8cH\xa9\xc8^w\xa0\xc3\x8b\xa6]%\xb6\
|
||||
\xba\xf2\x9a\xe3\xc2\xe3\x1c\xdbh z\xff\xbe\xd4\x9a\x05\
|
||||
\x98QAhqqQ4\xc1\xb9\xa4=\xb8\xd8\x80\xcb\
|
||||
\x12P\x00\xc1\xe6>\x9f\xbf,\x9e\xd1\x0eWs\x9b\xd4\
|
||||
\xb1\x01\xd7F\x83\xeaq.\xabLP\xfd\xfd\xf1\xaf\xb3\
|
||||
\xc8\xffU\xb1\xe8\x0d\xc4\x08\xfb\xc4\xc1\xeepS!\x05\
|
||||
\x10\xf4\x13\xc1\x15\xf1\x8c\xb6\x08\xe2\xcd\x97\xfd>\x0eO\
|
||||
\xbf\xd0\xea\x9b\xdb\x99\xf0\xfe%p|p~\x91\xee\x05\
|
||||
2.\xda\x05\x118\xc0\x84\xf3\xdf\x880Q\x009,\
|
||||
\x11\xd0O\x04\x97\xc43\x16\xc9\xda\xac\xdd\xa0\x03\xc2\x81\
|
||||
\xa2I\x8a=\x85\xce\xfdE<\x05z\x1c,\x0a\x22\x9a\
|
||||
i%e\x10]>f]\x01\xeeEO\x11,\xd7\x11\
|
||||
{o\x9c\xc64\x0aM\xf0\x06\xac\xcb\xb6\x86MW\x08\
|
||||
A\x81\xb0\xc4N\xe6\xee\xcf\xa1\x9d\x0b\x04\x89\x86\xc2\xc7\
|
||||
b\x08\x7f\xdb\xe9\xe7\x04\xc1;\xe2\x19\xed\x17\x10\x02x\
|
||||
\xfa\xf4i\xa9\x0dn\xedq\xcc\xb9\xc2\xcc\xdd\x9f\xc3\xc2\
|
||||
\x05&\x18\x0a\xc3H=f\xd9\x00=\x0c\x83\x88\xe0\x8a\
|
||||
xF\xbbJ\x0cN\x9d:%\xb5#\xc6\x5caM\xdc\
|
||||
\x9f\x83.p\x03\x88$\xe1\xfe\x1e\xd3\x1a\x83\xe5\x83\xbe\
|
||||
\x22\xa8\xd1*\x03\xb4\xd7\x12g\xdd.\xd3\x8bj\xae0\
|
||||
\xb4+\xac\x89\xfbsX5Gk\xa7\x93<\xe1r\x7f\
|
||||
s\x129\x838ApF<\x83\xab\x99v\x7fS\x0a\
|
||||
\xd3;\xd4\x08\xe9\x0ak\xe6\xfe\x1c\x16.\x10\x02\x18\xf9\
|
||||
*\x90\x86\xb4\xfa\xfe\xa2\xcb\xfducP\x11\xbc \x9e\
|
||||
\x81\x00j_\xd1j\xd1.\xd3\x0b\xe7\x0a\x17>\xb0s\
|
||||
\x85p}\x8b\xbf\xaf\x8d\xfbsX\xb9\xc0\x88CaD\
|
||||
\x8bG\x0a\xe1{(\xc4\xd2\xb7q\x18H\x04S\x0d\x89\
|
||||
\x81\xc5\xd59z \x86\x8b\x1f\xb4f\x15j\xb5\xd3`\
|
||||
\xcd\xef\x7f\xfd\x22\xbbU\x1f\x83b\xf1>\x8b\xb4 \xd2\
|
||||
\xcc\xfb\x15\x07\xc4oA\x12dP'\x08\xbc\xbf\xcax\
|
||||
A\xb5\x0b$\xb5w\x83U\xb0.\x179:\x9f!2\
|
||||
\x04\x0f\xe2\xfa\xe3_\x89\xec\xf8\x9a\xd4\x118@\x0b\x17\
|
||||
\x88\xa1\xa7\x11Q\x15\xbf\xb9\xb2\x9d.I6\x0d\xfa\x85\
|
||||
\x1a\x03\x15\x80\xc5\xbc5\x5cA1X\x81Tx\xffl\
|
||||
\x914=1\xde@\x06\x88)\x84\xb5Fy\xbfN`\
|
||||
(\x88\xf6\x12\xb9\x88\xde\xc3(x,\x14\xa2\xe7=E\
|
||||
\x16\x8a\x81\x9d`\x19\x12\xaf\x88g,V\x90\xe0\x0dZ\
|
||||
\xcb\x96\x99^\xa0p\x82\x10\x19!\xec0\xce\x10\xce\x0f\
|
||||
\xf9>\xfc?\xe4\x1bk.\x80x_Y\xec\x1f\x12\xd8\
|
||||
\x05\xc2\xe5!\x12t\x05\x8fl\x04\x10\x0c\xec\x04A\xe1\
|
||||
\x06\xb1\xd6\xef5\xf1\xcc\xc2\xc2\x82zN%\xeb1[\
|
||||
>\xf8\xec\xd3\xa2\xae\xf7G\x91\xeb\xbfm\xb9\xc3OJ\
|
||||
\x87\x08\x91\xc3\xe0\x86\xfb\x1fl\x85\xbb\x89\xee\xf3\xa1\x01\
|
||||
\xc4\x0fQ\x8c\x85\x0b\xc4\xc8\xac\x00Ua\xb8>\x08\xde\
|
||||
R\xca\xe1n?\x86\x15A\x8c\xdb\xffH<\x8e\xdd\x07\
|
||||
\xda\xa3\xf7\x1d\xd8C!\xb2\xbc\x0aI\x18\xcc\x0a\xb4\xc8\
|
||||
\x05\x1a\xbfok!|U\x86)\x8c\xb8\xb5\xc4\xde\xc7\
|
||||
k\xc1\x9d\xcd\xcc\xcc\x886p\x9c)m}H\xe2\xc5\
|
||||
\xaa\x18\x02\xf7\xa7\xdcJ\x86s\x1a}\xc08\x01\xbfX\
|
||||
\x86\xbb\x0bu\x11@0\x94\x08\x96\xa8\x94\xc1\x0f\x1c8\
|
||||
`\x12\xaajN\xfa%\xf5\x00\x17R\xab\xd6+\xf4\x05\
|
||||
*\x85\xc1\x0diUv!|\xfb1\xd5\xa5N\xc2W\
|
||||
eh\x11\xd4*\x90X\xb9AT\xa3Y$!\xe3\x00\
|
||||
\x01\xb4\x88( ~\x8aa\xf0\x99TVth3\x8a\
|
||||
\x13\x04\xde'\xcb\x00+7h\xf5&&\xf9\x81\x94\x8a\
|
||||
\xd5\x9at\xe5<\xe0\x92\x90&\xa3\x8a\xe0\x92(\xac \
|
||||
\x81\x00Z$\x80Q\x80aXL\x86\xc52\x0c\x86\x0b\
|
||||
T\x5c\x22\xb7D\x17\xb8\xceH\x22X\xe6\x0eT\xde\x0d\
|
||||
p\x83\x16\xad\x00\x0c\x8b\xc90\xb8v\x18+\x94\xcd\xc0\
|
||||
1!w\x19\xd5\x09\x02\x14HT\x12\xa9\xf3\xf3\xf3b\
|
||||
\xc1\xf1\xe3\xc7\xe5\xda\xb5kBH?\xf0^\xb1J\xa1\
|
||||
\xa0\x1aL\x17h\xc7\xc8\x22X\xbaA\xef#\xb6\x80\xc5\
|
||||
\x9ab\xc7\xc1\x83\x07\x99\x1f$=A\x1e\xf0\xd2\xa5K\
|
||||
b\x05\xfa\x02\x15\xa1\x0blc\x1c'\x08\xd4\xa6F\x9c\
|
||||
8qB,\x80\x002?H\xba\x81\x94\x89\xe5$\x22\
|
||||
\x08\xa0b:\x88.\xb0\x03c\x89`\xf9\x07Uq\x83\
|
||||
\xca\xed\x01\x1b@~\x10\xe1\x0e!U\x90*\xb1|_\
|
||||
\x18\xbc\xe7\xe9\x02;0\xae\x13\x04s\xa2\x84U\x91\x04\
|
||||
X_\xf1I\xdcX\x17B\x80vK\x0c]`g\xc6\
|
||||
\x16AM7\x88\x96\x19\xab\x22\x09@\xee\x87\x15c\xe2\
|
||||
\x04\xd0r\xdbV\xe5b\x08\xa0\x0b\xec\x82\x0f'\x08\xe6\
|
||||
D\x09\x14I\xa6\xa7\xa7\xc5\x0a\x84?\xb5\xdc\xa0\x894\
|
||||
\xb1\x9a\x0cS\x05\xd1\x8er1\x84.\xb0\x07CM\x91\
|
||||
\xe9\xc5\x9d;wP$Q\xf1\xf3VSf\xaa\xc0\x81\
|
||||
&\xba\xc15\x19\x91\x10\x02\x080&Kyl\xfeC\
|
||||
\x14\xc1\xee\xf8r\x82`N\x94\xfa\x06\x11\x16\xcf\xce\xce\
|
||||
\x8a%Vc\x92H\x1c\x84\x12@8@e\x01<F\
|
||||
\x01\xec\x8d7\x11\xd4\x5cE\x02\xe0\xca,\xc3b\x00!\
|
||||
d\xb1$\x7fP\x05\xde\xbd{\xb7\xb9\x00\x1aT\x83\x1b\
|
||||
\xc25\xc2}\xf1\xe9\x04\x81\xda*\x12\x80\xdeA\xeb\xe9\
|
||||
\xba\x16S\xafI8\xe0\xf6\xad\x8b \x00\xd1\x0d\xc2`\
|
||||
e\xe8\x02\x07\xc0\xab\x08\x96n\xf0\x88(a]-v\
|
||||
@\x08\xb1\xb2\xc4\xfaD!\xba\xe0u\x85\xdb\x0f\xf1\xba\
|
||||
\x22\xbd\xa3|Ao`F\xa0\x90\xbe\xf8v\x82R\xfe\
|
||||
\xe1WD\x09\xe4OB\x8c\xc8\xc7\xb2\xa9\x10!\x13\xf1\
|
||||
\x0fD\x0f\x17\xb5P\x0e\x1fy@\x83\xa2\xdb\xb3B\x06\
|
||||
\xc2[u\xb8\x8a\xd6\xf6\x9cU^x\xe1\x05\xb9z\xf5\
|
||||
\xaaX\xe3\x8a4\xac\x1c\xa7\x09\xf2\x7f!\xd7\x8b\xef\xd8\
|
||||
\xb1C.^\xbc(\xca\xa0%F\x7fBq&\xa8\x88\
|
||||
\xd0l\x99\x01\xb8\x9a\x87tfX\xcd\x02G\xca\xdd\
|
||||
\xeb\xd2\xc1\xad\x0a\x0a\x95\xd60\xda5\xae!\xad\xad1\
|
||||
\x1bB\x06BS\x04Uv\xa6\xab\x82\xabz\x88\xa4\xb6\
|
||||
\x03of\xe4(\x95[\x1c\xc8\x98\xb8!\x19X#\x1e\
|
||||
\x0a\x5c,\xe1\x00\x0d\x0a{3\xcc\x05\x0e\x87\xf7\x9c\xa0\
|
||||
C\xbbH\x02\x10ZX\xf7\x0fVq\xbde8\xc1\x98\
|
||||
+\x8c\x13\xb8?D\x0c!\x05\x10\xe0bi \x80K\
|
||||
\x14\xc0\xe1Qs\x82\x8e\xc2\x11\x2278%\x8a\xc4\xd0\
|
||||
\xc6\xe2z\xbe\x98+\x8c\x03\x88\x1e\xde\x13\xa1\xc5\x0f\x18\
|
||||
\xed\x1b\xdc\x10\x86\xc1#a!\x82\x13\xc5\xcd\x1fD1\
|
||||
,\x06?\xfa\xd1\x8f\xe4\xf4\xe9\xd3\x12\x1a\x88!\xdc\xe9\
|
||||
\xae]\xbb\x84\xd8\xe3\xf6\x01\x89e\xb5\x8f\xe1\xc6\xe9\x0c\
|
||||
\x83GD]\x04A!\x84X\x1d\xfe\x9a(\xf3\xf2\xcb\
|
||||
/\xcb\xf2\xf2\xb2\xc4\x80k\xe5a\xbe\xd0\x06\xe4\x85q\
|
||||
\x11D\xf8\x1bK?\xa7\xa1\x00\xb2\x1a<\x06&\x22\x08\
|
||||
,\xc2b\xbc\xf9\x91\xa3\x8bi\xdf\x10\x88 \xf6S\xa6\
|
||||
3\xd4!F\xf1\x03\xe8\x1e0\xcaW7\x84a\xf0X\
|
||||
X\x8a\xe0\x84\x18\x84\xc51\x0a!p9C\x88!\xdb\
|
||||
j\xc6\x07\xb9>\xb8~\x84\xbd\xb1\xad\xe4A^\xd8p\
|
||||
e\x13'\xc4\x8c\x89\x99\x08\x82B\x08\xf7\x147o\x8b\
|
||||
2\xa1&\x82\x0c\x02\x04\x10B\x08w\x88\xea6\x19\x1c\
|
||||
\x88\x1dV\xee\x9c?\x7f>\x8a\x82G'\x8c\x05\x10k\
|
||||
\x83\xe7\x84\x8c\x85\xa9\x08\x02\xed&jG\xccB\xe8\x80\
|
||||
;t\xa1\xb2\xf5`\x88\x94\x88\xd9\xf5U1\x0c\x81\xc1\
|
||||
\x85B\x00\xf7\x0a\x19\x9b\x10\x22\x88p\x18\xf9\xc1GE\
|
||||
\x19\x08 \x96H\xa5\xb0\xb70\x5c\xa1\x9b\xa2]\xf7b\
|
||||
\x0a\x84\x0e\xafY\x0a\xc2\xe70,\x82\x80\x860\x0f\xe8\
|
||||
\x0ds\x11\x04V\xf9A\x10k\x8e\xb0\x17\x08\x99\xdd\xde\
|
||||
\xcb\xb8\xadC\xd8\x8c\xd7\xc79>\xdcOib\x8f\xb1\
|
||||
\x00b\x11\xc2c\x14@\x7f\x04\x11A`\x95\x1ft\xa4\
|
||||
<)\x1a\xa2\x08!\xc4\x01aD\xe8\x9c\xb20\xc2\xa1\
|
||||
C\xe80\x00\x03\xb7\xa9\x89^\x95\x00\xdb0\x1c)\x04\
|
||||
Pm\xbf\xef:\x12L\x04A!\x84s\xc5\xcd\x0f\xc5\
|
||||
\x88\xdc\x06\xa4B\x08!\x88\x93\x93\x93\xcd[\x1c\x10L\
|
||||
w\x1b\x12\x08\x9d\x0bkq\x1f\xc7\xf5\xeb\xd7\xef>\x9e\
|
||||
:\xf8\xfb\x9e<y\xd2:u\xc1B\x88\x02AE\x10\
|
||||
\x14B\x087\xb8G\x8c\xa8\xd3\xa4h'\x8c\x00'\xed\
|
||||
\x96-[\xee>>.\xb7o\xdf\x96\xb5\xb5\xb5\xbb\xf7\
|
||||
!l\xae\x08\x95\xfb:j\xa3i0\xed\xb0\x10\xa2D\
|
||||
\x0c\x22\x88\xbc \xf2\x83\x13bD\xe8\x99r$]P\
|
||||
\xb8\xc26\x0f\xc6N{UZ\x85\x10\xb5\xad+\xeaL\
|
||||
p\x11\x04e\xa1\x04\x15\xe3\x091\x22\x85\x16\x1a\x12\x17\
|
||||
h\x7fA\x1b\x8c1\x0da%X\x15\xb5QZ\xc3P\
|
||||
\xbe\xc0\xb0\xfafW:\x842W\xae\x5ci\xf6\xe9\x11\
|
||||
\xd2\x0b\x17\xfeR\x00\xf3$\x0a'\xe8(\x1c\xe1\xfe\xe2\
|
||||
\xc6|\x14\x0c\xaa\xc6\xc8\x13\xd2\x15\x92v\x02\x85\xbf\x80\
|
||||
\xad0FD%\x82\xc0j\xe2L;\x10\xc0W^y\
|
||||
%\xc8\xbe%$> z\xe8\xfd\x0b\xe0\xfe\x00\x04\x10\
|
||||
\x0epU\x88:\xd1\x89 \xb0n\x9d\xa9\x12z\x1f\x0a\
|
||||
\x12\x1e\xf4b\x86\xd8\xe3\xba\xc2\xdeB\x00/\x081!\
|
||||
J\x11\x04!\x85\x10\xae\x10CZc\x99MHl\x08\
|
||||
\xec\xfe\x1c\x1c\x8ejL\xb4\x22\x08B\x0a!`\xae\xb0\
|
||||
>D\xb2{ \x050\x00Q\x8b \x08-\x84\x10@\
|
||||
\x84\xc81\x8c\xee'\xfeA\xe8\x8b\xb5\xbf\x81\x87V4\
|
||||
7%\xa3\x00\x86!z\x11\x04\xa1\x85\x10@\x0c\xb1\xda\
|
||||
\x04\xb3\xecH\xfaD\xb4\x17\x0c\x8b \x81IB\x04A\
|
||||
\x0cB\x08\xb0\xda\x04\xf9BV\x91\xd3$\x92\xbc\x9f\x83\
|
||||
\x02\x18\x01\xc9\x88 \x88E\x08\x01\xc6>\xc1\x19R\x0c\
|
||||
\xd3\x00\xe2\x87\xc6x\x88_$\xdb\x1b4\x84\x8d\xd0Q\
|
||||
\x90\x94\x08\x82P\x0d\xd5\xdd\x80\x18\xa2\x80\xc209N\
|
||||
0ig\xdf\xbe}\xcdqW\x11\xed\xed\xd2\x10\x0a`\
|
||||
4$'\x82\xa0\x10BL\xa5\xc6\xf4\x99\x09\x89\x04\xe6\
|
||||
\x0c\xe3\x22\x92\x82G'.\x17\xc7\x1e\x0eC\x88\x87$\
|
||||
E\x10\x84\x18\xba0\x08\x10C\xb8C\xb6\xd6\xd8\xe3B\
|
||||
^\x14;\x22\x1d:\xbbX\x88\xdfa!Q\x91\xac\x08\
|
||||
\x82R\x08\xe1\x08\xd5\xf7+\x19\x05\x17*#oHA\
|
||||
\xd4\xc3\xb9>\x08_\xc4\xdb\x99r\x22t\xa4$-\x82\
|
||||
\x8e\x98\x0a&\xdd\xc0V\x918\xb0\x0a\x85K\xf2\xc6\x07\
|
||||
\xc2\x07\xc7\x17Y\xae\xaf\x13\x0di-\x83c\x058R\
|
||||
\xb2\x10AP\x0e^\x80\x10\xaao\xde4.t\x88\xc3\
|
||||
\x03\xa1s\xc2\x97\xd0\x06\xf6\xc8\xff\xedg\x01$n\xb2\
|
||||
\x11A\x10k\x9e\xb0\x17n\x975\x1c\x10E\xba\xc4u\
|
||||
\xdcn{\xeeH\x0c\xee\x07\x92\x08Y\x89 (\xc7\xf5\
|
||||
\xcf\x89\xc1\x06\xef\x1a\xb8\xdd\xd7\x10:\xbb\xcd\x89\xea\x80\
|
||||
szn\x8b\xd1\xc8\xf3{\xbdhHk\x0d\xf0\x8a\x90\
|
||||
$\xc8N\x04\x1de?!\xc2\xe3\x09I\x18\xb7c\x9b\
|
||||
\xdb\xb5\x0d\xb7\x10\xc6T\x1d#\x84m\xdb\xb6mw\x85\
|
||||
\xcem\x1f\x1apl\x95O0\xfej\x86\xed/i\x91\
|
||||
\xad\x08\x822<Fc\xf5\x94d\x86\xdb\xdd\xad\xfd\xc0\
|
||||
\xe38\xb0\x03\x9cu\xbe\x11\x02\xe7D\xce\xddw;\xde\
|
||||
9g\x97\x89\xd8\xb5\x03\xd1;\xc6\xeao\x9ad-\x82\
|
||||
\x8e\x94\x8a&\x1a81t\x02\xe9\x03'r\xed\xf7k\
|
||||
\x08\x8b\x1f\x89S\x0b\x11\x04\xa5+\x9c+\x8e\x97\x84\x90\
|
||||
\xf1\x81\xfb\x9b\xe1\x04\xe8\xf4\x89b\xb79\x0bp\xa5.\
|
||||
\x8e\xfd\xc5]l/\xd7\x10BFg\xb18\x1e\xa2\x00\
|
||||
\xe6Am\x9c`\x95\xd2\x15\x22DN\xb2\x82L\x82\x81\
|
||||
\xd0w\x8e\x95\xdf\xbc\xa8\xa5\x08:\x18\x22\x93\x01\xe1\xe4\
|
||||
\xe7\x8c\xa9\xb5\x08:ri\xa7!\xde\x81\xf8!\xf4]\
|
||||
`\xdbK\xbeP\x04+P\x0cI\x09\xc5\xafFP\x04\
|
||||
;@1\xac-\x14\xbf\x1aB\x11\xec\x01\xc5\xb06P\
|
||||
\xfcj\x0cEp\x00J1\xc4\xf1\x8c\x90\x9c\xa0\xf8\x11\
|
||||
\x8a\xe00\xb0\x9a\x9c\x0dhuY`\x9f\x1f\x01\x14\xc1\
|
||||
\x11(\xc5pJ\x18*\xa7\x04\x9c\xde\x99\xe2\xb8\xc0>\
|
||||
?R\x85\x228&\xe5\xa6Oh\xbcF\xa8<!$\
|
||||
6\x9a\xae\xaf8V\x18\xf2\x92NP\x04=R\x08\xe2\
|
||||
\x9e\xe2\x06\xc7\xb7\xa4\xa6\xc3\x1a\x22\x01\xc2\x87Pw\x89\
|
||||
\xc2G\xfaA\x11T\xa2\x22\x88t\x886P\xf8\xc8H\
|
||||
P\x04\x0d(\x04qJZ\x82\x88\xd0\x99\x15f?@\
|
||||
\xe8\xde)\x8e\x15i\xe5\xf9(|d$(\x82\xc6\x94\
|
||||
\xe3\xff\xa7*\xc7#B\x06\x01\x22\x07\xb7\xb7\x22-\xd1\
|
||||
k\x08!\x1e\xa0\x08\x06\xa6\x14E8\xc4)Y\x17\xc5\
|
||||
\xba\xe7\x13!x7\xa4%x\xab\xd2*j4\x84\x10\
|
||||
\x05(\x82\x11R\x11F'\x8e\xf88Wq\x84\xd8A\
|
||||
\xe8\x1a\xe5\xed*\xf7\xe8%\x96P\x04\x13\xa2\x22\x8e\x13\
|
||||
m\x07\x1e\xdf.q\x8a$D\x0e\xcen\xb5\xbcm\xc8\
|
||||
\xba\xe0\xddb.\x8f\x84\x86\x22\x98\x19e#\xf7}\xb2\
|
||||
.\x88\x13m\xb7\xedT\xbf\xb6\x1f\xb7\xca\xa3\xdbc\x8d\
|
||||
\xca\xc7\x148\x92\x04\xff\x000H\x87\xfd\xc2`\x8f\x83\
|
||||
\x00\x00\x00\x00IEND\xaeB`\x82\
|
||||
\x00\x00\x09S\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
@@ -11549,6 +12131,10 @@ qt_resource_name = b"\
|
||||
\x00\x1cX\x87\
|
||||
\x00w\
|
||||
\x00i\x00k\x00i\x00.\x00p\x00n\x00g\
|
||||
\x00\x0f\
|
||||
\x00\xb3\xceG\
|
||||
\x00k\
|
||||
\x00o\x00f\x00i\x00_\x00s\x00y\x00m\x00b\x00o\x00l\x00.\x00p\x00n\x00g\
|
||||
\x00\x0e\
|
||||
\x08\x9f\xcbG\
|
||||
\x00f\
|
||||
@@ -11599,7 +12185,7 @@ qt_resource_name = b"\
|
||||
qt_resource_struct = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00J\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1c\
|
||||
\x00\x00\x00J\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1d\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00&\x00\x02\x00\x00\x00\x01\x00\x00\x00\x14\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
@@ -11611,49 +12197,51 @@ qt_resource_struct = b"\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x07\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x02.\xed\
|
||||
\x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x02S.\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x02\x83\x87\
|
||||
\x00\x00\x02\x22\x00\x00\x00\x00\x00\x01\x00\x02\xa7\xc8\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02Y\x8c\
|
||||
\x00\x00\x02\x0e\x00\x00\x00\x00\x00\x01\x00\x02}\xcd\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02N)\
|
||||
\x00\x00\x01\xfa\x00\x00\x00\x00\x00\x01\x00\x02rj\
|
||||
\x00\x00\x01\x89\x89D9.\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x0c\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x01(\x97\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\
|
||||
\x00\x00\x01\x94\x1a\xa2\xa2\x92\
|
||||
\x00\x00\x01\x96\x16b\x1f\x99\
|
||||
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x01\x1d\x90\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x01:\x05\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x11\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x02\xb5\xd3\
|
||||
\x00\x00\x02f\x00\x00\x00\x00\x00\x01\x00\x02\xda\x14\
|
||||
\x00\x00\x01\x88;p\xbcJ\
|
||||
\x00\x00\x02\x14\x00\x00\x00\x00\x00\x01\x00\x02\x9f\xd6\
|
||||
\x00\x00\x028\x00\x00\x00\x00\x00\x01\x00\x02\xc4\x17\
|
||||
\x00\x00\x01\x88;p\xbcI\
|
||||
\x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x02\xa93\
|
||||
\x00\x00\x02N\x00\x00\x00\x00\x00\x01\x00\x02\xcdt\
|
||||
\x00\x00\x01\x88;p\xbcI\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x07\x00\x00\x00\x15\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x08\x00\x00\x00\x15\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x01P\xb1\
|
||||
\x00\x00\x01\x88;p\xbcJ\
|
||||
\x00\x00\x012\x00\x00\x00\x00\x00\x01\x00\x01yY\
|
||||
\x00\x00\x01\x97\xc9|\x88\xde\
|
||||
\x00\x00\x01V\x00\x00\x00\x00\x00\x01\x00\x01\x9d\x9a\
|
||||
\x00\x00\x01\x88;p\xbcI\
|
||||
\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x01\xd2-\
|
||||
\x00\x00\x01\xb8\x00\x00\x00\x00\x00\x01\x00\x01\xf6n\
|
||||
\x00\x00\x01\x94\xb4\xd4\xf0a\
|
||||
\x00\x00\x01z\x00\x00\x00\x00\x00\x01\x00\x01\x8c\xe6\
|
||||
\x00\x00\x01\x9e\x00\x00\x00\x00\x00\x01\x00\x01\xb1'\
|
||||
\x00\x00\x01\x88;p\xbcH\
|
||||
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x01LR\
|
||||
\x00\x00\x01\x88;p\xbcF\
|
||||
\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x01?\xe9\
|
||||
\x00\x00\x01\x88;p\xbcH\
|
||||
\x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x01\x82\xb0\
|
||||
\x00\x00\x01x\x00\x00\x00\x00\x00\x01\x00\x01\xa6\xf1\
|
||||
\x00\x00\x01\x88;p\xbcH\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1d\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1e\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x88;p\xbcH\
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'KCC.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.8.1
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -26,7 +26,7 @@ class Ui_mainWindow(object):
|
||||
def setupUi(self, mainWindow):
|
||||
if not mainWindow.objectName():
|
||||
mainWindow.setObjectName(u"mainWindow")
|
||||
mainWindow.resize(482, 448)
|
||||
mainWindow.resize(566, 573)
|
||||
icon = QIcon()
|
||||
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
mainWindow.setWindowIcon(icon)
|
||||
@@ -35,236 +35,14 @@ class Ui_mainWindow(object):
|
||||
self.gridLayout = QGridLayout(self.centralWidget)
|
||||
self.gridLayout.setObjectName(u"gridLayout")
|
||||
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
|
||||
self.optionWidget = QWidget(self.centralWidget)
|
||||
self.optionWidget.setObjectName(u"optionWidget")
|
||||
self.gridLayout_2 = QGridLayout(self.optionWidget)
|
||||
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.croppingBox = QCheckBox(self.optionWidget)
|
||||
self.croppingBox.setObjectName(u"croppingBox")
|
||||
self.croppingBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
||||
|
||||
self.mangaBox = QCheckBox(self.optionWidget)
|
||||
self.mangaBox.setObjectName(u"mangaBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
||||
|
||||
self.webtoonBox = QCheckBox(self.optionWidget)
|
||||
self.webtoonBox.setObjectName(u"webtoonBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
||||
|
||||
self.rotateBox = QCheckBox(self.optionWidget)
|
||||
self.rotateBox.setObjectName(u"rotateBox")
|
||||
self.rotateBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
||||
|
||||
self.borderBox = QCheckBox(self.optionWidget)
|
||||
self.borderBox.setObjectName(u"borderBox")
|
||||
self.borderBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
||||
|
||||
self.gammaBox = QCheckBox(self.optionWidget)
|
||||
self.gammaBox.setObjectName(u"gammaBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
||||
|
||||
self.interPanelCropBox = QCheckBox(self.optionWidget)
|
||||
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
|
||||
self.interPanelCropBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
|
||||
|
||||
self.colorBox = QCheckBox(self.optionWidget)
|
||||
self.colorBox.setObjectName(u"colorBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
|
||||
|
||||
self.qualityBox = QCheckBox(self.optionWidget)
|
||||
self.qualityBox.setObjectName(u"qualityBox")
|
||||
self.qualityBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
|
||||
|
||||
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
||||
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
|
||||
|
||||
self.maximizeStrips = QCheckBox(self.optionWidget)
|
||||
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||
|
||||
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
||||
|
||||
self.authorEdit = QLineEdit(self.optionWidget)
|
||||
self.authorEdit.setObjectName(u"authorEdit")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth())
|
||||
self.authorEdit.setSizePolicy(sizePolicy)
|
||||
self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
|
||||
self.authorEdit.setClearButtonEnabled(False)
|
||||
|
||||
self.gridLayout_2.addWidget(self.authorEdit, 0, 0, 1, 1)
|
||||
|
||||
self.deleteBox = QCheckBox(self.optionWidget)
|
||||
self.deleteBox.setObjectName(u"deleteBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
||||
|
||||
self.mozJpegBox = QCheckBox(self.optionWidget)
|
||||
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
||||
self.mozJpegBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
||||
|
||||
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
||||
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
||||
|
||||
self.upscaleBox = QCheckBox(self.optionWidget)
|
||||
self.upscaleBox.setObjectName(u"upscaleBox")
|
||||
self.upscaleBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
|
||||
|
||||
self.outputSplit = QCheckBox(self.optionWidget)
|
||||
self.outputSplit.setObjectName(u"outputSplit")
|
||||
|
||||
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
||||
|
||||
self.noRotateBox = QCheckBox(self.optionWidget)
|
||||
self.noRotateBox.setObjectName(u"noRotateBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
||||
|
||||
self.reduceRainbowBox = QCheckBox(self.optionWidget)
|
||||
self.reduceRainbowBox.setObjectName(u"reduceRainbowBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.reduceRainbowBox, 7, 2, 1, 1)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
||||
|
||||
self.gammaWidget = QWidget(self.centralWidget)
|
||||
self.gammaWidget.setObjectName(u"gammaWidget")
|
||||
self.gammaWidget.setVisible(False)
|
||||
self.horizontalLayout_2 = QHBoxLayout(self.gammaWidget)
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.gammaLabel = QLabel(self.gammaWidget)
|
||||
self.gammaLabel.setObjectName(u"gammaLabel")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.gammaLabel)
|
||||
|
||||
self.gammaSlider = QSlider(self.gammaWidget)
|
||||
self.gammaSlider.setObjectName(u"gammaSlider")
|
||||
self.gammaSlider.setMaximum(250)
|
||||
self.gammaSlider.setSingleStep(5)
|
||||
self.gammaSlider.setOrientation(Qt.Orientation.Horizontal)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.gammaSlider)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.gammaWidget, 6, 0, 1, 2)
|
||||
|
||||
self.croppingWidget = QWidget(self.centralWidget)
|
||||
self.croppingWidget.setObjectName(u"croppingWidget")
|
||||
self.croppingWidget.setVisible(False)
|
||||
self.horizontalLayout_3 = QHBoxLayout(self.croppingWidget)
|
||||
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
|
||||
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||
self.croppingPowerLabel = QLabel(self.croppingWidget)
|
||||
self.croppingPowerLabel.setObjectName(u"croppingPowerLabel")
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.croppingPowerLabel)
|
||||
|
||||
self.croppingPowerSlider = QSlider(self.croppingWidget)
|
||||
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
|
||||
self.croppingPowerSlider.setMaximum(300)
|
||||
self.croppingPowerSlider.setSingleStep(1)
|
||||
self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal)
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.croppingPowerSlider)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.croppingWidget, 8, 0, 1, 2)
|
||||
|
||||
self.buttonWidget = QWidget(self.centralWidget)
|
||||
self.buttonWidget.setObjectName(u"buttonWidget")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
|
||||
self.buttonWidget.setSizePolicy(sizePolicy1)
|
||||
self.gridLayout_4 = QGridLayout(self.buttonWidget)
|
||||
self.gridLayout_4.setObjectName(u"gridLayout_4")
|
||||
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||
self.directoryButton = QPushButton(self.buttonWidget)
|
||||
self.directoryButton.setObjectName(u"directoryButton")
|
||||
self.directoryButton.setMinimumSize(QSize(0, 30))
|
||||
icon1 = QIcon()
|
||||
icon1.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.directoryButton.setIcon(icon1)
|
||||
|
||||
self.gridLayout_4.addWidget(self.directoryButton, 0, 0, 1, 1)
|
||||
|
||||
self.fileButton = QPushButton(self.buttonWidget)
|
||||
self.fileButton.setObjectName(u"fileButton")
|
||||
self.fileButton.setMinimumSize(QSize(0, 30))
|
||||
icon2 = QIcon()
|
||||
icon2.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.fileButton.setIcon(icon2)
|
||||
|
||||
self.gridLayout_4.addWidget(self.fileButton, 0, 3, 1, 1)
|
||||
|
||||
self.deviceBox = QComboBox(self.buttonWidget)
|
||||
self.deviceBox.setObjectName(u"deviceBox")
|
||||
self.deviceBox.setMinimumSize(QSize(0, 28))
|
||||
|
||||
self.gridLayout_4.addWidget(self.deviceBox, 1, 0, 1, 1)
|
||||
|
||||
self.formatBox = QComboBox(self.buttonWidget)
|
||||
self.formatBox.setObjectName(u"formatBox")
|
||||
self.formatBox.setMinimumSize(QSize(0, 28))
|
||||
|
||||
self.gridLayout_4.addWidget(self.formatBox, 1, 3, 1, 1)
|
||||
|
||||
self.convertButton = QPushButton(self.buttonWidget)
|
||||
self.convertButton.setObjectName(u"convertButton")
|
||||
self.convertButton.setMinimumSize(QSize(0, 30))
|
||||
font = QFont()
|
||||
font.setBold(True)
|
||||
self.convertButton.setFont(font)
|
||||
icon3 = QIcon()
|
||||
icon3.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.convertButton.setIcon(icon3)
|
||||
|
||||
self.gridLayout_4.addWidget(self.convertButton, 1, 2, 1, 1)
|
||||
|
||||
self.clearButton = QPushButton(self.buttonWidget)
|
||||
self.clearButton.setObjectName(u"clearButton")
|
||||
self.clearButton.setMinimumSize(QSize(0, 30))
|
||||
icon4 = QIcon()
|
||||
icon4.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.clearButton.setIcon(icon4)
|
||||
|
||||
self.gridLayout_4.addWidget(self.clearButton, 0, 2, 1, 1)
|
||||
|
||||
self.directoryButton.raise_()
|
||||
self.clearButton.raise_()
|
||||
self.fileButton.raise_()
|
||||
self.deviceBox.raise_()
|
||||
self.convertButton.raise_()
|
||||
self.formatBox.raise_()
|
||||
|
||||
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
|
||||
self.jobList = QListWidget(self.centralWidget)
|
||||
self.jobList.setObjectName(u"jobList")
|
||||
self.jobList.setStyleSheet(u"")
|
||||
self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
||||
self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
|
||||
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
|
||||
|
||||
self.toolWidget = QWidget(self.centralWidget)
|
||||
self.toolWidget.setObjectName(u"toolWidget")
|
||||
@@ -274,32 +52,115 @@ class Ui_mainWindow(object):
|
||||
self.editorButton = QPushButton(self.toolWidget)
|
||||
self.editorButton.setObjectName(u"editorButton")
|
||||
self.editorButton.setMinimumSize(QSize(0, 30))
|
||||
icon5 = QIcon()
|
||||
icon5.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.editorButton.setIcon(icon5)
|
||||
icon1 = QIcon()
|
||||
icon1.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.editorButton.setIcon(icon1)
|
||||
|
||||
self.horizontalLayout.addWidget(self.editorButton)
|
||||
|
||||
self.kofiButton = QPushButton(self.toolWidget)
|
||||
self.kofiButton.setObjectName(u"kofiButton")
|
||||
self.kofiButton.setMinimumSize(QSize(0, 30))
|
||||
icon2 = QIcon()
|
||||
icon2.addFile(u":/Other/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.kofiButton.setIcon(icon2)
|
||||
self.kofiButton.setIconSize(QSize(19, 16))
|
||||
|
||||
self.horizontalLayout.addWidget(self.kofiButton)
|
||||
|
||||
self.wikiButton = QPushButton(self.toolWidget)
|
||||
self.wikiButton.setObjectName(u"wikiButton")
|
||||
self.wikiButton.setMinimumSize(QSize(0, 30))
|
||||
icon6 = QIcon()
|
||||
icon6.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.wikiButton.setIcon(icon6)
|
||||
icon3 = QIcon()
|
||||
icon3.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.wikiButton.setIcon(icon3)
|
||||
|
||||
self.horizontalLayout.addWidget(self.wikiButton)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
|
||||
|
||||
self.jobList = QListWidget(self.centralWidget)
|
||||
self.jobList.setObjectName(u"jobList")
|
||||
self.jobList.setStyleSheet(u"")
|
||||
self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
||||
self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.buttonWidget = QWidget(self.centralWidget)
|
||||
self.buttonWidget.setObjectName(u"buttonWidget")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
|
||||
self.buttonWidget.setSizePolicy(sizePolicy)
|
||||
self.gridLayout_4 = QGridLayout(self.buttonWidget)
|
||||
self.gridLayout_4.setObjectName(u"gridLayout_4")
|
||||
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||
self.convertButton = QPushButton(self.buttonWidget)
|
||||
self.convertButton.setObjectName(u"convertButton")
|
||||
self.convertButton.setMinimumSize(QSize(0, 30))
|
||||
font = QFont()
|
||||
font.setBold(True)
|
||||
self.convertButton.setFont(font)
|
||||
icon4 = QIcon()
|
||||
icon4.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.convertButton.setIcon(icon4)
|
||||
|
||||
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
|
||||
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
|
||||
|
||||
self.clearButton = QPushButton(self.buttonWidget)
|
||||
self.clearButton.setObjectName(u"clearButton")
|
||||
self.clearButton.setMinimumSize(QSize(0, 30))
|
||||
icon5 = QIcon()
|
||||
icon5.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.clearButton.setIcon(icon5)
|
||||
|
||||
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
|
||||
|
||||
self.deviceBox = QComboBox(self.buttonWidget)
|
||||
self.deviceBox.setObjectName(u"deviceBox")
|
||||
self.deviceBox.setMinimumSize(QSize(0, 28))
|
||||
|
||||
self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1)
|
||||
|
||||
self.fileButton = QPushButton(self.buttonWidget)
|
||||
self.fileButton.setObjectName(u"fileButton")
|
||||
self.fileButton.setMinimumSize(QSize(0, 30))
|
||||
icon6 = QIcon()
|
||||
icon6.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.fileButton.setIcon(icon6)
|
||||
|
||||
self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
|
||||
|
||||
self.defaultOutputFolderButton = QPushButton(self.buttonWidget)
|
||||
self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton")
|
||||
self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30))
|
||||
icon7 = QIcon()
|
||||
icon7.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.defaultOutputFolderButton.setIcon(icon7)
|
||||
|
||||
self.gridLayout_4.addWidget(self.defaultOutputFolderButton, 0, 5, 1, 1)
|
||||
|
||||
self.defaultOutputFolderBox = QCheckBox(self.buttonWidget)
|
||||
self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
|
||||
self.defaultOutputFolderBox.setSizePolicy(sizePolicy1)
|
||||
self.defaultOutputFolderBox.setTristate(True)
|
||||
|
||||
self.gridLayout_4.addWidget(self.defaultOutputFolderBox, 0, 4, 1, 1)
|
||||
|
||||
self.formatBox = QComboBox(self.buttonWidget)
|
||||
self.formatBox.setObjectName(u"formatBox")
|
||||
self.formatBox.setMinimumSize(QSize(0, 28))
|
||||
|
||||
self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 2)
|
||||
|
||||
self.clearButton.raise_()
|
||||
self.deviceBox.raise_()
|
||||
self.convertButton.raise_()
|
||||
self.formatBox.raise_()
|
||||
self.defaultOutputFolderButton.raise_()
|
||||
self.fileButton.raise_()
|
||||
self.defaultOutputFolderBox.raise_()
|
||||
|
||||
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
|
||||
|
||||
self.progressBar = QProgressBar(self.centralWidget)
|
||||
self.progressBar.setObjectName(u"progressBar")
|
||||
@@ -328,7 +189,7 @@ class Ui_mainWindow(object):
|
||||
|
||||
self.widthBox = QSpinBox(self.customWidget)
|
||||
self.widthBox.setObjectName(u"widthBox")
|
||||
self.widthBox.setMaximum(2160)
|
||||
self.widthBox.setMaximum(2400)
|
||||
|
||||
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
||||
|
||||
@@ -346,7 +207,240 @@ class Ui_mainWindow(object):
|
||||
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.customWidget, 7, 0, 1, 2)
|
||||
self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2)
|
||||
|
||||
self.croppingWidget = QWidget(self.centralWidget)
|
||||
self.croppingWidget.setObjectName(u"croppingWidget")
|
||||
self.croppingWidget.setVisible(False)
|
||||
self.gridLayout_5 = QGridLayout(self.croppingWidget)
|
||||
self.gridLayout_5.setObjectName(u"gridLayout_5")
|
||||
self.gridLayout_5.setContentsMargins(0, 0, 0, 0)
|
||||
self.preserveMarginLabel = QLabel(self.croppingWidget)
|
||||
self.preserveMarginLabel.setObjectName(u"preserveMarginLabel")
|
||||
|
||||
self.gridLayout_5.addWidget(self.preserveMarginLabel, 1, 0, 1, 1)
|
||||
|
||||
self.croppingPowerLabel = QLabel(self.croppingWidget)
|
||||
self.croppingPowerLabel.setObjectName(u"croppingPowerLabel")
|
||||
|
||||
self.gridLayout_5.addWidget(self.croppingPowerLabel, 0, 0, 1, 1)
|
||||
|
||||
self.croppingPowerSlider = QSlider(self.croppingWidget)
|
||||
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
|
||||
self.croppingPowerSlider.setMaximum(300)
|
||||
self.croppingPowerSlider.setSingleStep(1)
|
||||
self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal)
|
||||
|
||||
self.gridLayout_5.addWidget(self.croppingPowerSlider, 0, 1, 1, 1)
|
||||
|
||||
self.preserveMarginBox = QSpinBox(self.croppingWidget)
|
||||
self.preserveMarginBox.setObjectName(u"preserveMarginBox")
|
||||
sizePolicy1.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth())
|
||||
self.preserveMarginBox.setSizePolicy(sizePolicy1)
|
||||
self.preserveMarginBox.setMaximum(99)
|
||||
self.preserveMarginBox.setSingleStep(5)
|
||||
self.preserveMarginBox.setValue(0)
|
||||
|
||||
self.gridLayout_5.addWidget(self.preserveMarginBox, 1, 1, 1, 1)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.croppingWidget, 9, 0, 1, 2)
|
||||
|
||||
self.optionWidget = QWidget(self.centralWidget)
|
||||
self.optionWidget.setObjectName(u"optionWidget")
|
||||
self.gridLayout_2 = QGridLayout(self.optionWidget)
|
||||
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.interPanelCropBox = QCheckBox(self.optionWidget)
|
||||
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
|
||||
self.interPanelCropBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
|
||||
|
||||
self.mangaBox = QCheckBox(self.optionWidget)
|
||||
self.mangaBox.setObjectName(u"mangaBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
||||
|
||||
self.authorEdit = QLineEdit(self.optionWidget)
|
||||
self.authorEdit.setObjectName(u"authorEdit")
|
||||
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
sizePolicy3.setHorizontalStretch(0)
|
||||
sizePolicy3.setVerticalStretch(0)
|
||||
sizePolicy3.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth())
|
||||
self.authorEdit.setSizePolicy(sizePolicy3)
|
||||
self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
|
||||
self.authorEdit.setClearButtonEnabled(False)
|
||||
|
||||
self.gridLayout_2.addWidget(self.authorEdit, 0, 0, 1, 1)
|
||||
|
||||
self.croppingBox = QCheckBox(self.optionWidget)
|
||||
self.croppingBox.setObjectName(u"croppingBox")
|
||||
self.croppingBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
||||
|
||||
self.webtoonBox = QCheckBox(self.optionWidget)
|
||||
self.webtoonBox.setObjectName(u"webtoonBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
||||
|
||||
self.colorBox = QCheckBox(self.optionWidget)
|
||||
self.colorBox.setObjectName(u"colorBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
|
||||
|
||||
self.deleteBox = QCheckBox(self.optionWidget)
|
||||
self.deleteBox.setObjectName(u"deleteBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
||||
|
||||
self.comicinfoTitleBox = QCheckBox(self.optionWidget)
|
||||
self.comicinfoTitleBox.setObjectName(u"comicinfoTitleBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.comicinfoTitleBox, 7, 0, 1, 1)
|
||||
|
||||
self.qualityBox = QCheckBox(self.optionWidget)
|
||||
self.qualityBox.setObjectName(u"qualityBox")
|
||||
self.qualityBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
|
||||
|
||||
self.reduceRainbowBox = QCheckBox(self.optionWidget)
|
||||
self.reduceRainbowBox.setObjectName(u"reduceRainbowBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.reduceRainbowBox, 7, 2, 1, 1)
|
||||
|
||||
self.noRotateBox = QCheckBox(self.optionWidget)
|
||||
self.noRotateBox.setObjectName(u"noRotateBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
||||
|
||||
self.fileFusionBox = QCheckBox(self.optionWidget)
|
||||
self.fileFusionBox.setObjectName(u"fileFusionBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
|
||||
|
||||
self.gammaBox = QCheckBox(self.optionWidget)
|
||||
self.gammaBox.setObjectName(u"gammaBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
||||
|
||||
self.mozJpegBox = QCheckBox(self.optionWidget)
|
||||
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
||||
self.mozJpegBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
||||
|
||||
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
||||
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
|
||||
|
||||
self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
|
||||
self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
|
||||
|
||||
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
||||
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
||||
|
||||
self.upscaleBox = QCheckBox(self.optionWidget)
|
||||
self.upscaleBox.setObjectName(u"upscaleBox")
|
||||
self.upscaleBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
|
||||
|
||||
self.outputSplit = QCheckBox(self.optionWidget)
|
||||
self.outputSplit.setObjectName(u"outputSplit")
|
||||
|
||||
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
||||
|
||||
self.rotateBox = QCheckBox(self.optionWidget)
|
||||
self.rotateBox.setObjectName(u"rotateBox")
|
||||
self.rotateBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
||||
|
||||
self.borderBox = QCheckBox(self.optionWidget)
|
||||
self.borderBox.setObjectName(u"borderBox")
|
||||
self.borderBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
||||
|
||||
self.maximizeStrips = QCheckBox(self.optionWidget)
|
||||
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||
|
||||
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
||||
|
||||
self.rotateFirstBox = QCheckBox(self.optionWidget)
|
||||
self.rotateFirstBox.setObjectName(u"rotateFirstBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
||||
|
||||
self.gammaWidget = QWidget(self.centralWidget)
|
||||
self.gammaWidget.setObjectName(u"gammaWidget")
|
||||
self.gammaWidget.setVisible(False)
|
||||
self.horizontalLayout_2 = QHBoxLayout(self.gammaWidget)
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.gammaLabel = QLabel(self.gammaWidget)
|
||||
self.gammaLabel.setObjectName(u"gammaLabel")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.gammaLabel)
|
||||
|
||||
self.gammaSlider = QSlider(self.gammaWidget)
|
||||
self.gammaSlider.setObjectName(u"gammaSlider")
|
||||
self.gammaSlider.setMaximum(250)
|
||||
self.gammaSlider.setSingleStep(5)
|
||||
self.gammaSlider.setOrientation(Qt.Orientation.Horizontal)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.gammaSlider)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.gammaWidget, 7, 0, 1, 2)
|
||||
|
||||
self.chunkSizeWidget = QWidget(self.centralWidget)
|
||||
self.chunkSizeWidget.setObjectName(u"chunkSizeWidget")
|
||||
sizePolicy3.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.chunkSizeWidget.setSizePolicy(sizePolicy3)
|
||||
self.chunkSizeWidget.setVisible(False)
|
||||
self.horizontalLayout_4 = QHBoxLayout(self.chunkSizeWidget)
|
||||
self.horizontalLayout_4.setSpacing(0)
|
||||
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
|
||||
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||
self.chunkSizeLabel = QLabel(self.chunkSizeWidget)
|
||||
self.chunkSizeLabel.setObjectName(u"chunkSizeLabel")
|
||||
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
||||
sizePolicy4.setHorizontalStretch(0)
|
||||
sizePolicy4.setVerticalStretch(0)
|
||||
sizePolicy4.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
|
||||
self.chunkSizeLabel.setSizePolicy(sizePolicy4)
|
||||
|
||||
self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
|
||||
|
||||
self.chunkSizeBox = QSpinBox(self.chunkSizeWidget)
|
||||
self.chunkSizeBox.setObjectName(u"chunkSizeBox")
|
||||
self.chunkSizeBox.setMinimum(100)
|
||||
self.chunkSizeBox.setMaximum(600)
|
||||
self.chunkSizeBox.setValue(400)
|
||||
|
||||
self.horizontalLayout_4.addWidget(self.chunkSizeBox)
|
||||
|
||||
self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget)
|
||||
self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel")
|
||||
sizePolicy4.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
|
||||
self.chunkSizeWarnLabel.setSizePolicy(sizePolicy4)
|
||||
|
||||
self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1)
|
||||
|
||||
mainWindow.setCentralWidget(self.centralWidget)
|
||||
self.statusBar = QStatusBar(mainWindow)
|
||||
@@ -354,9 +448,7 @@ class Ui_mainWindow(object):
|
||||
self.statusBar.setSizeGripEnabled(False)
|
||||
mainWindow.setStatusBar(self.statusBar)
|
||||
QWidget.setTabOrder(self.convertButton, self.clearButton)
|
||||
QWidget.setTabOrder(self.clearButton, self.directoryButton)
|
||||
QWidget.setTabOrder(self.directoryButton, self.fileButton)
|
||||
QWidget.setTabOrder(self.fileButton, self.deviceBox)
|
||||
QWidget.setTabOrder(self.clearButton, self.deviceBox)
|
||||
QWidget.setTabOrder(self.deviceBox, self.formatBox)
|
||||
QWidget.setTabOrder(self.formatBox, self.mangaBox)
|
||||
QWidget.setTabOrder(self.mangaBox, self.rotateBox)
|
||||
@@ -367,18 +459,23 @@ class Ui_mainWindow(object):
|
||||
QWidget.setTabOrder(self.gammaBox, self.borderBox)
|
||||
QWidget.setTabOrder(self.borderBox, self.outputSplit)
|
||||
QWidget.setTabOrder(self.outputSplit, self.colorBox)
|
||||
QWidget.setTabOrder(self.colorBox, self.croppingBox)
|
||||
QWidget.setTabOrder(self.croppingBox, self.mozJpegBox)
|
||||
QWidget.setTabOrder(self.colorBox, self.mozJpegBox)
|
||||
QWidget.setTabOrder(self.mozJpegBox, self.maximizeStrips)
|
||||
QWidget.setTabOrder(self.maximizeStrips, self.deleteBox)
|
||||
QWidget.setTabOrder(self.maximizeStrips, self.croppingBox)
|
||||
QWidget.setTabOrder(self.croppingBox, self.spreadShiftBox)
|
||||
QWidget.setTabOrder(self.spreadShiftBox, self.deleteBox)
|
||||
QWidget.setTabOrder(self.deleteBox, self.disableProcessingBox)
|
||||
QWidget.setTabOrder(self.disableProcessingBox, self.editorButton)
|
||||
QWidget.setTabOrder(self.disableProcessingBox, self.chunkSizeBox)
|
||||
QWidget.setTabOrder(self.chunkSizeBox, self.noRotateBox)
|
||||
QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox)
|
||||
QWidget.setTabOrder(self.interPanelCropBox, self.reduceRainbowBox)
|
||||
QWidget.setTabOrder(self.reduceRainbowBox, self.heightBox)
|
||||
QWidget.setTabOrder(self.heightBox, self.croppingPowerSlider)
|
||||
QWidget.setTabOrder(self.croppingPowerSlider, self.editorButton)
|
||||
QWidget.setTabOrder(self.editorButton, self.wikiButton)
|
||||
QWidget.setTabOrder(self.wikiButton, self.jobList)
|
||||
QWidget.setTabOrder(self.jobList, self.gammaSlider)
|
||||
QWidget.setTabOrder(self.gammaSlider, self.widthBox)
|
||||
QWidget.setTabOrder(self.widthBox, self.heightBox)
|
||||
QWidget.setTabOrder(self.heightBox, self.croppingPowerSlider)
|
||||
|
||||
self.retranslateUi(mainWindow)
|
||||
|
||||
@@ -388,107 +485,34 @@ class Ui_mainWindow(object):
|
||||
def retranslateUi(self, mainWindow):
|
||||
mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>", None))
|
||||
self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
||||
self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None))
|
||||
self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None))
|
||||
self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Manga mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled<br/></span>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><pre style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Do not process any image, ignore profile and processing options</pre></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<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>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.reduceRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Reduce rainbow effect on color eink by slightly blurring images", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.reduceRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Reduce Rainbow", None))
|
||||
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
|
||||
self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add directory", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add file", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Target device.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory.</p></body></html>", None))
|
||||
self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None))
|
||||
self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>", None))
|
||||
self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Target device.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add file(s)", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Use this to select the default output directory.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.defaultOutputFolderButton.setText("")
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - next to source<br/></span>Place output files next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - folder next to source<br/></span>Place output files in a folder next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Custom<br/></span>Place output files in custom directory specified by right button</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.defaultOutputFolderBox.setText(QCoreApplication.translate("mainWindow", u"Output Folder", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None))
|
||||
self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
@@ -503,5 +527,108 @@ class Ui_mainWindow(object):
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.preserveMarginLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>After calculating the cropping boundaries, "back up" a specified percentage amount.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None))
|
||||
self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled<br/></span>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.comicinfoTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Write Title from ComicInfo.xml", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.comicinfoTitleBox.setText(QCoreApplication.translate("mainWindow", u"ComicInfo Title", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.reduceRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Reduce rainbow effect on color eink by slightly blurring images", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.reduceRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow blur", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:700; text-decoration: underline;\">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=\" font-weight:700; text-decoration: underline;\">Checked</span><br/>Output file size specified in "Chunk size MB" before split occurs.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<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>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None))
|
||||
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None))
|
||||
self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'MetaEditor.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.8.1
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '7.2.1'
|
||||
__version__ = '8.0.4'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -23,24 +23,27 @@ import pathlib
|
||||
import re
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from time import strftime, gmtime
|
||||
from time import perf_counter, strftime, gmtime
|
||||
from copy import copy
|
||||
from glob import glob, escape
|
||||
from re import sub
|
||||
from stat import S_IWRITE, S_IREAD, S_IEXEC
|
||||
from typing import List
|
||||
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||
from tempfile import mkdtemp, gettempdir, TemporaryFile
|
||||
from shutil import move, copytree, rmtree, copyfile
|
||||
from multiprocessing import Pool
|
||||
from uuid import uuid4
|
||||
from natsort import os_sorted
|
||||
from natsort import os_sort_keygen, os_sorted
|
||||
from slugify import slugify as slugify_ext
|
||||
from PIL import Image, ImageFile
|
||||
from subprocess import STDOUT, PIPE
|
||||
from pathlib import Path
|
||||
from subprocess import STDOUT, PIPE, CalledProcessError
|
||||
from psutil import virtual_memory, disk_usage
|
||||
from html import escape as hescape
|
||||
|
||||
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||
from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||
from .comicarchive import SEVENZIP, available_archive_tools
|
||||
from . import comic2panel
|
||||
from . import image
|
||||
from . import comicarchive
|
||||
@@ -51,7 +54,7 @@ from . import kindle
|
||||
from . import __version__
|
||||
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
|
||||
OS_SORT_KEY = os_sort_keygen()
|
||||
|
||||
def main(argv=None):
|
||||
global options
|
||||
@@ -77,15 +80,15 @@ def main(argv=None):
|
||||
return 0
|
||||
|
||||
|
||||
def buildHTML(path, imgfile, imgfilepath):
|
||||
imgfilepath = md5Checksum(imgfilepath)
|
||||
def buildHTML(path, imgfile, imgfilepath, imgfile2=None):
|
||||
key = pathlib.Path(imgfilepath).name
|
||||
filename = getImageFileName(imgfile)
|
||||
deviceres = options.profileData[1]
|
||||
if not options.noprocessing and "Rotated" in options.imgMetadata[imgfilepath]:
|
||||
if not options.noprocessing and "Rotated" in options.imgMetadata[key]:
|
||||
rotatedPage = True
|
||||
else:
|
||||
rotatedPage = False
|
||||
if not options.noprocessing and "BlackBackground" in options.imgMetadata[imgfilepath]:
|
||||
if not options.noprocessing and "BlackBackground" in options.imgMetadata[key]:
|
||||
additionalStyle = 'background-color:#000000;'
|
||||
else:
|
||||
additionalStyle = ''
|
||||
@@ -103,10 +106,13 @@ def buildHTML(path, imgfile, imgfilepath):
|
||||
os.makedirs(htmlpath)
|
||||
htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml')
|
||||
imgsize = Image.open(os.path.join(head, "Images", postfix, imgfile)).size
|
||||
imgsizeframe = list(imgsize)
|
||||
imgsize2 = (0, 0)
|
||||
if imgfile2:
|
||||
imgsize2 = Image.open(os.path.join(head, "Images", postfix, imgfile2)).size
|
||||
imgsizeframe[1] += imgsize2[1]
|
||||
if options.hq:
|
||||
imgsizeframe = (int(imgsize[0] // 1.5), int(imgsize[1] // 1.5))
|
||||
else:
|
||||
imgsizeframe = imgsize
|
||||
imgsizeframe = (int(imgsizeframe[0] // 1.5), int(imgsizeframe[1] // 1.5))
|
||||
f = open(htmlfile, "w", encoding='UTF-8')
|
||||
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
|
||||
"<!DOCTYPE html>\n",
|
||||
@@ -115,12 +121,17 @@ def buildHTML(path, imgfile, imgfilepath):
|
||||
"<title>", hescape(filename[0]), "</title>\n",
|
||||
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
||||
"<meta name=\"viewport\" "
|
||||
"content=\"width=" + str(imgsize[0]) + ", height=" + str(imgsize[1]) + "\"/>\n"
|
||||
"content=\"width=" + str(imgsizeframe[0]) + ", height=" + str(imgsizeframe[1]) + "\"/>\n"
|
||||
"</head>\n",
|
||||
"<body style=\"" + additionalStyle + "\">\n",
|
||||
"<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsizeframe) + "%;\">\n",
|
||||
"<img width=\"" + str(imgsizeframe[0]) + "\" height=\"" + str(imgsizeframe[1]) + "\" ",
|
||||
"src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n</div>\n"])
|
||||
# this display none div fixes formatting issues with virtual panel mode, for some reason
|
||||
'<div style="display:none;">.</div>\n',
|
||||
])
|
||||
f.write(f'<img width="{imgsize[0]}" height="{imgsize[1]}" src="{"../" * backref}Images/{postfix}{imgfile}"/>\n')
|
||||
if imgfile2:
|
||||
f.write(f'<img width="{imgsize2[0]}" height="{imgsize2[1]}" src="{"../" * backref}Images/{postfix}{imgfile2}"/>\n')
|
||||
f.write("</div>\n")
|
||||
if options.iskindle and options.panelview:
|
||||
if options.autoscale:
|
||||
size = (getPanelViewResolution(imgsize, deviceres))
|
||||
@@ -216,7 +227,7 @@ def buildNCX(dstdir, title, chapters, chapternames):
|
||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||
navID = folder.replace('/', '_').replace('\\', '_')
|
||||
if options.chapters:
|
||||
if options.comicinfo_chapters:
|
||||
title = chapternames[chapter[1]]
|
||||
navID = filename[0].replace('/', '_').replace('\\', '_')
|
||||
elif os.path.basename(folder) != "Text":
|
||||
@@ -244,7 +255,7 @@ def buildNAV(dstdir, title, chapters, chapternames):
|
||||
for chapter in chapters:
|
||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||
if options.chapters:
|
||||
if options.comicinfo_chapters:
|
||||
title = chapternames[chapter[1]]
|
||||
elif os.path.basename(folder) != "Text":
|
||||
title = chapternames[os.path.basename(folder)]
|
||||
@@ -256,7 +267,7 @@ def buildNAV(dstdir, title, chapters, chapternames):
|
||||
for chapter in chapters:
|
||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||
if options.chapters:
|
||||
if options.comicinfo_chapters:
|
||||
title = chapternames[chapter[1]]
|
||||
elif os.path.basename(folder) != "Text":
|
||||
title = chapternames[os.path.basename(folder)]
|
||||
@@ -283,9 +294,9 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
"<dc:identifier id=\"BookID\">urn:uuid:", options.uuid, "</dc:identifier>\n",
|
||||
"<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n"])
|
||||
if len(options.summary) > 0:
|
||||
f.writelines(["<dc:description>", options.summary, "</dc:description>\n"])
|
||||
f.writelines(["<dc:description>", hescape(options.summary), "</dc:description>\n"])
|
||||
for author in options.authors:
|
||||
f.writelines(["<dc:creator>", author, "</dc:creator>\n"])
|
||||
f.writelines(["<dc:creator>", hescape(author), "</dc:creator>\n"])
|
||||
f.writelines(["<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n",
|
||||
"<meta name=\"cover\" content=\"cover\"/>\n"])
|
||||
if options.iskindle and options.profile != 'Custom':
|
||||
@@ -312,12 +323,8 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
"<item id=\"nav\" href=\"nav.xhtml\" ",
|
||||
"properties=\"nav\" media-type=\"application/xhtml+xml\"/>\n"])
|
||||
if cover is not None:
|
||||
filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\'))
|
||||
if '.png' == filename[1]:
|
||||
mt = 'image/png'
|
||||
else:
|
||||
mt = 'image/jpeg'
|
||||
f.write("<item id=\"cover\" href=\"Images/cover" + filename[1] + "\" media-type=\"" + mt +
|
||||
mt = 'image/jpeg'
|
||||
f.write("<item id=\"cover\" href=\"Images/cover.jpg" + "\" media-type=\"" + mt +
|
||||
"\" properties=\"cover-image\"/>\n")
|
||||
reflist = []
|
||||
for path in filelist:
|
||||
@@ -330,10 +337,16 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
".xhtml\" media-type=\"application/xhtml+xml\"/>\n")
|
||||
if '.png' == filename[1]:
|
||||
mt = 'image/png'
|
||||
elif '.gif' == filename[1]:
|
||||
mt = 'image/gif'
|
||||
else:
|
||||
mt = 'image/jpeg'
|
||||
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
|
||||
mt + "\"/>\n")
|
||||
if 'above' in path[1]:
|
||||
bottom = path[1].replace('above', 'below')
|
||||
uniqueid = uniqueid.replace('above', 'below')
|
||||
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + bottom + "\" media-type=\"" + mt + "\"/>\n")
|
||||
f.write("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n")
|
||||
|
||||
|
||||
@@ -356,63 +369,63 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
pageside = "left"
|
||||
else:
|
||||
pageside = "right"
|
||||
|
||||
# initial spread order forwards
|
||||
page_spread_property_list = []
|
||||
for entry in reflist:
|
||||
if options.righttoleft:
|
||||
if entry.endswith("-a"):
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty("center"))
|
||||
)
|
||||
if "-kcc-a" in entry or "-kcc-d" in entry:
|
||||
page_spread_property_list.append("center")
|
||||
pageside = "right"
|
||||
elif entry.endswith("-b"):
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty("right"))
|
||||
)
|
||||
elif "-kcc-b" in entry:
|
||||
page_spread_property_list.append("right")
|
||||
pageside = "right"
|
||||
elif entry.endswith("-c"):
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty("left"))
|
||||
)
|
||||
elif "-kcc-c" in entry:
|
||||
page_spread_property_list.append("left")
|
||||
pageside = "right"
|
||||
else:
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty(pageside))
|
||||
)
|
||||
page_spread_property_list.append(pageside)
|
||||
if pageside == "right":
|
||||
pageside = "left"
|
||||
else:
|
||||
pageside = "right"
|
||||
else:
|
||||
if entry.endswith("-a"):
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty("center"))
|
||||
)
|
||||
if "-kcc-a" in entry or "-kcc-d" in entry:
|
||||
page_spread_property_list.append("center")
|
||||
pageside = "left"
|
||||
elif entry.endswith("-b"):
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty("left"))
|
||||
)
|
||||
elif "-kcc-b" in entry:
|
||||
page_spread_property_list.append("left")
|
||||
pageside = "left"
|
||||
elif entry.endswith("-c"):
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty("right"))
|
||||
)
|
||||
elif "-kcc-c" in entry:
|
||||
page_spread_property_list.append("right")
|
||||
pageside = "left"
|
||||
else:
|
||||
f.write(
|
||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||
pageSpreadProperty(pageside))
|
||||
)
|
||||
page_spread_property_list.append(pageside)
|
||||
if pageside == "right":
|
||||
pageside = "left"
|
||||
else:
|
||||
pageside = "right"
|
||||
|
||||
# fix spread orders backward
|
||||
spread_seen = False
|
||||
for i in range(len(reflist) -1, -1, -1):
|
||||
entry = reflist[i]
|
||||
if "-kcc-x" not in entry:
|
||||
spread_seen = True
|
||||
if options.righttoleft:
|
||||
pageside = "left"
|
||||
else:
|
||||
pageside = "right"
|
||||
elif spread_seen:
|
||||
page_spread_property_list[i] = pageside
|
||||
if pageside == "right":
|
||||
pageside = "left"
|
||||
else:
|
||||
pageside = "right"
|
||||
|
||||
for entry, prop in zip(reflist, page_spread_property_list):
|
||||
f.write(f'<itemref idref="page_{entry}" {pageSpreadProperty(prop)}/>\n')
|
||||
|
||||
f.write("</spine>\n</package>\n")
|
||||
f.close()
|
||||
os.mkdir(os.path.join(dstdir, 'META-INF'))
|
||||
@@ -425,11 +438,9 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
"</container>"])
|
||||
f.close()
|
||||
|
||||
|
||||
def buildEPUB(path, chapternames, tomenumber, ischunked):
|
||||
def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, len_tomes=0):
|
||||
filelist = []
|
||||
chapterlist = []
|
||||
cover = None
|
||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w', encoding='UTF-8')
|
||||
f.writelines(["@page {\n",
|
||||
@@ -439,7 +450,14 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
||||
"display: block;\n",
|
||||
"margin: 0;\n",
|
||||
"padding: 0;\n",
|
||||
"}\n"])
|
||||
"}\n",
|
||||
])
|
||||
if options.kindle_scribe_azw3:
|
||||
f.writelines([
|
||||
"img {\n",
|
||||
"display: block;\n",
|
||||
"}\n",
|
||||
])
|
||||
if options.iskindle and options.panelview:
|
||||
f.writelines(["#PV {\n",
|
||||
"position: absolute;\n",
|
||||
@@ -506,21 +524,32 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
||||
"display: none;\n",
|
||||
"}\n"])
|
||||
f.close()
|
||||
build_html_start = perf_counter()
|
||||
cover.save_to_epub(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes)
|
||||
options.covers.append((cover, options.uuid))
|
||||
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||
chapter = False
|
||||
dirnames, filenames = walkSort(dirnames, filenames)
|
||||
for afile in filenames:
|
||||
if cover is None:
|
||||
cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
|
||||
'cover' + getImageFileName(afile)[1])
|
||||
options.covers.append((image.Cover(os.path.join(dirpath, afile), cover, options,
|
||||
tomenumber), options.uuid))
|
||||
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
||||
if afile == 'cover.jpg':
|
||||
continue
|
||||
if 'below' in afile:
|
||||
continue
|
||||
if not chapter:
|
||||
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
|
||||
chapterlist.append((dirpath.replace('Images', 'Text'), afile))
|
||||
chapter = True
|
||||
if 'above' in afile:
|
||||
bottom = afile.replace('above', 'below')
|
||||
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile), bottom))
|
||||
else:
|
||||
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
||||
build_html_end = perf_counter()
|
||||
print(f"buildHTML: {build_html_end - build_html_start} seconds")
|
||||
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
|
||||
if not chapternames and options.chapters and not ischunked:
|
||||
if ischunked:
|
||||
options.comicinfo_chapters = []
|
||||
|
||||
if not chapternames and options.comicinfo_chapters:
|
||||
chapterlist = []
|
||||
|
||||
global_diff = 0
|
||||
@@ -533,7 +562,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
||||
elif options.splitter == 2:
|
||||
diff_delta = 2
|
||||
|
||||
for aChapter in options.chapters:
|
||||
for aChapter in options.comicinfo_chapters:
|
||||
pageid = aChapter[0]
|
||||
cur_diff = global_diff
|
||||
global_diff = 0
|
||||
@@ -556,7 +585,6 @@ def imgDirectoryProcessing(path):
|
||||
workerPool = Pool(maxtasksperchild=100)
|
||||
workerOutput = []
|
||||
options.imgMetadata = {}
|
||||
options.imgOld = []
|
||||
work = []
|
||||
pagenumber = 0
|
||||
for dirpath, _, filenames in os.walk(path):
|
||||
@@ -566,19 +594,23 @@ def imgDirectoryProcessing(path):
|
||||
if GUI:
|
||||
GUI.progressBarTick.emit(str(pagenumber))
|
||||
if len(work) > 0:
|
||||
img_processing_start = perf_counter()
|
||||
for i in work:
|
||||
workerPool.apply_async(func=imgFileProcessing, args=(i,), callback=imgFileProcessingTick)
|
||||
workerPool.close()
|
||||
workerPool.join()
|
||||
img_processing_end = perf_counter()
|
||||
print(f"imgFileProcessing: {img_processing_end - img_processing_start} seconds")
|
||||
|
||||
# macOS 15 likes to add ._ files after multiprocessing
|
||||
dot_clean(path)
|
||||
|
||||
if GUI and not GUI.conversionAlive:
|
||||
rmtree(os.path.join(path, '..', '..'), True)
|
||||
raise UserWarning("Conversion interrupted.")
|
||||
if len(workerOutput) > 0:
|
||||
rmtree(os.path.join(path, '..', '..'), True)
|
||||
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
|
||||
for file in options.imgOld:
|
||||
if os.path.isfile(file):
|
||||
os.remove(file)
|
||||
else:
|
||||
rmtree(os.path.join(path, '..', '..'), True)
|
||||
raise UserWarning("Source directory is empty.")
|
||||
@@ -592,7 +624,6 @@ def imgFileProcessingTick(output):
|
||||
for page in output:
|
||||
if page is not None:
|
||||
options.imgMetadata[page[0]] = page[1]
|
||||
options.imgOld.append(page[2])
|
||||
if GUI:
|
||||
GUI.progressBarTick.emit('tick')
|
||||
if not GUI.conversionAlive:
|
||||
@@ -608,16 +639,21 @@ def imgFileProcessing(work):
|
||||
workImg = image.ComicPageParser((dirpath, afile), opt)
|
||||
for i in workImg.payload:
|
||||
img = image.ComicPage(opt, *i)
|
||||
is_not_color = not opt.forcecolor or not img.color
|
||||
if is_not_color:
|
||||
img.convertToGrayscale()
|
||||
if opt.cropping == 2 and not opt.webtoon:
|
||||
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
||||
if opt.cropping > 0 and not opt.webtoon:
|
||||
if opt.cropping == 1 and not opt.webtoon:
|
||||
img.cropMargin(opt.croppingp, opt.croppingm)
|
||||
if opt.interpanelcrop > 0:
|
||||
img.cropInterPanelEmptySections("horizontal" if opt.interpanelcrop == 1 else "both")
|
||||
img.autocontrastImage()
|
||||
img.gammaCorrectImage()
|
||||
if is_not_color:
|
||||
img.autocontrastImage()
|
||||
img.resizeImage()
|
||||
img.optimizeForDisplay(opt.reducerainbow)
|
||||
if opt.forcepng and not opt.forcecolor:
|
||||
if is_not_color and opt.forcepng:
|
||||
img.quantizeImage()
|
||||
output.append(img.saveToDir())
|
||||
return output
|
||||
@@ -657,16 +693,14 @@ def getWorkFolder(afile):
|
||||
path = cbx.extract(workdir)
|
||||
sanitizePermissions(path)
|
||||
tdir = os.listdir(workdir)
|
||||
is_nested_single_dir = False
|
||||
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
||||
tdir.remove('ComicInfo.xml')
|
||||
is_nested_single_dir = os.path.isdir(os.path.join(workdir, tdir[0]))
|
||||
if is_nested_single_dir:
|
||||
if os.path.isdir(os.path.join(workdir, tdir[0])):
|
||||
os.replace(
|
||||
os.path.join(workdir, 'ComicInfo.xml'),
|
||||
os.path.join(workdir, tdir[0], 'ComicInfo.xml')
|
||||
)
|
||||
if len(tdir) == 1 and is_nested_single_dir:
|
||||
if len(tdir) == 1 and os.path.isdir(os.path.join(workdir, tdir[0])):
|
||||
path = os.path.join(workdir, tdir[0])
|
||||
except OSError as e:
|
||||
rmtree(workdir, True)
|
||||
@@ -674,8 +708,7 @@ def getWorkFolder(afile):
|
||||
else:
|
||||
raise UserWarning("Failed to open source file/directory.")
|
||||
newpath = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||
copytree(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
||||
rmtree(workdir, True)
|
||||
os.renames(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
||||
return newpath
|
||||
|
||||
|
||||
@@ -716,7 +749,7 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
||||
|
||||
def getComicInfo(path, originalpath):
|
||||
xmlPath = os.path.join(path, 'ComicInfo.xml')
|
||||
options.chapters = []
|
||||
options.comicinfo_chapters = []
|
||||
options.summary = ''
|
||||
titleSuffix = ''
|
||||
if options.title == 'defaulttitle':
|
||||
@@ -739,11 +772,11 @@ def getComicInfo(path, originalpath):
|
||||
except Exception:
|
||||
os.remove(xmlPath)
|
||||
return
|
||||
if xml.data['Title']:
|
||||
options.title = hescape(xml.data['Title'])
|
||||
if options.comicinfotitle:
|
||||
options.title = xml.data['Title']
|
||||
elif defaultTitle:
|
||||
if xml.data['Series']:
|
||||
options.title = hescape(xml.data['Series'])
|
||||
options.title = xml.data['Series']
|
||||
if xml.data['Volume']:
|
||||
titleSuffix += ' V' + xml.data['Volume'].zfill(2)
|
||||
if xml.data['Number']:
|
||||
@@ -753,16 +786,16 @@ def getComicInfo(path, originalpath):
|
||||
options.authors = []
|
||||
for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']:
|
||||
for person in xml.data[field]:
|
||||
options.authors.append(hescape(person))
|
||||
options.authors.append(person)
|
||||
if len(options.authors) > 0:
|
||||
options.authors = list(set(options.authors))
|
||||
options.authors.sort()
|
||||
else:
|
||||
options.authors = ['KCC']
|
||||
if xml.data['Bookmarks']:
|
||||
options.chapters = xml.data['Bookmarks']
|
||||
options.comicinfo_chapters = xml.data['Bookmarks']
|
||||
if xml.data['Summary']:
|
||||
options.summary = hescape(xml.data['Summary'])
|
||||
options.summary = xml.data['Summary']
|
||||
os.remove(xmlPath)
|
||||
|
||||
|
||||
@@ -791,26 +824,45 @@ def getPanelViewSize(deviceres, size):
|
||||
return str(int(x)), str(int(y))
|
||||
|
||||
|
||||
def removeNonImages(filetree):
|
||||
for root, dirs, files in os.walk(filetree):
|
||||
for name in files:
|
||||
_, ext = getImageFileName(name)
|
||||
if ext not in ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.avif'):
|
||||
if os.path.exists(os.path.join(root, name)):
|
||||
os.remove(os.path.join(root, name))
|
||||
# remove empty nested folders
|
||||
for root, dirs, files in os.walk(filetree, False):
|
||||
if not files and not dirs:
|
||||
os.rmdir(root)
|
||||
|
||||
|
||||
def sanitizeTree(filetree):
|
||||
chapterNames = {}
|
||||
for root, dirs, files in os.walk(filetree, False):
|
||||
for i, name in enumerate(os_sorted(files)):
|
||||
splitname = os.path.splitext(name)
|
||||
page = 1
|
||||
cover_path = None
|
||||
for root, dirs, files in os.walk(filetree):
|
||||
files.sort(key=OS_SORT_KEY)
|
||||
for name in files:
|
||||
_, ext = getImageFileName(name)
|
||||
|
||||
# file needs kcc at front AND back to avoid renaming issues
|
||||
slugified = f'kcc-{i:04}'
|
||||
for suffix in '-KCC', '-KCC-A', '-KCC-B', '-KCC-C':
|
||||
if splitname[0].endswith(suffix):
|
||||
slugified += suffix.lower()
|
||||
break
|
||||
# 9999 page limit
|
||||
unique_name = f'kcc-{page:04}'
|
||||
page += 1
|
||||
|
||||
newKey = os.path.join(root, slugified + splitname[1])
|
||||
newKey = os.path.join(root, unique_name + ext)
|
||||
key = os.path.join(root, name)
|
||||
if key != newKey:
|
||||
os.replace(key, newKey)
|
||||
for name in dirs:
|
||||
if not cover_path:
|
||||
cover_path = newKey
|
||||
is_natural_sorted = False
|
||||
if os_sorted(dirs) == sorted(dirs):
|
||||
is_natural_sorted = True
|
||||
dirs.sort(key=OS_SORT_KEY)
|
||||
for i, name in enumerate(dirs):
|
||||
tmpName = name
|
||||
slugified = slugify(name)
|
||||
slugified = slugify(name, is_natural_sorted)
|
||||
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
||||
slugified += "A"
|
||||
chapterNames[slugified] = tmpName
|
||||
@@ -818,7 +870,16 @@ def sanitizeTree(filetree):
|
||||
key = os.path.join(root, name)
|
||||
if key != newKey:
|
||||
os.replace(key, newKey)
|
||||
return chapterNames
|
||||
dirs[i] = newKey
|
||||
return chapterNames, cover_path
|
||||
|
||||
|
||||
def flattenTree(filetree):
|
||||
for root, dirs, files in os.walk(filetree, topdown=False):
|
||||
for name in files:
|
||||
os.rename(os.path.join(root, name), os.path.join(filetree, name))
|
||||
for name in dirs:
|
||||
os.rmdir(os.path.join(root, name))
|
||||
|
||||
|
||||
def sanitizePermissions(filetree):
|
||||
@@ -827,16 +888,30 @@ def sanitizePermissions(filetree):
|
||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
|
||||
for name in dirs:
|
||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
|
||||
# clean dot from original file
|
||||
dot_clean(filetree)
|
||||
|
||||
def dot_clean(filetree):
|
||||
for root, _, files in os.walk(filetree, topdown=False):
|
||||
for name in files:
|
||||
if name.startswith('._'):
|
||||
os.remove(os.path.join(root, name))
|
||||
|
||||
|
||||
def chunk_directory(path):
|
||||
level = -1
|
||||
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||
for f in files:
|
||||
# Windows MAX_LENGTH = 260 plus some buffer
|
||||
if len(os.path.join(root, f)) > 180:
|
||||
flattenTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
level = 1
|
||||
break
|
||||
if getImageFileName(f):
|
||||
newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
|
||||
if level != -1 and level != newLevel:
|
||||
level = 0
|
||||
flattenTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
level = 1
|
||||
break
|
||||
else:
|
||||
level = newLevel
|
||||
@@ -863,11 +938,23 @@ def chunk_process(path, mode, parent):
|
||||
targetSize = 419430400
|
||||
if options.batchsplit == 2 and mode == 2:
|
||||
mode = 3
|
||||
if options.batchsplit == 1 and mode == 2:
|
||||
with os.scandir(path) as it:
|
||||
for entry in it:
|
||||
if not entry.name.startswith('.') and entry.is_dir():
|
||||
if getDirectorySize(os.path.join(path, entry)) > targetSize:
|
||||
flattenTree(path)
|
||||
mode = 1
|
||||
break
|
||||
if mode < 3:
|
||||
for root, dirs, files in walkLevel(path, 0):
|
||||
for name in files if mode == 1 else dirs:
|
||||
if mode == 1:
|
||||
size = os.path.getsize(os.path.join(root, name))
|
||||
size = 0
|
||||
if mode == 1:
|
||||
if 'below' not in name:
|
||||
size = os.path.getsize(os.path.join(root, name))
|
||||
if 'above' in name:
|
||||
size += os.path.getsize(os.path.join(root, name.replace('above', 'below')))
|
||||
else:
|
||||
size = getDirectorySize(os.path.join(root, name))
|
||||
if currentSize + size > targetSize:
|
||||
@@ -890,15 +977,14 @@ def chunk_process(path, mode, parent):
|
||||
firstTome = False
|
||||
return output
|
||||
|
||||
|
||||
def detectCorruption(tmppath, orgpath):
|
||||
def detectSuboptimalProcessing(tmppath, orgpath):
|
||||
imageNumber = 0
|
||||
imageSmaller = 0
|
||||
alreadyProcessed = False
|
||||
for root, _, files in os.walk(tmppath, False):
|
||||
for name in files:
|
||||
if getImageFileName(name) is not None:
|
||||
if not alreadyProcessed and getImageFileName(name)[0].endswith('-kcc'):
|
||||
if not alreadyProcessed and '-kcc' in getImageFileName(name)[0]:
|
||||
alreadyProcessed = True
|
||||
path = os.path.join(root, name)
|
||||
pathOrg = orgpath + path.split('OEBPS' + os.path.sep + 'Images')[1]
|
||||
@@ -907,9 +993,6 @@ def detectCorruption(tmppath, orgpath):
|
||||
raise RuntimeError('Image file %s is corrupted.' % pathOrg)
|
||||
try:
|
||||
img = Image.open(path)
|
||||
img.verify()
|
||||
img = Image.open(path)
|
||||
img.load()
|
||||
imageNumber += 1
|
||||
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
|
||||
imageSmaller += 1
|
||||
@@ -921,7 +1004,8 @@ def detectCorruption(tmppath, orgpath):
|
||||
raise RuntimeError('Image file %s is corrupted. Error: %s' % (pathOrg, str(err)))
|
||||
else:
|
||||
try:
|
||||
os.remove(os.path.join(root, name))
|
||||
if os.path.exists(os.path.join(root, name)):
|
||||
os.remove(os.path.join(root, name))
|
||||
except OSError as e:
|
||||
raise RuntimeError(f"{name}: {e}")
|
||||
if alreadyProcessed:
|
||||
@@ -946,24 +1030,40 @@ def createNewTome(parent):
|
||||
return tomePath, tomePathRoot
|
||||
|
||||
|
||||
def slugify(value):
|
||||
value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||
def slugify(value, is_natural_sorted):
|
||||
if options.format == 'CBZ' and is_natural_sorted:
|
||||
return value
|
||||
if options.format != 'CBZ':
|
||||
# convert all unicode to ascii via slugify
|
||||
value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
||||
if not is_natural_sorted:
|
||||
# pad zeros to numbers
|
||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||
return value
|
||||
|
||||
|
||||
def makeZIP(zipfilename, basedir, isepub=False):
|
||||
start = perf_counter()
|
||||
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||
if isepub:
|
||||
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
||||
for dirpath, _, filenames in os.walk(basedir):
|
||||
for name in filenames:
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
aPath = os.path.normpath(os.path.join(dirpath.replace(basedir, ''), name))
|
||||
if os.path.isfile(path):
|
||||
zipOutput.write(path, aPath)
|
||||
zipOutput.close()
|
||||
if SEVENZIP in available_archive_tools():
|
||||
if isepub:
|
||||
mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w')
|
||||
mimetypeFile.write('application/epub+zip')
|
||||
mimetypeFile.close()
|
||||
subprocess_run([SEVENZIP, 'a', '-tzip', zipfilename, os.path.join(basedir, "*")], capture_output=True, check=True)
|
||||
else:
|
||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||
if isepub:
|
||||
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
||||
for dirpath, _, filenames in os.walk(basedir):
|
||||
for name in filenames:
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
aPath = os.path.normpath(os.path.join(dirpath.replace(basedir, ''), name))
|
||||
if os.path.isfile(path):
|
||||
zipOutput.write(path, aPath)
|
||||
zipOutput.close()
|
||||
end = perf_counter()
|
||||
print(f"makeZIP time: {end - start} seconds")
|
||||
return zipfilename
|
||||
|
||||
|
||||
@@ -999,6 +1099,8 @@ def makeParser():
|
||||
help="Output generated file to specified directory or file")
|
||||
output_options.add_argument("-t", "--title", action="store", dest="title", default="defaulttitle",
|
||||
help="Comic title [Default=filename or directory name]")
|
||||
output_options.add_argument("--comicinfotitle", action="store_true", dest="comicinfotitle", default=False,
|
||||
help="Write Title from ComicInfo.xml")
|
||||
output_options.add_argument("-a", "--author", action="store", dest="author", default="defaultauthor",
|
||||
help="Author name [Default=KCC]")
|
||||
output_options.add_argument("-f", "--format", action="store", dest="format", default="Auto",
|
||||
@@ -1013,6 +1115,8 @@ def makeParser():
|
||||
help="Shift first page to opposite side in landscape for spread alignment")
|
||||
output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False,
|
||||
help="Do not rotate double page spreads in spread splitter option.")
|
||||
output_options.add_argument("--rotatefirst", action="store_true", dest="rotatefirst", default=False,
|
||||
help="Put rotated 2 page spread first in spread splitter option.")
|
||||
|
||||
processing_options.add_argument("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False,
|
||||
help="Do not modify image and ignore any profil or processing option")
|
||||
@@ -1028,6 +1132,8 @@ def makeParser():
|
||||
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
|
||||
processing_options.add_argument("--cp", "--croppingpower", type=float, dest="croppingp", default="1.0",
|
||||
help="Set cropping power [Default=1.0]")
|
||||
processing_options.add_argument("--preservemargin", type=int, dest="preservemargin", default="0",
|
||||
help="After calculating crop, back up specified percentage amount. [Default=0]")
|
||||
processing_options.add_argument("--cm", "--croppingminimum", type=float, dest="croppingm", default="0.0",
|
||||
help="Set cropping minimum area ratio [Default=0.0]")
|
||||
processing_options.add_argument("--ipc", "--interpanelcrop", type=int, dest="interpanelcrop", default="0",
|
||||
@@ -1071,6 +1177,12 @@ def checkOptions(options):
|
||||
options.format = 'EPUB'
|
||||
if options.batchsplit != 2:
|
||||
options.batchsplit = 1
|
||||
if options.format == 'MOBI+EPUB-200MB':
|
||||
options.keep_epub = True
|
||||
options.targetsize = 195
|
||||
options.format = 'MOBI'
|
||||
if options.batchsplit != 2:
|
||||
options.batchsplit = 1
|
||||
if options.format == 'MOBI+EPUB':
|
||||
options.keep_epub = True
|
||||
options.format = 'MOBI'
|
||||
@@ -1097,6 +1209,8 @@ def checkOptions(options):
|
||||
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'K34' or options.profile == 'KDX':
|
||||
options.panelview = False
|
||||
options.hq = False
|
||||
if not options.hq and not options.autoscale:
|
||||
options.panelview = False
|
||||
# Webtoon mode mandatory options
|
||||
if options.webtoon:
|
||||
options.panelview = False
|
||||
@@ -1138,15 +1252,13 @@ def checkTools(source):
|
||||
source = source.upper()
|
||||
if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
|
||||
source.endswith('.ZIP') or source.endswith('.CBZ'):
|
||||
try:
|
||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
||||
except FileNotFoundError:
|
||||
if SEVENZIP not in available_archive_tools():
|
||||
print('ERROR: 7z is missing!')
|
||||
sys.exit(1)
|
||||
if options.format == 'MOBI':
|
||||
try:
|
||||
subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT)
|
||||
except FileNotFoundError:
|
||||
subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, check=True)
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
print('ERROR: KindleGen is missing!')
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1169,19 +1281,59 @@ def checkPre(source):
|
||||
raise UserWarning("Target directory is not writable.")
|
||||
|
||||
|
||||
def makeFusion(sources: List[str]):
|
||||
if len(sources) < 2:
|
||||
raise UserWarning('Fusion requires at least 2 sources. Did you forget to uncheck fusion?')
|
||||
start = perf_counter()
|
||||
first_path = Path(sources[0])
|
||||
if first_path.is_file():
|
||||
fusion_path = first_path.parent.joinpath(first_path.stem + ' [fused]')
|
||||
else:
|
||||
fusion_path = first_path.parent.joinpath(first_path.name + ' [fused]')
|
||||
print("Running Fusion")
|
||||
|
||||
for source in sources:
|
||||
print(f"Processing {source}...")
|
||||
checkPre(source)
|
||||
print("Checking images...")
|
||||
path = getWorkFolder(source)
|
||||
pathfinder = os.path.join(path, "OEBPS", "Images")
|
||||
sanitizeTree(pathfinder)
|
||||
# TODO: remove flattenTree when subchapters are supported
|
||||
flattenTree(pathfinder)
|
||||
source_path = Path(source)
|
||||
if source_path.is_file():
|
||||
os.renames(pathfinder, fusion_path.joinpath(source_path.stem))
|
||||
else:
|
||||
os.renames(pathfinder, fusion_path.joinpath(source_path.name))
|
||||
|
||||
|
||||
end = perf_counter()
|
||||
print(f"makefusion: {end - start} seconds")
|
||||
print("Combined File: "+ str(fusion_path))
|
||||
|
||||
return str(fusion_path)
|
||||
|
||||
|
||||
def makeBook(source, qtgui=None):
|
||||
start = perf_counter()
|
||||
global GUI
|
||||
GUI = qtgui
|
||||
if GUI:
|
||||
GUI.progressBarTick.emit('1')
|
||||
else:
|
||||
checkTools(source)
|
||||
options.kindle_scribe_azw3 = options.profile == 'KS' and ('MOBI' in options.format or 'EPUB' in options.format)
|
||||
checkPre(source)
|
||||
print("Preparing source images...")
|
||||
path = getWorkFolder(source)
|
||||
print("Checking images...")
|
||||
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
|
||||
detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
|
||||
removeNonImages(os.path.join(path, "OEBPS", "Images"))
|
||||
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
||||
chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
cover = image.Cover(cover_path, options)
|
||||
|
||||
if options.webtoon:
|
||||
y = image.ProfileData.Profiles[options.profile][1][1]
|
||||
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui)
|
||||
@@ -1194,7 +1346,6 @@ def makeBook(source, qtgui=None):
|
||||
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
|
||||
if GUI:
|
||||
GUI.progressBarTick.emit('1')
|
||||
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
if options.batchsplit > 0:
|
||||
tomes = chunk_directory(path)
|
||||
else:
|
||||
@@ -1228,10 +1379,10 @@ def makeBook(source, qtgui=None):
|
||||
else:
|
||||
print("Creating EPUB file...")
|
||||
if len(tomes) > 1:
|
||||
buildEPUB(tome, chapterNames, tomeNumber, True)
|
||||
buildEPUB(tome, chapterNames, tomeNumber, True, cover, len(tomes))
|
||||
filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber)))
|
||||
else:
|
||||
buildEPUB(tome, chapterNames, tomeNumber, False)
|
||||
buildEPUB(tome, chapterNames, tomeNumber, False, cover)
|
||||
filepath.append(getOutputFilename(source, options.output, '.epub', ''))
|
||||
makeZIP(tome + '_comic', tome, True)
|
||||
copyfile(tome + '_comic.zip', filepath[-1])
|
||||
@@ -1272,6 +1423,8 @@ def makeBook(source, qtgui=None):
|
||||
elif os.path.isdir(source):
|
||||
rmtree(source)
|
||||
|
||||
end = perf_counter()
|
||||
print(f"makeBook: {end - start} seconds")
|
||||
return filepath
|
||||
|
||||
|
||||
@@ -1305,27 +1458,30 @@ def makeMOBIWorker(item):
|
||||
try:
|
||||
if os.path.getsize(item) < 629145600:
|
||||
output = subprocess_run(['kindlegen', '-dont_append_source', '-locale', 'en', item],
|
||||
stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
|
||||
for line in output.stdout.splitlines():
|
||||
# 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
|
||||
if ":I1036: Mobi file built successfully" in line:
|
||||
break
|
||||
stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True)
|
||||
else:
|
||||
# ERROR: EPUB too big
|
||||
kindlegenErrorCode = 23026
|
||||
return [kindlegenErrorCode, kindlegenError, item]
|
||||
except Exception as err:
|
||||
except CalledProcessError as err:
|
||||
for line in err.stdout.splitlines():
|
||||
# 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
|
||||
if ":I1036: Mobi file built successfully" in line:
|
||||
return [0, '', item]
|
||||
if ":I1037: Mobi file built with WARNINGS!" in line:
|
||||
return [0, '', item]
|
||||
# ERROR: KCC unknown generic error
|
||||
kindlegenErrorCode = 1
|
||||
kindlegenError = format(err)
|
||||
if kindlegenErrorCode == 0:
|
||||
kindlegenErrorCode = err.returncode
|
||||
kindlegenError = err.stdout
|
||||
return [kindlegenErrorCode, kindlegenError, item]
|
||||
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ def splitImage(work):
|
||||
panelImg = imgOrg.crop((0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]))
|
||||
newPage.paste(panelImg, (0, targetHeight))
|
||||
targetHeight += panelsProcessed[panel][2]
|
||||
newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
|
||||
newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber).zfill(4) + '.png'), 'PNG')
|
||||
pageNumber += 1
|
||||
os.remove(filePath)
|
||||
except Exception:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
from functools import cached_property, lru_cache
|
||||
import os
|
||||
import platform
|
||||
import distro
|
||||
@@ -28,6 +28,7 @@ from xml.parsers.expat import ExpatError
|
||||
from .shared import subprocess_run
|
||||
|
||||
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
|
||||
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
|
||||
|
||||
|
||||
class ComicArchive:
|
||||
@@ -39,7 +40,7 @@ class ComicArchive:
|
||||
@cached_property
|
||||
def type(self):
|
||||
extraction_commands = [
|
||||
['7z', 'l', '-y', '-p1', self.filepath],
|
||||
[SEVENZIP, 'l', '-y', '-p1', self.filepath],
|
||||
]
|
||||
|
||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||
@@ -68,14 +69,16 @@ class ComicArchive:
|
||||
|
||||
extraction_commands = [
|
||||
['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.filepath, '-C', targetdir],
|
||||
['7z', 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
|
||||
[SEVENZIP, 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
|
||||
]
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
extraction_commands.append(
|
||||
['unar', self.filepath, '-f', '-o', targetdir]
|
||||
['unar', self.filepath, '-D', '-f', '-o', targetdir]
|
||||
)
|
||||
|
||||
extraction_commands.reverse()
|
||||
|
||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||
extraction_commands.append(
|
||||
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
||||
@@ -84,7 +87,7 @@ class ComicArchive:
|
||||
for cmd in extraction_commands:
|
||||
try:
|
||||
subprocess_run(cmd, capture_output=True, check=True)
|
||||
return targetdir
|
||||
return targetdir
|
||||
except FileNotFoundError:
|
||||
missing.append(cmd[0])
|
||||
except CalledProcessError:
|
||||
@@ -98,13 +101,13 @@ class ComicArchive:
|
||||
def addFile(self, sourcefile):
|
||||
if self.type in ['RAR', 'RAR5']:
|
||||
raise NotImplementedError
|
||||
process = subprocess_run(['7z', 'a', '-y', self.filepath, sourcefile],
|
||||
process = subprocess_run([SEVENZIP, 'a', '-y', self.filepath, sourcefile],
|
||||
stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0:
|
||||
raise OSError('Failed to add the file.')
|
||||
|
||||
def extractMetadata(self):
|
||||
process = subprocess_run(['7z', 'x', '-y', '-so', self.filepath, 'ComicInfo.xml'],
|
||||
process = subprocess_run([SEVENZIP, 'x', '-y', '-so', self.filepath, 'ComicInfo.xml'],
|
||||
stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
@@ -112,3 +115,16 @@ class ComicArchive:
|
||||
return parseString(process.stdout)
|
||||
except ExpatError:
|
||||
return None
|
||||
|
||||
@lru_cache
|
||||
def available_archive_tools():
|
||||
available = []
|
||||
|
||||
for tool in ['tar', SEVENZIP, 'unar', 'unrar']:
|
||||
try:
|
||||
subprocess_run([tool], stdout=PIPE, stderr=STDOUT)
|
||||
available.append(tool)
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
pass
|
||||
|
||||
return available
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import io
|
||||
import os
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from functools import cached_property
|
||||
import mozjpeg_lossless_optimization
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
||||
from .shared import md5Checksum
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter, ImageDraw
|
||||
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
||||
from .inter_panel_crop_alg import crop_empty_inter_panel
|
||||
|
||||
@@ -85,12 +87,14 @@ class ProfileData:
|
||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
||||
'K578': ("Kindle", (600, 800), Palette16, 1.8),
|
||||
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.8),
|
||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
|
||||
'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
|
||||
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.8),
|
||||
}
|
||||
|
||||
ProfilesKindlePDOC = {
|
||||
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.8),
|
||||
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.8),
|
||||
'KO': ("Kindle Oasis 2/3/Paperwhite 12/Colorsoft 12", (1264, 1680), Palette16, 1.8),
|
||||
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
|
||||
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.8),
|
||||
@@ -141,8 +145,12 @@ class ComicPageParser:
|
||||
self.source = source
|
||||
self.size = self.opt.profileData[1]
|
||||
self.payload = []
|
||||
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
|
||||
self.color = self.colorCheck()
|
||||
|
||||
# Detect corruption in source image, let caller catch any exceptions triggered.
|
||||
srcImgPath = os.path.join(source[0], source[1])
|
||||
Image.open(srcImgPath).verify()
|
||||
self.image = Image.open(srcImgPath)
|
||||
|
||||
self.fill = self.fillCheck()
|
||||
# backwards compatibility for Pillow >9.1.0
|
||||
if not hasattr(Image, 'Resampling'):
|
||||
@@ -173,13 +181,13 @@ class ComicPageParser:
|
||||
new_image = Image.new("RGB", (int(width / 2), int(height*2)))
|
||||
new_image.paste(pageone, (0, 0))
|
||||
new_image.paste(pagetwo, (0, height))
|
||||
self.payload.append(['N', self.source, new_image, self.color, self.fill])
|
||||
self.payload.append(['N', self.source, new_image, self.fill])
|
||||
elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
|
||||
and not self.opt.webtoon and self.opt.splitter == 1:
|
||||
spread = self.image
|
||||
if not self.opt.norotate:
|
||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||
self.payload.append(['R', self.source, spread, self.color, self.fill])
|
||||
self.payload.append(['R', self.source, spread, self.fill])
|
||||
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
||||
if self.opt.splitter != 1:
|
||||
if width > height:
|
||||
@@ -194,38 +202,15 @@ class ComicPageParser:
|
||||
else:
|
||||
pageone = self.image.crop(leftbox)
|
||||
pagetwo = self.image.crop(rightbox)
|
||||
self.payload.append(['S1', self.source, pageone, self.color, self.fill])
|
||||
self.payload.append(['S2', self.source, pagetwo, self.color, self.fill])
|
||||
self.payload.append(['S1', self.source, pageone, self.fill])
|
||||
self.payload.append(['S2', self.source, pagetwo, self.fill])
|
||||
if self.opt.splitter > 0:
|
||||
spread = self.image
|
||||
if not self.opt.norotate:
|
||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||
self.payload.append(['R', self.source, spread,
|
||||
self.color, self.fill])
|
||||
self.payload.append(['R', self.source, spread, self.fill])
|
||||
else:
|
||||
self.payload.append(['N', self.source, self.image, self.color, self.fill])
|
||||
|
||||
def colorCheck(self):
|
||||
if self.opt.webtoon:
|
||||
return True
|
||||
else:
|
||||
img = self.image.copy()
|
||||
bands = img.getbands()
|
||||
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
|
||||
thumb = img.resize((40, 40))
|
||||
SSE, bias = 0, [0, 0, 0]
|
||||
bias = ImageStat.Stat(thumb).mean[:3]
|
||||
bias = [b - sum(bias) / 3 for b in bias]
|
||||
for pixel in thumb.getdata():
|
||||
mu = sum(pixel) / 3
|
||||
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
|
||||
MSE = float(SSE) / (40 * 40)
|
||||
if MSE > 22:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
self.payload.append(['N', self.source, self.image, self.fill])
|
||||
|
||||
def fillCheck(self):
|
||||
if self.opt.bordersColor:
|
||||
@@ -267,78 +252,121 @@ class ComicPageParser:
|
||||
|
||||
|
||||
class ComicPage:
|
||||
def __init__(self, options, mode, path, image, color, fill):
|
||||
def __init__(self, options, mode, path, image, fill):
|
||||
self.opt = options
|
||||
_, self.size, self.palette, self.gamma = self.opt.profileData
|
||||
if self.opt.hq:
|
||||
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
||||
self.kindle_scribe_azw3 = (options.profile == 'KS') and (options.format in ('MOBI', 'EPUB'))
|
||||
self.image = image
|
||||
self.color = color
|
||||
self.original_color_mode = image.mode
|
||||
self.image = image.convert("RGB")
|
||||
self.fill = fill
|
||||
self.rotated = False
|
||||
self.orgPath = os.path.join(path[0], path[1])
|
||||
self.targetPathStart = os.path.join(path[0], os.path.splitext(path[1])[0])
|
||||
if 'N' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
|
||||
self.targetPathOrder = '-kcc-x'
|
||||
elif 'R' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
|
||||
self.rotated = True
|
||||
self.targetPathOrder = '-kcc-a' if options.rotatefirst else '-kcc-d'
|
||||
if not options.norotate:
|
||||
self.rotated = True
|
||||
elif 'S1' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-B'
|
||||
self.targetPathOrder = '-kcc-b'
|
||||
elif 'S2' in mode:
|
||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-C'
|
||||
self.targetPathOrder = '-kcc-c'
|
||||
# backwards compatibility for Pillow >9.1.0
|
||||
if not hasattr(Image, 'Resampling'):
|
||||
Image.Resampling = Image
|
||||
|
||||
@cached_property
|
||||
def color(self):
|
||||
if self.original_color_mode in ("L", "1"):
|
||||
return False
|
||||
img = self.image.convert("YCbCr")
|
||||
_, cb, cr = img.split()
|
||||
|
||||
cb_hist = cb.histogram()
|
||||
cr_hist = cr.histogram()
|
||||
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
||||
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
||||
cb_spread = cb_nonzero[-1] - cb_nonzero[0] if len(cb_nonzero) else 0
|
||||
cr_spread = cr_nonzero[-1] - cr_nonzero[0] if len(cr_nonzero) else 0
|
||||
|
||||
SPREAD_THRESHOLD=20
|
||||
if cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def saveToDir(self):
|
||||
try:
|
||||
flags = []
|
||||
if not self.opt.forcecolor and not self.opt.forcepng:
|
||||
self.image = self.image.convert('L')
|
||||
if self.rotated:
|
||||
flags.append('Rotated')
|
||||
if self.fill != 'white':
|
||||
flags.append('BlackBackground')
|
||||
if self.opt.forcepng:
|
||||
self.image.info["transparency"] = None
|
||||
self.targetPath += '.png'
|
||||
self.image.save(self.targetPath, 'PNG', optimize=1)
|
||||
if self.opt.kindle_scribe_azw3 and self.image.size[1] > 1920:
|
||||
w, h = self.image.size
|
||||
targetPath = self.save_with_codec(self.image.crop((0, 0, w, 1920)), self.targetPathStart + self.targetPathOrder + '-above')
|
||||
self.save_with_codec(self.image.crop((0, 1920, w, h)), self.targetPathStart + self.targetPathOrder + '-below')
|
||||
elif self.opt.kindle_scribe_azw3:
|
||||
targetPath = self.save_with_codec(self.image, self.targetPathStart + self.targetPathOrder + '-whole')
|
||||
else:
|
||||
self.targetPath += '.jpg'
|
||||
if self.opt.mozjpeg:
|
||||
with io.BytesIO() as output:
|
||||
self.image.save(output, format="JPEG", optimize=1, quality=85)
|
||||
input_jpeg_bytes = output.getvalue()
|
||||
output_jpeg_bytes = mozjpeg_lossless_optimization.optimize(input_jpeg_bytes)
|
||||
with open(self.targetPath, "wb") as output_jpeg_file:
|
||||
output_jpeg_file.write(output_jpeg_bytes)
|
||||
else:
|
||||
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
|
||||
return [md5Checksum(self.targetPath), flags, self.orgPath]
|
||||
targetPath = self.save_with_codec(self.image, self.targetPathStart + self.targetPathOrder)
|
||||
if os.path.isfile(self.orgPath):
|
||||
os.remove(self.orgPath)
|
||||
return [Path(targetPath).name, flags]
|
||||
except IOError as err:
|
||||
raise RuntimeError('Cannot save image. ' + str(err))
|
||||
|
||||
def autocontrastImage(self):
|
||||
def save_with_codec(self, image, targetPath):
|
||||
if self.opt.forcepng:
|
||||
image.info["transparency"] = None
|
||||
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
|
||||
targetPath += '.gif'
|
||||
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
||||
else:
|
||||
targetPath += '.png'
|
||||
image.save(targetPath, 'PNG', optimize=1)
|
||||
else:
|
||||
targetPath += '.jpg'
|
||||
if self.opt.mozjpeg:
|
||||
with io.BytesIO() as output:
|
||||
image.save(output, format="JPEG", optimize=1, quality=85)
|
||||
input_jpeg_bytes = output.getvalue()
|
||||
output_jpeg_bytes = mozjpeg_lossless_optimization.optimize(input_jpeg_bytes)
|
||||
with open(targetPath, "wb") as output_jpeg_file:
|
||||
output_jpeg_file.write(output_jpeg_bytes)
|
||||
else:
|
||||
image.save(targetPath, 'JPEG', optimize=1, quality=85)
|
||||
return targetPath
|
||||
|
||||
def gammaCorrectImage(self):
|
||||
gamma = self.opt.gamma
|
||||
if gamma < 0.1:
|
||||
gamma = self.gamma
|
||||
if self.gamma != 1.0 and self.color:
|
||||
gamma = 1.0
|
||||
if gamma == 1.0:
|
||||
self.image = ImageOps.autocontrast(self.image)
|
||||
pass
|
||||
else:
|
||||
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: int(255 * (a / 255.) ** gamma)))
|
||||
self.image = Image.eval(self.image, lambda a: int(255 * (a / 255.) ** gamma))
|
||||
|
||||
def autocontrastImage(self):
|
||||
# autocontrast on non grayscale images has unexpected results
|
||||
# since it autocontrasts each color channel separately
|
||||
self.image = ImageOps.autocontrast(self.image)
|
||||
|
||||
def convertToGrayscale(self):
|
||||
self.image = self.image.convert('L')
|
||||
|
||||
def quantizeImage(self):
|
||||
colors = len(self.palette) // 3
|
||||
if colors < 256:
|
||||
self.palette += self.palette[:3] * (256 - colors)
|
||||
# remove all color pixels from image, since colorCheck() has some tolerance
|
||||
# quantize with a small number of color pixels in a mostly b/w image can have unexpected results
|
||||
self.image = self.image.convert("RGB")
|
||||
|
||||
palImg = Image.new('P', (1, 1))
|
||||
palImg.putpalette(self.palette)
|
||||
self.image = self.image.convert('L')
|
||||
self.image = self.image.convert('RGB')
|
||||
# Quantize is deprecated but new function call it internally anyway...
|
||||
self.image = self.image.quantize(palette=palImg)
|
||||
|
||||
def optimizeForDisplay(self, reducerainbow):
|
||||
@@ -350,38 +378,33 @@ class ComicPage:
|
||||
self.image = self.image.filter(unsharpFilter)
|
||||
|
||||
def resizeImage(self):
|
||||
# kindle scribe conversion to mobi is limited in resolution by kindlegen, same with send to kindle and epub
|
||||
if self.kindle_scribe_azw3:
|
||||
self.size = (1440, 1920)
|
||||
ratio_device = float(self.size[1]) / float(self.size[0])
|
||||
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
|
||||
method = self.resize_method()
|
||||
if self.opt.stretch:
|
||||
self.image = self.image.resize(self.size, method)
|
||||
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
||||
if self.opt.format == 'CBZ' or self.opt.kfx:
|
||||
borderw = int((self.size[0] - self.image.size[0]) / 2)
|
||||
borderh = int((self.size[1] - self.image.size[1]) / 2)
|
||||
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
|
||||
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
|
||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||
pass
|
||||
else: # if image bigger than device resolution or smaller with upscaling
|
||||
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||
elif self.opt.format == 'CBZ' or self.opt.kfx:
|
||||
elif (self.opt.format == 'CBZ' or self.opt.kfx) and not self.opt.white_borders:
|
||||
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
||||
else:
|
||||
if self.kindle_scribe_azw3:
|
||||
self.size = (1860, 1920)
|
||||
self.image = ImageOps.contain(self.image, self.size, method=method)
|
||||
|
||||
def resize_method(self):
|
||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
||||
if self.image.size[0] < self.size[0] and self.image.size[1] < self.size[1]:
|
||||
return Image.Resampling.BICUBIC
|
||||
else:
|
||||
return Image.Resampling.LANCZOS
|
||||
|
||||
def maybeCrop(self, box, minimum):
|
||||
w, h = self.image.size
|
||||
left, upper, right, lower = box
|
||||
if self.opt.preservemargin:
|
||||
ratio = 1 - self.opt.preservemargin / 100
|
||||
box = left * ratio, upper * ratio, right + (w - right) * (1 - ratio), lower + (h - lower) * (1 - ratio)
|
||||
box_area = (box[2] - box[0]) * (box[3] - box[1])
|
||||
image_area = self.image.size[0] * self.image.size[1]
|
||||
if (box_area / image_area) >= minimum:
|
||||
@@ -403,14 +426,9 @@ class ComicPage:
|
||||
self.image = crop_empty_inter_panel(self.image, direction, background_color=self.fill)
|
||||
|
||||
class Cover:
|
||||
def __init__(self, source, target, opt, tomeid):
|
||||
def __init__(self, source, opt):
|
||||
self.options = opt
|
||||
self.source = source
|
||||
self.target = target
|
||||
if tomeid == 0:
|
||||
self.tomeid = 1
|
||||
else:
|
||||
self.tomeid = tomeid
|
||||
self.image = Image.open(source)
|
||||
# backwards compatibility for Pillow >9.1.0
|
||||
if not hasattr(Image, 'Resampling'):
|
||||
@@ -422,17 +440,52 @@ class Cover:
|
||||
self.image = ImageOps.autocontrast(self.image)
|
||||
if not self.options.forcecolor:
|
||||
self.image = self.image.convert('L')
|
||||
self.image.thumbnail(self.options.profileData[1], Image.Resampling.LANCZOS)
|
||||
self.save()
|
||||
self.crop_main_cover()
|
||||
|
||||
def save(self):
|
||||
size = list(self.options.profileData[1])
|
||||
if self.options.kindle_scribe_azw3:
|
||||
size[1] = min(size[1], 1920)
|
||||
self.image.thumbnail(tuple(size), Image.Resampling.LANCZOS)
|
||||
|
||||
def crop_main_cover(self):
|
||||
w, h = self.image.size
|
||||
if w / h > 2:
|
||||
if self.options.righttoleft:
|
||||
self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h))
|
||||
else:
|
||||
self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h))
|
||||
elif w / h > 1.3:
|
||||
if self.options.righttoleft:
|
||||
self.image = self.image.crop((0, 0, w/2 - w * 0.03, h))
|
||||
else:
|
||||
self.image = self.image.crop((w/2 + w * 0.03, 0, w, h))
|
||||
|
||||
def save_to_epub(self, target, tomeid, len_tomes=0):
|
||||
try:
|
||||
self.image.save(self.target, "JPEG", optimize=1, quality=85)
|
||||
if tomeid == 0:
|
||||
self.image.save(target, "JPEG", optimize=1, quality=85)
|
||||
else:
|
||||
copy = self.image.copy()
|
||||
draw = ImageDraw.Draw(copy)
|
||||
w, h = copy.size
|
||||
draw.text(
|
||||
xy=(w/2, h * .85),
|
||||
text=f'{tomeid}/{len_tomes}',
|
||||
anchor='ms',
|
||||
font_size=h//7,
|
||||
fill=255,
|
||||
stroke_fill=0,
|
||||
stroke_width=25
|
||||
)
|
||||
copy.save(target, "JPEG", optimize=1, quality=85)
|
||||
dot_cover = Path(target).with_stem('._' + Path(target).stem)
|
||||
if os.path.exists(dot_cover):
|
||||
os.remove(dot_cover)
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to save cover.')
|
||||
|
||||
def saveToKindle(self, kindle, asin):
|
||||
self.image = self.image.resize((300, 470), Image.Resampling.LANCZOS)
|
||||
self.image = ImageOps.contain(self.image, (300, 470), Image.Resampling.LANCZOS)
|
||||
try:
|
||||
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
|
||||
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=85)
|
||||
|
||||
@@ -20,6 +20,7 @@ import os
|
||||
from xml.dom.minidom import parse, Document
|
||||
from tempfile import mkdtemp
|
||||
from shutil import rmtree
|
||||
from xml.sax.saxutils import unescape
|
||||
from . import comicarchive
|
||||
|
||||
|
||||
@@ -52,19 +53,19 @@ class MetadataParser:
|
||||
|
||||
def parseXML(self):
|
||||
if len(self.rawdata.getElementsByTagName('Series')) != 0:
|
||||
self.data['Series'] = self.rawdata.getElementsByTagName('Series')[0].firstChild.nodeValue
|
||||
self.data['Series'] = unescape(self.rawdata.getElementsByTagName('Series')[0].firstChild.nodeValue)
|
||||
if len(self.rawdata.getElementsByTagName('Volume')) != 0:
|
||||
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
|
||||
if len(self.rawdata.getElementsByTagName('Number')) != 0:
|
||||
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
|
||||
if len(self.rawdata.getElementsByTagName('Summary')) != 0:
|
||||
self.data['Summary'] = self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue
|
||||
self.data['Summary'] = unescape(self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue)
|
||||
if len(self.rawdata.getElementsByTagName('Title')) != 0:
|
||||
self.data['Title'] = self.rawdata.getElementsByTagName('Title')[0].firstChild.nodeValue
|
||||
self.data['Title'] = unescape(self.rawdata.getElementsByTagName('Title')[0].firstChild.nodeValue)
|
||||
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
|
||||
if len(self.rawdata.getElementsByTagName(field)) != 0:
|
||||
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
|
||||
self.data[field + 's'].append(person)
|
||||
self.data[field + 's'].append(unescape(person))
|
||||
self.data[field + 's'] = list(set(self.data[field + 's']))
|
||||
self.data[field + 's'].sort()
|
||||
if len(self.rawdata.getElementsByTagName('Page')) != 0:
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from hashlib import md5
|
||||
from html.parser import HTMLParser
|
||||
import subprocess
|
||||
from packaging.version import Version
|
||||
@@ -49,8 +48,6 @@ class HTMLStripper(HTMLParser):
|
||||
def getImageFileName(imgfile):
|
||||
name, ext = os.path.splitext(imgfile)
|
||||
ext = ext.lower()
|
||||
if (name.startswith('.') and len(name) == 1) or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.j2k', '.jpx']:
|
||||
return None
|
||||
return [name, ext]
|
||||
|
||||
|
||||
@@ -74,16 +71,6 @@ def walkLevel(some_dir, level=1):
|
||||
del dirs[:]
|
||||
|
||||
|
||||
def md5Checksum(fpath):
|
||||
with open(fpath, 'rb') as fh:
|
||||
m = md5()
|
||||
while True:
|
||||
data = fh.read(8192)
|
||||
if not data:
|
||||
break
|
||||
m.update(data)
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
def sanitizeTrace(traceback):
|
||||
return ''.join(format_tb(traceback))\
|
||||
@@ -129,10 +116,10 @@ def dependencyCheck(level):
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
try:
|
||||
from PIL import __version__ as pillowVersion
|
||||
if Version('5.2.0') > Version(pillowVersion):
|
||||
missing.append('Pillow 5.2.0+')
|
||||
if Version('11.3.0') > Version(pillowVersion):
|
||||
missing.append('Pillow 11.3.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 5.2.0+')
|
||||
missing.append('Pillow 11.3.0+')
|
||||
if len(missing) > 0:
|
||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
PySide6>=6.5.1
|
||||
Pillow>=5.2.0
|
||||
Pillow>=11.3.0
|
||||
psutil>=5.9.5
|
||||
requests>=2.31.0
|
||||
python-slugify>=1.2.1
|
||||
raven>=6.0.0
|
||||
packaging>=23.2
|
||||
mozjpeg-lossless-optimization>=1.1.2
|
||||
mozjpeg-lossless-optimization>=1.2.0
|
||||
natsort>=8.4.0
|
||||
distro>=1.8.0
|
||||
numpy>=1.22.4,<2.0.0
|
||||
numpy>=1.22.4
|
||||
|
||||
4
setup.py
4
setup.py
@@ -75,7 +75,7 @@ setuptools.setup(
|
||||
packages=['kindlecomicconverter'],
|
||||
install_requires=[
|
||||
'pyside6>=6.5.1',
|
||||
'Pillow>=5.2.0',
|
||||
'Pillow>=11.3.0',
|
||||
'psutil>=5.9.5',
|
||||
'python-slugify>=1.2.1,<9.0.0',
|
||||
'raven>=6.0.0',
|
||||
@@ -83,7 +83,7 @@ setuptools.setup(
|
||||
'mozjpeg-lossless-optimization>=1.1.2',
|
||||
'natsort>=8.4.0',
|
||||
'distro',
|
||||
'numpy>=1.22.4,<2.0.0'
|
||||
'numpy>=1.22.4'
|
||||
],
|
||||
classifiers=[],
|
||||
zip_safe=False,
|
||||
|
||||
Reference in New Issue
Block a user