mirror of
https://github.com/ciromattia/kcc
synced 2026-04-17 14:38:47 +00:00
Compare commits
304 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3a3e9b3c2 | ||
|
|
380dc5c42c | ||
|
|
8ab7520754 | ||
|
|
3cd6e09bcb | ||
|
|
cb5f4db5c4 | ||
|
|
f1ffb2c4e8 | ||
|
|
26327728d0 | ||
|
|
61bfb0a51f | ||
|
|
2f03119926 | ||
|
|
8f08c63f4e | ||
|
|
eb24a400b4 | ||
|
|
cc2eb9dcf3 | ||
|
|
d1a443b3d8 | ||
|
|
f8c35ce634 | ||
|
|
580a800d25 | ||
|
|
023797f012 | ||
|
|
63a18627d3 | ||
|
|
0d967084a0 | ||
|
|
2da5b11858 | ||
|
|
f6d10337d8 | ||
|
|
cf047ecf6f | ||
|
|
e7ee8bed9d | ||
|
|
9c8a1759cf | ||
|
|
6299754964 | ||
|
|
a3db86a29b | ||
|
|
67714a9b06 | ||
|
|
95f9a3cda9 | ||
|
|
0e12fc30c6 | ||
|
|
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 | ||
|
|
b35a2baf05 | ||
|
|
11a395e983 | ||
|
|
2e39a8c227 | ||
|
|
02535421a0 | ||
|
|
3d4fae62d8 | ||
|
|
2b550b8b98 | ||
|
|
ecee7cf6f5 | ||
|
|
b0a5558da1 | ||
|
|
1b487c18d6 | ||
|
|
a3546d19c3 | ||
|
|
2f703ef92c | ||
|
|
4fb993b38b | ||
|
|
1401f94c1f | ||
|
|
70d10204ee | ||
|
|
a9d0c57ba6 | ||
|
|
5598596650 | ||
|
|
4b7b6d1c58 | ||
|
|
075bc748d1 | ||
|
|
9e318ed33e | ||
|
|
6d21bfa6fa | ||
|
|
132574d57d | ||
|
|
317fb33fd0 | ||
|
|
2189f4b1cb | ||
|
|
462a3cebde | ||
|
|
2a414ef936 | ||
|
|
4adb998896 | ||
|
|
315b6e150d | ||
|
|
5875508597 | ||
|
|
0a4ef31daf | ||
|
|
c99df3308e | ||
|
|
e9482fbd6c | ||
|
|
434fe90b00 | ||
|
|
73a91ec0ae | ||
|
|
e0b1848e09 | ||
|
|
a9360e6bc3 | ||
|
|
ae475e709a | ||
|
|
4769f68265 | ||
|
|
dbe6043542 | ||
|
|
e1a318145d | ||
|
|
a71523b2d4 | ||
|
|
7a5473f530 | ||
|
|
f5a5624112 | ||
|
|
3b7e8dc9a5 | ||
|
|
e637f37ef0 | ||
|
|
6ba690659f | ||
|
|
50eb48fb9b | ||
|
|
4a661a1a17 | ||
|
|
c26383c4b5 | ||
|
|
4e6ee8b59b | ||
|
|
6c6f591e45 | ||
|
|
78c014bf22 | ||
|
|
421e6bcb64 | ||
|
|
5168cd73c4 | ||
|
|
f7f19b99da | ||
|
|
1131bab41f | ||
|
|
8d204668a7 | ||
|
|
99d94ceaa7 | ||
|
|
8ff401cc3a | ||
|
|
fb7d92d737 | ||
|
|
1ca8b2c11b | ||
|
|
40e0b4853b | ||
|
|
005313f978 | ||
|
|
add2ef9faa | ||
|
|
0c98acd606 | ||
|
|
fe902ec213 | ||
|
|
4e9714e6f8 | ||
|
|
1337ad7fe3 | ||
|
|
511c7c1580 | ||
|
|
16dd034af4 | ||
|
|
d22794555f | ||
|
|
ab089adbca | ||
|
|
2c770f4562 | ||
|
|
4c73006bd8 | ||
|
|
1093dbf65a | ||
|
|
df9990c692 | ||
|
|
114f4c9e57 | ||
|
|
c2c477475d | ||
|
|
a0f8d0b5cf |
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']
|
||||
3
.github/workflows/package-linux.yml
vendored
3
.github/workflows/package-linux.yml
vendored
@@ -23,7 +23,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
@@ -70,6 +70,5 @@ jobs:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
CHANGELOG.md
|
||||
LICENSE.txt
|
||||
*.AppImage*
|
||||
|
||||
3
.github/workflows/package-macos.yml
vendored
3
.github/workflows/package-macos.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-12, macos-14 ]
|
||||
os: [ macos-13, macos-14 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -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,5 +1,5 @@
|
||||
# Select final stage based on TARGETARCH ARG
|
||||
FROM ghcr.io/ciromattia/kcc:docker-base-20230809
|
||||
FROM ghcr.io/ciromattia/kcc:docker-base-20241116
|
||||
LABEL com.kcc.name="Kindle Comic Converter"
|
||||
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
||||
LABEL org.opencontainers.image.description='Kindle Comic Converter'
|
||||
@@ -16,4 +16,4 @@ COPY . /opt/kcc
|
||||
RUN cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
||||
|
||||
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
|
||||
CMD ["-h"]
|
||||
CMD ["-h"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 python:3.11-slim-bullseye as compile-amd64
|
||||
FROM --platform=linux/amd64 python:3.13-slim-bullseye as compile-amd64
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@@ -16,7 +16,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||
|
||||
######################################################################################
|
||||
|
||||
FROM --platform=linux/arm64 python:3.11-slim-bullseye as compile-arm64
|
||||
FROM --platform=linux/arm64 python:3.13-slim-bullseye as compile-arm64
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@@ -28,6 +28,9 @@ ENV LC_ALL=C.UTF-8 \
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY requirements.txt /opt/kcc/
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
RUN set -x && \
|
||||
TEMP_PACKAGES=() && \
|
||||
KEPT_PACKAGES=() && \
|
||||
@@ -64,14 +67,13 @@ RUN set -x && \
|
||||
&& \
|
||||
# Install required python modules
|
||||
python -m pip install --upgrade pip && \
|
||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
||||
python -m venv /opt/venv && \
|
||||
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
||||
python -m pip install -r /opt/kcc/requirements.txt
|
||||
|
||||
|
||||
######################################################################################
|
||||
|
||||
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as compile-armv7
|
||||
FROM --platform=linux/arm/v7 python:3.13-slim-bullseye as compile-armv7
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@@ -83,6 +85,9 @@ ENV LC_ALL=C.UTF-8 \
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY requirements.txt /opt/kcc/
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
RUN set -x && \
|
||||
TEMP_PACKAGES=() && \
|
||||
KEPT_PACKAGES=() && \
|
||||
@@ -120,19 +125,18 @@ RUN set -x && \
|
||||
&& \
|
||||
# Install required python modules
|
||||
python -m pip install --upgrade pip && \
|
||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
||||
python -m venv /opt/venv && \
|
||||
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
||||
python -m pip install --upgrade pillow psutil requests python-slugify raven packaging mozjpeg-lossless-optimization natsort distro numpy
|
||||
|
||||
|
||||
######################################################################################
|
||||
FROM --platform=linux/amd64 python:3.11-slim-bullseye as build-amd64
|
||||
FROM --platform=linux/amd64 python:3.13-slim-bullseye as build-amd64
|
||||
COPY --from=compile-amd64 /opt/venv /opt/venv
|
||||
|
||||
FROM --platform=linux/arm64 python:3.11-slim-bullseye as build-arm64
|
||||
FROM --platform=linux/arm64 python:3.13-slim-bullseye as build-arm64
|
||||
COPY --from=compile-arm64 /opt/venv /opt/venv
|
||||
|
||||
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as build-armv7
|
||||
FROM --platform=linux/arm/v7 python:3.13-slim-bullseye as build-armv7
|
||||
COPY --from=compile-armv7 /opt/venv /opt/venv
|
||||
######################################################################################
|
||||
|
||||
@@ -156,5 +160,5 @@ WORKDIR /app
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||
apt-get install -y p7zip-full unrar-free && \
|
||||
ln -s /app/kindlegen /bin/kindlegen && \
|
||||
echo docker-base-20230809 > /IMAGE_VERSION
|
||||
echo docker-base-20241116 > /IMAGE_VERSION
|
||||
|
||||
|
||||
@@ -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
|
||||
177
README.md
177
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:
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||
- [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
||||
- Paweł Jastrzębski:
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||
- [](https://jastrzeb.ski/donate/)
|
||||
- Alex Xu
|
||||
- [](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,31 @@ 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
|
||||
|
||||
- [Kindle Scribe cover guide](https://github.com/ciromattia/kcc/issues/508) (also works for older Kindles)
|
||||
- Should I use Calibre?
|
||||
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output in Calibre will break the formatting.
|
||||
Viewing KCC output in Calibre will also not work properly.
|
||||
On 7th gen and later Kindles running firmware 5.16.3+, you can get cover thumbnails simply by USB dropping into documents folder.
|
||||
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
|
||||
If you are careful to not modify the file however, you can still use Calibre, but direct USB dropping is reccomended.
|
||||
- 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
|
||||
|
||||
@@ -62,17 +131,19 @@ You'll need to install various tools to access important but optional features.
|
||||
|
||||
### KindleGen
|
||||
|
||||
#### Windows / macOS KindleGen
|
||||
On Windows and macOS, install [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011) and `kindlegen` will be autodetected from it.
|
||||
|
||||
Install [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011) and `kindlegen` will be autodetected from it.
|
||||
|
||||
If you have issues detecting it, or use another OS, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#kindlegen
|
||||
If you have issues detecting it, get stuck on the MOBI conversion step, or use Linux AppImage or Flatpak, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#kindlegen
|
||||
|
||||
### 7-Zip
|
||||
|
||||
This is an optional requirement as of KCC 6.1.0. You only need to install it if 1) you are using advanced features, 2) are using Windows 10 (2017) or earlier, or 3) need to use an older KCC version.
|
||||
This is optional but will make conversions much faster.
|
||||
|
||||
If you need to install it, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
||||
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
|
||||
|
||||
## INPUT FORMATS
|
||||
**KCC** can understand and convert, at the moment, the following input types:
|
||||
@@ -102,12 +173,14 @@ 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", (1264, 1680), 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),
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
|
||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
|
||||
@@ -124,6 +197,9 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
|
||||
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
||||
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8),
|
||||
'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.8),
|
||||
'Rmk1': ("reMarkable 1", (1404, 1872), Palette16, 1.8),
|
||||
'Rmk2': ("reMarkable 2", (1404, 1872), Palette16, 1.8),
|
||||
'RmkPP': ("reMarkable Paper Pro", (1620, 2160), Palette16, 1.8),
|
||||
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
||||
```
|
||||
|
||||
@@ -137,7 +213,8 @@ MANDATORY:
|
||||
|
||||
MAIN:
|
||||
-p PROFILE, --profile PROFILE
|
||||
Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS, KoE) [Default=KV]
|
||||
Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoCC, KoL, KoLC, KoF, KoS, KoE)
|
||||
[Default=KV]
|
||||
-m, --manga-style Manga style (right-to-left reading and splitting)
|
||||
-q, --hq Try to increase the quality of magnification
|
||||
-2, --two-panel Display two not four panels in Panel View mode
|
||||
@@ -153,12 +230,16 @@ PROCESSING:
|
||||
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
|
||||
-g GAMMA, --gamma GAMMA
|
||||
Apply gamma correction to linearize the image [Default=Auto]
|
||||
--autolevel Set most common dark pixel value to be black point for leveling.
|
||||
-c CROPPING, --cropping CROPPING
|
||||
Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
|
||||
--cp CROPPINGP, --croppingpower CROPPINGP
|
||||
Set cropping power [Default=1.0]
|
||||
--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
|
||||
Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]
|
||||
--blackborders Disable autodetection and force black borders
|
||||
--whiteborders Disable autodetection and force white borders
|
||||
--forcecolor Don't convert images to grayscale
|
||||
@@ -172,10 +253,18 @@ 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
|
||||
Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) [Default=Auto]
|
||||
--nokepub If format is EPUB, output file with '.epub' extension rather than '.kepub.epub'
|
||||
-b BATCHSPLIT, --batchsplit BATCHSPLIT
|
||||
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
|
||||
--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.
|
||||
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
||||
|
||||
CUSTOM PROFILE:
|
||||
--customwidth CUSTOMWIDTH
|
||||
@@ -211,15 +300,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
|
||||
|
||||
@@ -238,8 +337,16 @@ venv\Scripts\activate.bat
|
||||
python kcc.py
|
||||
```
|
||||
|
||||
You can build a `.exe` of KCC like the downloads we offer with
|
||||
|
||||
```
|
||||
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
|
||||
@@ -255,6 +362,12 @@ source venv/bin/activate
|
||||
python kcc.py
|
||||
```
|
||||
|
||||
You can build a `.app` of KCC like the downloads we offer with
|
||||
|
||||
```
|
||||
python setup.py build_binary
|
||||
```
|
||||
|
||||
## CREDITS
|
||||
**KCC** is made by
|
||||
|
||||
@@ -272,6 +385,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)
|
||||
@@ -291,5 +410,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,12 +4,12 @@ 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
|
||||
- distro
|
||||
- natsort[fast]>=8.4.0
|
||||
- natsort>=8.4.0
|
||||
- pip
|
||||
- pip:
|
||||
- mozjpeg-lossless-optimization>=1.1.2
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pyside6-uic gui/KCC.ui > kindlecomicconverter/KCC_ui.py
|
||||
pyside6-uic gui/MetaEditor.ui > kindlecomicconverter/KCC_ui_editor.py
|
||||
pyside6-uic gui/KCC.ui --from-imports > kindlecomicconverter/KCC_ui.py
|
||||
pyside6-uic gui/MetaEditor.ui --from-imports > kindlecomicconverter/KCC_ui_editor.py
|
||||
pyside6-rcc gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<file>../icons/Kobo.png</file>
|
||||
<file>../icons/Other.png</file>
|
||||
<file>../icons/Kindle.png</file>
|
||||
<file>../icons/Rmk.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="Formats">
|
||||
<file>../icons/CBZ.png</file>
|
||||
@@ -26,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>
|
||||
|
||||
1102
gui/KCC.ui
1102
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/Rmk.png
Normal file
BIN
icons/Rmk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 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 |
@@ -20,6 +20,8 @@
|
||||
|
||||
import sys
|
||||
|
||||
from kcc import modify_path
|
||||
|
||||
if sys.version_info < (3, 8, 0):
|
||||
print('ERROR: This is a Python 3.8+ script!')
|
||||
sys.exit(1)
|
||||
@@ -28,6 +30,7 @@ from multiprocessing import freeze_support, set_start_method
|
||||
from kindlecomicconverter.startup import startC2E
|
||||
|
||||
if __name__ == "__main__":
|
||||
modify_path()
|
||||
set_start_method('spawn')
|
||||
freeze_support()
|
||||
startC2E()
|
||||
|
||||
@@ -8,10 +8,10 @@ a = Analysis(['kcc-c2e.py'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
excludes=['pkg_resources'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
import sys
|
||||
|
||||
from kcc import modify_path
|
||||
|
||||
if sys.version_info < (3, 8, 0):
|
||||
print('ERROR: This is a Python 3.8+ script!')
|
||||
sys.exit(1)
|
||||
@@ -28,6 +30,7 @@ from multiprocessing import freeze_support, set_start_method
|
||||
from kindlecomicconverter.startup import startC2P
|
||||
|
||||
if __name__ == "__main__":
|
||||
modify_path()
|
||||
set_start_method('spawn')
|
||||
freeze_support()
|
||||
startC2P()
|
||||
|
||||
@@ -8,10 +8,10 @@ a = Analysis(['kcc-c2p.py'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
excludes=['pkg_resources'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
|
||||
104
kcc.py
104
kcc.py
@@ -18,62 +18,76 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
if sys.version_info < (3, 8, 0):
|
||||
print('ERROR: This is a Python 3.8+ script!')
|
||||
sys.exit(1)
|
||||
|
||||
# OS specific workarounds
|
||||
import os
|
||||
if sys.platform.startswith('darwin'):
|
||||
# prioritize KC2 since it optionally also installs KP3
|
||||
mac_paths = [
|
||||
'/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS',
|
||||
'/Applications/Kindle Previewer 3.app/Contents/lib/fc/bin/',
|
||||
]
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths +
|
||||
[
|
||||
'/opt/homebrew/bin',
|
||||
'/usr/local/bin',
|
||||
'/usr/bin',
|
||||
'/bin',
|
||||
]
|
||||
)
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
else:
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths)
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
elif sys.platform.startswith('win'):
|
||||
# prioritize KC2 since it optionally also installs KP3
|
||||
win_paths = [
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\KC2'),
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\Kindle Previewer 3\\lib\\fc\\bin\\'),
|
||||
'C:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'D:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'E:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'C:\\Program Files\\7-Zip',
|
||||
'D:\\Program Files\\7-Zip',
|
||||
'E:\\Program Files\\7-Zip',
|
||||
]
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(win_paths)
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
else:
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(win_paths)
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
# Load additional Sentry configuration
|
||||
# if getattr(sys, 'frozen', False):
|
||||
# try:
|
||||
# import kindlecomicconverter.sentry
|
||||
# except ImportError:
|
||||
# pass
|
||||
def modify_path():
|
||||
if platform.system() == 'Darwin':
|
||||
mac_paths = [
|
||||
'/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS',
|
||||
'/Applications/Kindle Previewer 3.app/Contents/lib/fc/bin/',
|
||||
]
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths +
|
||||
[
|
||||
'/opt/homebrew/bin',
|
||||
'/usr/local/bin',
|
||||
'/usr/bin',
|
||||
'/bin',
|
||||
]
|
||||
)
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
else:
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths)
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
elif platform.system() == 'Linux':
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(
|
||||
[
|
||||
str(Path.home() / ".local" / "bin"),
|
||||
'/opt/homebrew/bin',
|
||||
'/usr/local/bin',
|
||||
'/usr/bin',
|
||||
'/bin',
|
||||
]
|
||||
)
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
else:
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
elif platform.system() == 'Windows':
|
||||
win_paths = [
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\KC2'),
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\Kindle Previewer 3\\lib\\fc\\bin\\'),
|
||||
os.path.expandvars('%UserProfile%\\Kindle Previewer 3\\lib\\fc\\bin\\'),
|
||||
'C:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'D:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'E:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'C:\\Program Files\\7-Zip',
|
||||
'D:\\Program Files\\7-Zip',
|
||||
'E:\\Program Files\\7-Zip',
|
||||
]
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(win_paths)
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
else:
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(win_paths)
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
from multiprocessing import freeze_support, set_start_method
|
||||
from kindlecomicconverter.startup import start
|
||||
|
||||
if __name__ == "__main__":
|
||||
modify_path()
|
||||
set_start_method('spawn')
|
||||
freeze_support()
|
||||
start()
|
||||
|
||||
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,26 +16,32 @@
|
||||
# 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)
|
||||
from PySide6.QtNetwork import (QLocalSocket, QLocalServer)
|
||||
|
||||
import os
|
||||
import re
|
||||
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
|
||||
# noinspection PyUnresolvedReferences
|
||||
from PySide6 import QtGui, QtCore, QtWidgets, QtNetwork
|
||||
from PySide6.QtCore import Qt
|
||||
from xml.sax.saxutils import escape
|
||||
from psutil import Process
|
||||
from copy import copy
|
||||
from distutils.version import StrictVersion
|
||||
from packaging.version import Version
|
||||
from raven import Client
|
||||
from tempfile import gettempdir
|
||||
|
||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run_silent
|
||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
|
||||
from .comicarchive import SEVENZIP, available_archive_tools
|
||||
from . import __version__
|
||||
from . import comic2ebook
|
||||
from . import metadata
|
||||
@@ -44,18 +50,18 @@ from . import KCC_ui
|
||||
from . import KCC_ui_editor
|
||||
|
||||
|
||||
class QApplicationMessaging(QtWidgets.QApplication):
|
||||
messageFromOtherInstance = QtCore.Signal(bytes)
|
||||
class QApplicationMessaging(QApplication):
|
||||
messageFromOtherInstance = Signal(bytes)
|
||||
|
||||
def __init__(self, argv):
|
||||
QtWidgets.QApplication.__init__(self, argv)
|
||||
QApplication.__init__(self, argv)
|
||||
self._key = 'KCC'
|
||||
self._timeout = 1000
|
||||
self._locked = False
|
||||
socket = QtNetwork.QLocalSocket(self)
|
||||
socket.connectToServer(self._key, QtCore.QIODeviceBase.OpenModeFlag.WriteOnly)
|
||||
socket = QLocalSocket(self)
|
||||
socket.connectToServer(self._key, QIODeviceBase.OpenModeFlag.WriteOnly)
|
||||
if not socket.waitForConnected(self._timeout):
|
||||
self._server = QtNetwork.QLocalServer(self)
|
||||
self._server = QLocalServer(self)
|
||||
self._server.newConnection.connect(self.handleMessage)
|
||||
self._server.listen(self._key)
|
||||
else:
|
||||
@@ -67,11 +73,11 @@ class QApplicationMessaging(QtWidgets.QApplication):
|
||||
self._server.close()
|
||||
|
||||
def event(self, e):
|
||||
if e.type() == QtCore.QEvent.Type.FileOpen:
|
||||
if e.type() == QEvent.Type.FileOpen:
|
||||
self.messageFromOtherInstance.emit(bytes(e.file(), 'UTF-8'))
|
||||
return True
|
||||
else:
|
||||
return QtWidgets.QApplication.event(self, e)
|
||||
return QApplication.event(self, e)
|
||||
|
||||
def isRunning(self):
|
||||
return self._locked
|
||||
@@ -82,54 +88,58 @@ class QApplicationMessaging(QtWidgets.QApplication):
|
||||
self.messageFromOtherInstance.emit(socket.readAll().data())
|
||||
|
||||
def sendMessage(self, message):
|
||||
socket = QtNetwork.QLocalSocket(self)
|
||||
socket.connectToServer(self._key, QtCore.QIODeviceBase.OpenModeFlag.WriteOnly)
|
||||
socket = QLocalSocket(self)
|
||||
socket.connectToServer(self._key, QIODeviceBase.OpenModeFlag.WriteOnly)
|
||||
socket.waitForConnected(self._timeout)
|
||||
socket.write(bytes(message, 'UTF-8'))
|
||||
socket.waitForBytesWritten(self._timeout)
|
||||
socket.disconnectFromServer()
|
||||
|
||||
|
||||
class QMainWindowKCC(QtWidgets.QMainWindow):
|
||||
progressBarTick = QtCore.Signal(str)
|
||||
modeConvert = QtCore.Signal(int)
|
||||
addMessage = QtCore.Signal(str, str, bool)
|
||||
addTrayMessage = QtCore.Signal(str, str)
|
||||
showDialog = QtCore.Signal(str, str)
|
||||
hideProgressBar = QtCore.Signal()
|
||||
forceShutdown = QtCore.Signal()
|
||||
class QMainWindowKCC(QMainWindow):
|
||||
progressBarTick = Signal(str)
|
||||
modeConvert = Signal(int)
|
||||
addMessage = Signal(str, str, bool)
|
||||
addTrayMessage = Signal(str, str)
|
||||
showDialog = Signal(str, str)
|
||||
hideProgressBar = Signal()
|
||||
forceShutdown = Signal()
|
||||
|
||||
|
||||
class Icons:
|
||||
def __init__(self):
|
||||
self.deviceKindle = QtGui.QIcon()
|
||||
self.deviceKindle.addPixmap(QtGui.QPixmap(":/Devices/icons/Kindle.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.deviceKobo = QtGui.QIcon()
|
||||
self.deviceKobo.addPixmap(QtGui.QPixmap(":/Devices/icons/Kobo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.deviceOther = QtGui.QIcon()
|
||||
self.deviceOther.addPixmap(QtGui.QPixmap(":/Devices/icons/Other.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.deviceKindle = QIcon()
|
||||
self.deviceKindle.addPixmap(QPixmap(":/Devices/icons/Kindle.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.deviceKobo = QIcon()
|
||||
self.deviceKobo.addPixmap(QPixmap(":/Devices/icons/Kobo.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.deviceRmk = QIcon()
|
||||
self.deviceRmk.addPixmap(QPixmap(":/Devices/icons/Rmk.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.deviceOther = QIcon()
|
||||
self.deviceOther.addPixmap(QPixmap(":/Devices/icons/Other.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
self.MOBIFormat = QtGui.QIcon()
|
||||
self.MOBIFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/MOBI.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.CBZFormat = QtGui.QIcon()
|
||||
self.CBZFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/CBZ.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.EPUBFormat = QtGui.QIcon()
|
||||
self.EPUBFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/EPUB.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.MOBIFormat = QIcon()
|
||||
self.MOBIFormat.addPixmap(QPixmap(":/Formats/icons/MOBI.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.CBZFormat = QIcon()
|
||||
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 = QtGui.QIcon()
|
||||
self.info.addPixmap(QtGui.QPixmap(":/Status/icons/info.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.warning = QtGui.QIcon()
|
||||
self.warning.addPixmap(QtGui.QPixmap(":/Status/icons/warning.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.error = QtGui.QIcon()
|
||||
self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.info = QIcon()
|
||||
self.info.addPixmap(QPixmap(":/Status/icons/info.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.warning = QIcon()
|
||||
self.warning.addPixmap(QPixmap(":/Status/icons/warning.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.error = QIcon()
|
||||
self.error.addPixmap(QPixmap(":/Status/icons/error.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
self.programIcon = QtGui.QIcon()
|
||||
self.programIcon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
self.programIcon = QIcon()
|
||||
self.programIcon.addPixmap(QPixmap(":/Icon/icons/comic2ebook.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
|
||||
class VersionThread(QtCore.QThread):
|
||||
class VersionThread(QThread):
|
||||
def __init__(self):
|
||||
QtCore.QThread.__init__(self)
|
||||
QThread.__init__(self)
|
||||
self.newVersion = ''
|
||||
self.md5 = ''
|
||||
self.barProgress = 0
|
||||
@@ -146,9 +156,9 @@ class VersionThread(QtCore.QThread):
|
||||
latest_version = json_parser["tag_name"]
|
||||
latest_version = re.sub(r'^v', "", latest_version)
|
||||
|
||||
if ("b" not in __version__ and StrictVersion(latest_version) > StrictVersion(__version__)) \
|
||||
if ("b" not in __version__ and Version(latest_version) > Version(__version__)) \
|
||||
or ("b" in __version__
|
||||
and StrictVersion(latest_version) >= StrictVersion(re.sub(r'b.*', '', __version__))):
|
||||
and Version(latest_version) >= Version(re.sub(r'b.*', '', __version__))):
|
||||
MW.addMessage.emit('<a href="' + html_url + '"><b>The new version is available!</b></a>', 'warning',
|
||||
False)
|
||||
except Exception:
|
||||
@@ -158,9 +168,9 @@ class VersionThread(QtCore.QThread):
|
||||
self.answer = dialoganswer
|
||||
|
||||
|
||||
class ProgressThread(QtCore.QThread):
|
||||
class ProgressThread(QThread):
|
||||
def __init__(self):
|
||||
QtCore.QThread.__init__(self)
|
||||
QThread.__init__(self)
|
||||
self.running = False
|
||||
self.content = None
|
||||
self.progress = 0
|
||||
@@ -182,9 +192,9 @@ class ProgressThread(QtCore.QThread):
|
||||
self.running = False
|
||||
|
||||
|
||||
class WorkerThread(QtCore.QThread):
|
||||
class WorkerThread(QThread):
|
||||
def __init__(self):
|
||||
QtCore.QThread.__init__(self)
|
||||
QThread.__init__(self)
|
||||
self.conversionAlive = False
|
||||
self.errors = False
|
||||
self.kindlegenErrorCode = [0]
|
||||
@@ -237,9 +247,13 @@ class WorkerThread(QtCore.QThread):
|
||||
options.upscale = True
|
||||
if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09:
|
||||
options.gamma = float(GUI.gammaValue)
|
||||
if GUI.autoLevelBox.isChecked():
|
||||
options.autolevel = True
|
||||
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
|
||||
elif GUI.borderBox.checkState() == Qt.CheckState.Checked:
|
||||
@@ -248,14 +262,26 @@ class WorkerThread(QtCore.QThread):
|
||||
options.batchsplit = 2
|
||||
if GUI.colorBox.isChecked():
|
||||
options.forcecolor = True
|
||||
if GUI.eraseRainbowBox.isChecked():
|
||||
options.eraserainbow = True
|
||||
if GUI.maximizeStrips.isChecked():
|
||||
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.dedupeCoverBox.isChecked():
|
||||
options.dedupecover = 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:
|
||||
@@ -265,12 +291,29 @@ class WorkerThread(QtCore.QThread):
|
||||
options.customheight = str(GUI.heightBox.value())
|
||||
if GUI.targetDirectory != '':
|
||||
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:
|
||||
@@ -306,13 +349,8 @@ class WorkerThread(QtCore.QThread):
|
||||
GUI.progress.content = ''
|
||||
self.errors = True
|
||||
_, _, traceback = sys.exc_info()
|
||||
if len(err.args) == 1:
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||
else:
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
|
||||
GUI.sentry.extra_context({'realTraceback': err.args[1]})
|
||||
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 '
|
||||
@@ -380,7 +418,7 @@ class WorkerThread(QtCore.QThread):
|
||||
except Exception:
|
||||
pass
|
||||
MW.addMessage.emit('Processing MOBI files... <b>Done!</b>', 'info', True)
|
||||
k = kindle.Kindle()
|
||||
k = kindle.Kindle(options.profile)
|
||||
if k.path and k.coverSupport:
|
||||
for item in outputPath:
|
||||
comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle(
|
||||
@@ -412,6 +450,8 @@ class WorkerThread(QtCore.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):
|
||||
@@ -419,6 +459,12 @@ class WorkerThread(QtCore.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()
|
||||
@@ -429,7 +475,7 @@ class WorkerThread(QtCore.QThread):
|
||||
MW.modeConvert.emit(1)
|
||||
|
||||
|
||||
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
||||
class SystemTrayIcon(QSystemTrayIcon):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
if self.isSystemTrayAvailable():
|
||||
@@ -442,33 +488,49 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
||||
MW.activateWindow()
|
||||
|
||||
def addTrayMessage(self, message, icon):
|
||||
icon = getattr(QtWidgets.QSystemTrayIcon.MessageIcon, icon)
|
||||
icon = getattr(QSystemTrayIcon.MessageIcon, icon)
|
||||
if self.supportsMessages() and not MW.isActiveWindow():
|
||||
self.showMessage('Kindle Comic Converter', message, icon)
|
||||
|
||||
|
||||
class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
def selectDir(self):
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.jobList.clear()
|
||||
dname = QtWidgets.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:
|
||||
self.needClean = False
|
||||
GUI.jobList.clear()
|
||||
if self.tar or self.sevenzip:
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
fnames = QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf);;All (*.*)')
|
||||
else:
|
||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
fnames = QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.pdf);;All (*.*)')
|
||||
for fname in fnames[0]:
|
||||
if fname != '':
|
||||
@@ -480,8 +542,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
|
||||
def selectFileMetaEditor(self):
|
||||
sname = ''
|
||||
if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
|
||||
dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select directory', self.lastPath)
|
||||
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
|
||||
dname = QFileDialog.getExistingDirectory(MW, 'Select directory', self.lastPath)
|
||||
if dname != '':
|
||||
sname = os.path.join(dname, 'ComicInfo.xml')
|
||||
if sys.platform.startswith('win'):
|
||||
@@ -489,7 +551,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.lastPath = os.path.abspath(sname)
|
||||
else:
|
||||
if self.sevenzip:
|
||||
fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
|
||||
fname = QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
|
||||
'Comic (*.cbz *.cbr *.cb7)')
|
||||
else:
|
||||
fname = ['']
|
||||
@@ -518,7 +580,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
|
||||
def openWiki(self):
|
||||
# noinspection PyCallByClass
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki'))
|
||||
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:
|
||||
@@ -542,7 +608,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)
|
||||
@@ -553,16 +619,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if enable == 1:
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon = QIcon()
|
||||
icon.addPixmap(QPixmap(":/Other/icons/convert.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
GUI.convertButton.setIcon(icon)
|
||||
GUI.convertButton.setText('Convert')
|
||||
GUI.centralWidget.setAcceptDrops(True)
|
||||
elif enable == 0:
|
||||
self.conversionAlive = True
|
||||
self.worker.sync()
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon = QIcon()
|
||||
icon.addPixmap(QPixmap(":/Other/icons/clear.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
GUI.convertButton.setIcon(icon)
|
||||
GUI.convertButton.setText('Abort')
|
||||
GUI.centralWidget.setAcceptDrops(False)
|
||||
@@ -597,6 +663,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']:
|
||||
@@ -604,18 +672,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))
|
||||
@@ -649,7 +722,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':
|
||||
@@ -669,6 +745,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()
|
||||
@@ -678,16 +760,15 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
def addMessage(self, message, icon, replace=False):
|
||||
if icon != '':
|
||||
icon = getattr(self.icons, icon)
|
||||
item = QtWidgets.QListWidgetItem(icon, ' ' + self.stripTags(message))
|
||||
item = QListWidgetItem(icon, ' ' + self.stripTags(message))
|
||||
else:
|
||||
item = QtWidgets.QListWidgetItem(' ' + self.stripTags(message))
|
||||
item = QListWidgetItem(' ' + self.stripTags(message))
|
||||
if replace:
|
||||
GUI.jobList.takeItem(GUI.jobList.count() - 1)
|
||||
# Due to lack of HTML support in QListWidgetItem we overlay text field with QLabel
|
||||
# We still fill original text field with transparent content to trigger creation of horizontal scrollbar
|
||||
item.setForeground(QtGui.QColor('transparent'))
|
||||
label = QtWidgets.QLabel(message)
|
||||
label.setStyleSheet('background-image:url('');background-color:rgba(0,0,0,0);color:rgb(0,0,0);')
|
||||
item.setForeground(QColor('transparent'))
|
||||
label = QLabel(message)
|
||||
label.setOpenExternalLinks(True)
|
||||
GUI.jobList.addItem(item)
|
||||
GUI.jobList.setItemWidget(item, label)
|
||||
@@ -695,11 +776,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
|
||||
def showDialog(self, message, kind):
|
||||
if kind == 'error':
|
||||
QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.StandardButton.Ok)
|
||||
QMessageBox.critical(MW, 'KCC - Error', message, QMessageBox.StandardButton.Ok)
|
||||
elif kind == 'question':
|
||||
GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message,
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No))
|
||||
GUI.versionCheck.setAnswer(QMessageBox.question(MW, 'KCC - Question', message,
|
||||
QMessageBox.Yes,
|
||||
QMessageBox.No))
|
||||
|
||||
def updateProgressbar(self, command):
|
||||
if command == 'tick':
|
||||
@@ -723,14 +804,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
else:
|
||||
if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier:
|
||||
dname = QtWidgets.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 QApplication.keyboardModifiers() == Qt.KeyboardModifier.ShiftModifier:
|
||||
if not self.selectOutputFolder():
|
||||
return
|
||||
elif GUI.defaultOutputFolderBox.isChecked():
|
||||
self.targetDirectory = self.defaultOutputFolder
|
||||
else:
|
||||
GUI.targetDirectory = ''
|
||||
self.progress.start()
|
||||
@@ -741,6 +819,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')
|
||||
@@ -772,6 +856,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)
|
||||
@@ -780,21 +865,32 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'rotateBox': GUI.rotateBox.checkState().value,
|
||||
'qualityBox': GUI.qualityBox.checkState().value,
|
||||
'gammaBox': GUI.gammaBox.checkState().value,
|
||||
'autoLevelBox': GUI.autoLevelBox.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,
|
||||
'webtoonBox': GUI.webtoonBox.checkState().value,
|
||||
'outputSplit': GUI.outputSplit.checkState().value,
|
||||
'colorBox': GUI.colorBox.checkState().value,
|
||||
'eraseRainbowBox': GUI.eraseRainbowBox.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,
|
||||
'dedupeCoverBox': GUI.dedupeCoverBox.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()
|
||||
|
||||
@@ -846,16 +942,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
versionCheck = subprocess_run_silent(['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:
|
||||
versionCheck = line.split('V')[1].split(' ')[0]
|
||||
if StrictVersion(versionCheck) < StrictVersion('2.9'):
|
||||
if Version(versionCheck) < Version('2.9'):
|
||||
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()
|
||||
@@ -868,9 +964,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.setupUi(MW)
|
||||
self.editor = KCCGUI_MetaEditor()
|
||||
self.icons = Icons()
|
||||
self.settings = QtCore.QSettings('ciromattia', 'kcc')
|
||||
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)
|
||||
@@ -899,9 +998,9 @@ 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(QtCore.QSize(0, 0))
|
||||
getattr(GUI, element).setMinimumSize(QSize(0, 0))
|
||||
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
|
||||
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']:
|
||||
getattr(GUI, element).setContentsMargins(-1, 0, -1, 0)
|
||||
@@ -912,17 +1011,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": {
|
||||
@@ -931,15 +1033,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 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 12": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KO',
|
||||
},
|
||||
"Kindle Colorsoft": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': True, 'Label': 'KO',
|
||||
},
|
||||
"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,
|
||||
@@ -984,13 +1092,21 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'Label': 'KoS'},
|
||||
"Kobo Elipsa": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
'Label': 'KoE'},
|
||||
"reMarkable 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
'Label': 'Rmk1'},
|
||||
"reMarkable 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
'Label': 'Rmk2'},
|
||||
"reMarkable Paper Pro": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': True,
|
||||
'Label': 'RmkPP'},
|
||||
"Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1, 'DefaultUpscale': False, 'ForceColor': False,
|
||||
'Label': 'OTHER'},
|
||||
}
|
||||
profilesGUI = [
|
||||
"Kindle Colorsoft",
|
||||
"Kindle Paperwhite 12",
|
||||
"Kindle Scribe",
|
||||
"Kindle Paperwhite 11",
|
||||
"Kindle 11",
|
||||
"Kindle PW 11",
|
||||
"Kindle Oasis 9/10",
|
||||
"Separator",
|
||||
"Kobo Clara 2E",
|
||||
@@ -1001,13 +1117,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"Kobo Elipsa",
|
||||
"Kobo Nia",
|
||||
"Separator",
|
||||
"reMarkable 1",
|
||||
"reMarkable 2",
|
||||
"reMarkable Paper Pro",
|
||||
"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",
|
||||
@@ -1026,41 +1147,44 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"Kobo Mini/Touch",
|
||||
]
|
||||
|
||||
statusBarLabel = QtWidgets.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>')
|
||||
statusBarLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||
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_silent(['tar'], stdout=PIPE, stderr=STDOUT)
|
||||
self.tar = True
|
||||
except FileNotFoundError:
|
||||
self.tar = False
|
||||
try:
|
||||
subprocess_run_silent(['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)
|
||||
@@ -1068,6 +1192,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)
|
||||
@@ -1089,6 +1214,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.deviceBox.addItem(self.icons.deviceOther, profile)
|
||||
elif profile == "Separator":
|
||||
GUI.deviceBox.insertSeparator(GUI.deviceBox.count() + 1)
|
||||
elif 'reM' in profile:
|
||||
GUI.deviceBox.addItem(self.icons.deviceRmk, profile)
|
||||
elif 'Ko' in profile:
|
||||
GUI.deviceBox.addItem(self.icons.deviceKobo, profile)
|
||||
else:
|
||||
@@ -1118,6 +1245,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():
|
||||
@@ -1192,15 +1322,15 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
|
||||
return escape(s.strip())
|
||||
|
||||
def __init__(self):
|
||||
self.ui = QtWidgets.QDialog()
|
||||
self.ui = QDialog()
|
||||
self.parser = None
|
||||
self.setupUi(self.ui)
|
||||
self.ui.setWindowFlags(self.ui.windowFlags() & ~QtCore.Qt.WindowType.WindowContextHelpButtonHint)
|
||||
self.ui.setWindowFlags(self.ui.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)
|
||||
self.okButton.clicked.connect(self.saveData)
|
||||
self.cancelButton.clicked.connect(self.ui.close)
|
||||
if sys.platform.startswith('linux'):
|
||||
self.ui.resize(450, 260)
|
||||
self.ui.setMinimumSize(QtCore.QSize(450, 260))
|
||||
self.ui.setMinimumSize(QSize(450, 260))
|
||||
elif sys.platform.startswith('darwin'):
|
||||
self.ui.resize(450, 310)
|
||||
self.ui.setMinimumSize(QtCore.QSize(450, 310))
|
||||
self.ui.setMinimumSize(QSize(450, 310))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Resource object code (Python 3)
|
||||
# Created by: object code
|
||||
# Created by: The Resource Compiler for Qt version 6.5.2
|
||||
# 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
|
||||
@@ -4908,6 +4908,138 @@ D-\xbea6\x9bu\xd3\xe9\xf4+@\x03\xb0\xa2V\
|
||||
$\x12\x89D\x22\x91H$\x12\x89D\x15\xd1\xff\x00V\
|
||||
\x1c\x01\xcd\xc9\x01\xf3\xd5\x00\x00\x00\x00IEND\xae\
|
||||
B`\x82\
|
||||
\x00\x00\x08\x12\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
\x00\x00\x80\x00\x00\x00\x80\x08\x03\x00\x00\x00\xf4\xe0\x91\xf9\
|
||||
\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
|
||||
\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
|
||||
\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x027\
|
||||
PLTE\x00\x00\x00333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
3333333333333333\
|
||||
33333333333333<<\
|
||||
<[[[\x5c\x5c\x5c^^^___]]]\
|
||||
RRRaaa\xf9\xf9\xf9\xca\xca\xca\xfa\xfa\xfa\xfb\
|
||||
\xfb\xfb\xfc\xfc\xfc\xfd\xfd\xfd\xfe\xfe\xfe\xff\xff\xff\xfe\xfc\
|
||||
\xfbbbbdddeeeggg444\
|
||||
iiiccc\xff\xfe\xfe\xf6\xf6\xf6\x00\x00\x00\xaa\
|
||||
\xaa\xaa222\xbd\xbd\xbdKKK\xd2\xd2\xd2\xf3\xf3\
|
||||
\xf3\xb6\xb6\xb6\xd9\xd9\xd9\x95\x95\x95qqq\xe9\xe9\xe9\
|
||||
\xf5\xf5\xf5\xf8\xf8\xf8&&&\x9d\x9d\x9d***\xbe\
|
||||
\xbe\xbe\x96\x96\x96555\xcf\xcf\xcf\x8f\x8f\x8f\xc8\xc8\
|
||||
\xc8\xe5\xe5\xe5\xb2\xb2\xb2fff@@@sss\
|
||||
\xc1\xc1\xc1\xc0\xc0\xc0hhh\xd0\xd0\xd0\x91\x91\x91\x94\
|
||||
\x94\x94\x9e\x9e\x9eppp\x80\x80\x80\xc2\xc2\xc2\xbb\xbb\
|
||||
\xbb\x8e\x8e\x8e\x22\x22\x22\x0c\x0c\x0c\x0d\x0d\x0d\xaf\xaf\xaf\
|
||||
\x90\x90\x90\xee\xee\xee\x1c\x1c\x1c\xf4\xf4\xf4\xcc\xcc\xcc}\
|
||||
}}\x98\x98\x98\xa7\xa7\xa7\x7f\x7f\x7f\xc5\xc5\xc5\x8c\x8c\
|
||||
\x8c\x9b\x9b\x9b\xc7\xc7\xc7\xb4\xb4\xb4\xf0\xf0\xf0\xd7\xd7\xd7\
|
||||
\xda\xda\xda\xad\xad\xad\xcd\xcd\xcd\xc9\xc9\xc9\x87\x87\x87\xa3\
|
||||
\xa3\xa3\xd4\xd4\xd4\x16\x16\x16\xdb\xdb\xdb\xd8\xd8\xd8\xc6\xc6\
|
||||
\xc6MMM\x92\x92\x92\xa4\xa4\xa4\x82\x82\x82\xde\xde\xde\
|
||||
EEE\xe0\xe0\xe0XXX\x93\x93\x93vvvu\
|
||||
uu\xeb\xeb\xeb\xba\xba\xba\xdf\xdf\xdf\xb9\xb9\xb9SS\
|
||||
S\xdc\xdc\xdc\xf2\xf2\xf2\xe8\xe8\xe8\x97\x97\x97\xbc\xbc\xbc\
|
||||
\xd3\xd3\xd3\x8a\x8a\x8ammm\xfe\xfe\xfd\xfe\xfd\xfb\xfe\
|
||||
\xfd\xfc\xfe\xfc\xfaVVV\xa5\xa5\xa5\xecOs\x00\x00\
|
||||
\x00\x00=tRNS\x00\x1ba\x80\x8f\x85i)\xb1\
|
||||
\xfd\xc9?S\xf4xA\xfak\x09\xe3\xf8\x22n\x9b\xc2\
|
||||
\xec\x03\xf3\x0b9\x10>\x0f=\x02\xfb-\xdd\xfc\x94+\
|
||||
V\x90\xbd\x01\x05\xb7\xd6\x14\x04\x8c\xfe\xaa%\x87\xcd\xed\
|
||||
\xf2\xd5\x968\x82\x10\xbfn\x00\x00\x00\x01bKGD\
|
||||
M\x80h e\x00\x00\x00\x09pHYs\x00\x00\x0b\
|
||||
\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tI\
|
||||
ME\x07\xe8\x0c\x0a\x0c\x07#x\xb2 \xfb\x00\x00\x04\
|
||||
\xecIDATx\xda\xed\x9b\x87w\x14E\x1c\xc7\xcf\
|
||||
\x8a\xb1w\xb1\x12\x1b\xf6\xde\xcbI\x88zn\x99!\xa0\
|
||||
\x06F\x10PS,(\xc1\x18\x90\xa8\x04\x05BDP\
|
||||
\xac\xa0\xa0X\xc0\x02\xb6(\x88\xf5\x8fs\xca\xce\x96\xbb\
|
||||
\xcb\xee\xec\xdcws\x8f\xf7\xf6\x9b\x97w7sy\xf7\
|
||||
\xfd\xbc\xdf\xcc\xfcfvfR\xa9\x94*U\xaaT\xa9\
|
||||
,\x1du\xf41\xc7\xda\xeb\xb8\xe3g\xb4f?\xe3\x84\
|
||||
\x8ej\x8b:\xf1\xa4\x16\xfcO>\xa5U{\xae\x8eS\
|
||||
\xad\xfdO;\x1d\xe0\xcfu\x86\xa5\xff\x99ga\xfc\xab\
|
||||
g\x9fc\x07p.\xc8\xbfZ=\xcf\x0e`&\x0c\xe0\
|
||||
\xfc\x0b\xac\x00.\x84\x01T\xad\xda\xe0\x22\x9c\x7f\xf5b\
|
||||
\x1b\x80K\x80\x00\xb3\x8eh\x80\x07\xe6\x84\xea\xea\x9a\xdb\
|
||||
=\xf7\xc1\xae9\xa6z\x08\x02\xf0p\xcdZ\x8f\xc0\x01\
|
||||
\x1c\xa9\xb6\x018\x8e\xeb\xba\x1e\xff5E\x00\x03p{\
|
||||
\xcf\x17\xf2L\x11\xb0\x00\x8e\xb0'B\x02\xc1\x88\x00\x0c\
|
||||
\xe0){\x85`D\x00\x05p\x5c\xe9O\xb9$\x82g\
|
||||
@\x80\x04\xe0\x0d\xa0\xfd5Av\x10\x80\x00\xa2\x03\x84\
|
||||
\xfe\x0a\xc1\x80\x00\x07\x10\xf8G\x00\x94\xcc\x93\x04\xce\xf4\
|
||||
\x004\xfaG\xcd\xe0\x14\x0e\xd0\x13v\xc0F\x82\x18B\
|
||||
\xb3$\x89\x01\x98\xdf4\x00!\x81/S\xa3\x1b(\xc9\
|
||||
\x80\x01X\x10\x04\x806H\xe7\x04\xdf\x93\x92/\x89$\
|
||||
\x89\x01x4HA\xb4\x99\xc2\xcc\xa4a\xbc\xf8\xd8\x80\
|
||||
\x00<\xf6xM\xb6\x00\x9dB\xa4N\xf1\xd1\x09\x02\xa8\
|
||||
\xcf\x01\x19\x081\x02L\x13\x04\x00\xda\x8d\xa6\xa9\x8e\x00\
|
||||
\xd5\x07\xe2\x11 \xe9\x04*=`\x01\x16$:a\x16\
|
||||
@\x22Ic\x00z\x13\xc3\x90\x90\x85&\x04H\x80\x9e\
|
||||
8\x00\xc9\x8c@\x10\x02\xec\x5c\x10u\x82l\xffx\x08\
|
||||
p\x93Q\x94\x8a\xcd\x00\x88\x0a\x01r6\xcc\x01\x10\x85\
|
||||
\x00\xb7\x1e\x88\xb5\x81\x01\x00\xd5!@.H|\x83\xee\
|
||||
\x97\x08\x81\x18\x89\xf0\x15Q\xdcd\x11\xd3z\x22\xacs\
|
||||
\x16\xeb\xba%\xb2\x0d\x90k\xc2\x14\x80'=]\xb74\
|
||||
\xac[B\xd0\x00\x8dk\x92E\xcb\x96\x07nO\xe9\xaa\
|
||||
\xa7U\xf9\x99\xbe~\x07\x0d\xd0tM0\xc0\xd8\xa00\
|
||||
|V\x97\x9fS\xe5\xe7i0\x0c\xf0\xcf\x05u\x9d\x8d\
|
||||
\xb1\x17\x84\xe1\x8a\x9a*\xbe\xc8\x06_\x12\xe5\x95E\x00\
|
||||
\x0c\xe9\x0eW[\xf5\xf2\xe0\xf0+#D\x01\xac^#\
|
||||
j_U\x00k\xd9\xe8k\x12\x00?\x0aB\x80\xd7\xbd\
|
||||
7\xd4\x9bu\x01\xc0\x98(\xacW\x9d\xe2M\xb6\xb48\
|
||||
\x80\x81\xbe\x91\xb7\xf8wo\xd8\x18t\xbcM\x01\xc0\xb8\
|
||||
(l\x9e\x10\xa5\xb7\xd9\x0a'\x04\x00'\x22\xd9\x0b\xb6\
|
||||
H\xe7w\xfa\xc7\xbb\xf9\xcb\xd6\x00\x80n\x13\x95\xef\x8a\
|
||||
\xd2{l;\x95\x00\xef\xe3S\xb1\xd4Z\xf1\xe5\xc3\xbc\
|
||||
\xc3}\xc0X\xf7\x87\x1a`\xa5\xa8\xdd\xc2\x0b\xfd\x9b\xd9\
|
||||
GTG\xa08\x80\xed\xdc\xea\xe3Ov\xb8T\x03\xf0\
|
||||
\x96\xe7\x1a\xa7t'\x1bv\xa3\x08\xb8E\x01\xacK\x0e\
|
||||
\xc3\xd5\x94\x8e\x8a\xea1J?e\x9f\xd1i\x00\xd8\xd5\
|
||||
\x00\xb0KT\xaf\xa1\xcbv\xb3\xcf\x8b\x05p$\xc0\x17\
|
||||
\x0d\x00\xee\xb0\xa8\x1f\xda\xc3\xbe\xf4\x0b\x06\xf8\xaa9\x00\
|
||||
\xfdZ\xd4o\xfcF\xce\x8am\x01\xd8+\xea\xf7m`\
|
||||
\xdf\x16\x0b\xe0\xb8S\x01\x90.\x99 \xbe#\xc5\x02\xb8\
|
||||
\xde\xf7S\x00\x88\xbc \x1a\x81\x16\x0a\xc0\xa7\xe3\xa9\x22\
|
||||
@\xfb$\xc0P\xa1\x00bQ\xf8\x83\xf8\xf2\x1d1\xff\
|
||||
\x1ac\xab\xe4\x9b\xfd\xfc\x83^\xf9nT-\x87\x88\x07\
|
||||
\x06p\xdc\xda\x81\x1f\xd5t\xf8\xd3DM\xaf\x88~f\
|
||||
\xec\x97_'y\xd3\x8f\xf0\x0f\x96\xf3\xaa\x89\xbd\xfb\xe4\
|
||||
\xdf\xfc61\x80\x06\xf0\xa2\xe5\x1e\xfb=\xb9&\x9c\xa4\
|
||||
tr7c}r\x85\xa4u\x10\xfcd\xe4z\x87\xd2\
|
||||
\x00\xe8\x1f\xec0M\x00\x8c\xa1\x01\xf2<\x16\x10\xdd\x05\
|
||||
\xd0\xcf\x86\xe6\x00\xd8\xc7\xf3\xc4\xd3q;\x00z\x9a\xaf\
|
||||
\x88\xd3Z\x00\x1d\x81\x5c\x00\xf1m\xaa\xb6E@oS\
|
||||
\x01#\x90\xa3\x0f\x90h\x8f\xa6m\x9d\x10\x9e\x8ac\xe7\
|
||||
E\xc6!\x00\x03\xe4\x0d\x81_\xc8\x0eI\x9e\x10x\x85\
|
||||
\xec\x90\xe4 @o\xd1\xc4N-\xb3vJc\x03\x11\
|
||||
\x1f\x01\x09`DP(\x80\x10\x99\xbe\x08\xf4\xc4\xfd\xc5\
|
||||
\xf7g\x87\x00\x0b\xd0\x9b\xd8\xaa5\x1d\x05@\x80\xf9y\
|
||||
\xd3\x00\xa5\x7f\x06\xd3\x11\xee\xd8\xceh2\x0a[f\xe1\
|
||||
_\xd0Dd\x0c\x10\xb6\xc0\xdf\xff@\x9f\x0b\xf26\x01\
|
||||
\xf1kzA0m\x9d\x90D?\xc1Y*vA\x12\
|
||||
\x1f\x86D\xf9\xa5\xca\xc7\xae\x88\xea\x13Q\xa6\xa2\xe3[\
|
||||
\xf0f\xb5o\xc2 o\xf98\xd0sC\xbdW\xecz\
|
||||
\xea\x16Q\xaa\xd4\xf99\xf6\xe8VoP8\xe1%\x81\
|
||||
T\xc5o\x10\xe0\xef\x92\x05\xf7$\xd2\x84\xbfA\xd1\xf6\
|
||||
\xdbt\xff\xee\xb4\xd6\x7f\x10\x00\x84J\x80\x12\xa0\x048\
|
||||
2\x01:\x81\x00\x97\xda\x00\x5cv9\x0e\xe0\x0a\x1b\x80\
|
||||
\xca\x950\xff\xd9\x9dV\x00W\xc1\x00fZ\xf9W\xae\
|
||||
\x9e\x8d\x02\xb8\xc6\x0e\xa0r-\xc8\xff\xba\xeb-\x01n\
|
||||
\xb8\x11\xe2\x7f\xd3\xcd\x96\xfe\x95\xca-\xb7\xb6\xfe\x9fF\
|
||||
\xb7\xdd\xdei\xed\xcfu\xc7\x9dw\xdd\xdda\xaf{\xee\
|
||||
\xbd\xef\xfeV\xecK\x95*U\xaax\xfd\x0f\xf4\x94\xdc\
|
||||
\x07\xeb\xfb\xc9m\x00\x00\x00\x00IEND\xaeB`\
|
||||
\x82\
|
||||
\x00\x00\x05\xe0\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
@@ -5930,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\
|
||||
@@ -11397,6 +12111,10 @@ qt_resource_name = b"\
|
||||
\x05\x92]\x07\
|
||||
\x00K\
|
||||
\x00o\x00b\x00o\x00.\x00p\x00n\x00g\
|
||||
\x00\x07\
|
||||
\x09>W\xe7\
|
||||
\x00R\
|
||||
\x00m\x00k\x00.\x00p\x00n\x00g\
|
||||
\x00\x09\
|
||||
\x0e\xc5\xfa\x07\
|
||||
\x00O\
|
||||
@@ -11413,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\
|
||||
@@ -11463,11 +12185,11 @@ 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\x1b\
|
||||
\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\x13\
|
||||
\x00\x00\x00&\x00\x02\x00\x00\x00\x01\x00\x00\x00\x14\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0f\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x10\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x006\x00\x02\x00\x00\x00\x01\x00\x00\x00\x0b\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
@@ -11475,47 +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\xac\x00\x00\x00\x00\x00\x01\x00\x02&\xd7\
|
||||
\x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x02S.\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02{q\
|
||||
\x00\x00\x02\x22\x00\x00\x00\x00\x00\x01\x00\x02\xa7\xc8\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02Qv\
|
||||
\x00\x00\x02\x0e\x00\x00\x00\x00\x00\x01\x00\x02}\xcd\
|
||||
\x00\x00\x01\x88;p\xbcB\
|
||||
\x00\x00\x01\xc2\x00\x00\x00\x00\x00\x01\x00\x02F\x13\
|
||||
\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\x03\x00\x00\x00\x0c\
|
||||
\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\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\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\
|
||||
\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\x10\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x11\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x02.\x00\x00\x00\x00\x00\x01\x00\x02\xad\xbd\
|
||||
\x00\x00\x02f\x00\x00\x00\x00\x00\x01\x00\x02\xda\x14\
|
||||
\x00\x00\x01\x88;p\xbcJ\
|
||||
\x00\x00\x02\x00\x00\x00\x00\x00\x00\x01\x00\x02\x97\xc0\
|
||||
\x00\x00\x028\x00\x00\x00\x00\x00\x01\x00\x02\xc4\x17\
|
||||
\x00\x00\x01\x88;p\xbcI\
|
||||
\x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x02\xa1\x1d\
|
||||
\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\x14\
|
||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x08\x00\x00\x00\x15\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x08\x00\x00\x00\x00\x00\x01\x00\x01H\x9b\
|
||||
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x01P\xb1\
|
||||
\x00\x00\x01\x88;p\xbcJ\
|
||||
\x00\x00\x01\x1e\x00\x00\x00\x00\x00\x01\x00\x01qC\
|
||||
\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\x80\x00\x00\x00\x00\x00\x01\x00\x01\xca\x17\
|
||||
\x00\x00\x01\x88;p\xbcI\
|
||||
\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x01\x84\xd0\
|
||||
\x00\x00\x01\xb8\x00\x00\x00\x00\x00\x01\x00\x01\xf6n\
|
||||
\x00\x00\x01\x94\xb4\xd4\xf0a\
|
||||
\x00\x00\x01\x9e\x00\x00\x00\x00\x00\x01\x00\x01\xb1'\
|
||||
\x00\x00\x01\x88;p\xbcH\
|
||||
\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x01D<\
|
||||
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x01LR\
|
||||
\x00\x00\x01\x88;p\xbcF\
|
||||
\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x017\xd3\
|
||||
\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x01?\xe9\
|
||||
\x00\x00\x01\x88;p\xbcH\
|
||||
\x00\x00\x01@\x00\x00\x00\x00\x00\x01\x00\x01z\x9a\
|
||||
\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\x1c\
|
||||
\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.5.2
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -16,110 +16,374 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QComboBox,
|
||||
QGridLayout, QHBoxLayout, QLabel, QListWidget,
|
||||
QListWidgetItem, QMainWindow, QProgressBar, QPushButton,
|
||||
QSizePolicy, QSlider, QSpinBox, QStatusBar,
|
||||
QWidget)
|
||||
QGridLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QListWidget, QListWidgetItem, QMainWindow, QProgressBar,
|
||||
QPushButton, QSizePolicy, QSlider, QSpinBox,
|
||||
QStatusBar, QWidget)
|
||||
from . import KCC_rc
|
||||
|
||||
class Ui_mainWindow(object):
|
||||
def setupUi(self, mainWindow):
|
||||
if not mainWindow.objectName():
|
||||
mainWindow.setObjectName(u"mainWindow")
|
||||
mainWindow.resize(450, 400)
|
||||
mainWindow.resize(566, 573)
|
||||
icon = QIcon()
|
||||
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Normal, QIcon.Off)
|
||||
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
mainWindow.setWindowIcon(icon)
|
||||
self.centralWidget = QWidget(mainWindow)
|
||||
self.centralWidget.setObjectName(u"centralWidget")
|
||||
self.gridLayout = QGridLayout(self.centralWidget)
|
||||
self.gridLayout.setObjectName(u"gridLayout")
|
||||
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
|
||||
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")
|
||||
self.horizontalLayout = QHBoxLayout(self.toolWidget)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.editorButton = QPushButton(self.toolWidget)
|
||||
self.editorButton.setObjectName(u"editorButton")
|
||||
self.editorButton.setMinimumSize(QSize(0, 30))
|
||||
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))
|
||||
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.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_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")
|
||||
self.progressBar.setMinimumSize(QSize(0, 30))
|
||||
self.progressBar.setFont(font)
|
||||
self.progressBar.setVisible(False)
|
||||
self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter)
|
||||
|
||||
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
|
||||
|
||||
self.customWidget = QWidget(self.centralWidget)
|
||||
self.customWidget.setObjectName(u"customWidget")
|
||||
self.customWidget.setVisible(False)
|
||||
self.gridLayout_3 = QGridLayout(self.customWidget)
|
||||
self.gridLayout_3.setObjectName(u"gridLayout_3")
|
||||
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||
self.hLabel = QLabel(self.customWidget)
|
||||
self.hLabel.setObjectName(u"hLabel")
|
||||
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
||||
sizePolicy2.setHorizontalStretch(0)
|
||||
sizePolicy2.setVerticalStretch(0)
|
||||
sizePolicy2.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
|
||||
self.hLabel.setSizePolicy(sizePolicy2)
|
||||
|
||||
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
|
||||
|
||||
self.widthBox = QSpinBox(self.customWidget)
|
||||
self.widthBox.setObjectName(u"widthBox")
|
||||
self.widthBox.setMaximum(2400)
|
||||
|
||||
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
||||
|
||||
self.wLabel = QLabel(self.customWidget)
|
||||
self.wLabel.setObjectName(u"wLabel")
|
||||
sizePolicy2.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
|
||||
self.wLabel.setSizePolicy(sizePolicy2)
|
||||
|
||||
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
|
||||
|
||||
self.heightBox = QSpinBox(self.customWidget)
|
||||
self.heightBox.setObjectName(u"heightBox")
|
||||
self.heightBox.setMaximum(3840)
|
||||
|
||||
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
||||
|
||||
|
||||
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.qualityBox = QCheckBox(self.optionWidget)
|
||||
self.qualityBox.setObjectName(u"qualityBox")
|
||||
self.qualityBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1)
|
||||
|
||||
self.deleteBox = QCheckBox(self.optionWidget)
|
||||
self.deleteBox.setObjectName(u"deleteBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.deleteBox, 4, 1, 1, 1)
|
||||
|
||||
self.maximizeStrips = QCheckBox(self.optionWidget)
|
||||
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||
|
||||
self.gridLayout_2.addWidget(self.maximizeStrips, 3, 1, 1, 1)
|
||||
|
||||
self.gammaBox = QCheckBox(self.optionWidget)
|
||||
self.gammaBox.setObjectName(u"gammaBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.gammaBox, 1, 2, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
||||
|
||||
self.mangaBox = QCheckBox(self.optionWidget)
|
||||
self.mangaBox.setObjectName(u"mangaBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
||||
|
||||
self.borderBox = QCheckBox(self.optionWidget)
|
||||
self.borderBox.setObjectName(u"borderBox")
|
||||
self.borderBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
||||
|
||||
self.webtoonBox = QCheckBox(self.optionWidget)
|
||||
self.webtoonBox.setObjectName(u"webtoonBox")
|
||||
self.interPanelCropBox = QCheckBox(self.optionWidget)
|
||||
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
|
||||
self.interPanelCropBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.webtoonBox, 1, 0, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
|
||||
|
||||
self.upscaleBox = QCheckBox(self.optionWidget)
|
||||
self.upscaleBox.setObjectName(u"upscaleBox")
|
||||
self.upscaleBox.setTristate(True)
|
||||
self.fileFusionBox = QCheckBox(self.optionWidget)
|
||||
self.fileFusionBox.setObjectName(u"fileFusionBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.upscaleBox, 1, 1, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
|
||||
|
||||
self.mangaBox = QCheckBox(self.optionWidget)
|
||||
self.mangaBox.setObjectName(u"mangaBox")
|
||||
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.mangaBox, 0, 0, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.authorEdit, 0, 0, 1, 1)
|
||||
|
||||
self.rotateFirstBox = QCheckBox(self.optionWidget)
|
||||
self.rotateFirstBox.setObjectName(u"rotateFirstBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1)
|
||||
|
||||
self.eraseRainbowBox = QCheckBox(self.optionWidget)
|
||||
self.eraseRainbowBox.setObjectName(u"eraseRainbowBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1)
|
||||
|
||||
self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
|
||||
self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
|
||||
|
||||
self.rotateBox = QCheckBox(self.optionWidget)
|
||||
self.rotateBox.setObjectName(u"rotateBox")
|
||||
self.rotateBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
||||
|
||||
self.outputSplit = QCheckBox(self.optionWidget)
|
||||
self.outputSplit.setObjectName(u"outputSplit")
|
||||
|
||||
self.gridLayout_2.addWidget(self.outputSplit, 3, 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.spreadShiftBox = QCheckBox(self.optionWidget)
|
||||
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
||||
|
||||
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
||||
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 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.croppingBox = QCheckBox(self.optionWidget)
|
||||
self.croppingBox.setObjectName(u"croppingBox")
|
||||
self.croppingBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.croppingBox, 3, 2, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
||||
|
||||
self.outputSplit = QCheckBox(self.optionWidget)
|
||||
self.outputSplit.setObjectName(u"outputSplit")
|
||||
self.maximizeStrips = QCheckBox(self.optionWidget)
|
||||
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||
|
||||
self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
||||
|
||||
self.noRotateBox = QCheckBox(self.optionWidget)
|
||||
self.noRotateBox.setObjectName(u"noRotateBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
||||
|
||||
self.deleteBox = QCheckBox(self.optionWidget)
|
||||
self.deleteBox.setObjectName(u"deleteBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 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.mozJpegBox = QCheckBox(self.optionWidget)
|
||||
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
||||
self.mozJpegBox.setTristate(True)
|
||||
|
||||
self.gridLayout_2.addWidget(self.mozJpegBox, 3, 0, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
||||
|
||||
self.colorBox = QCheckBox(self.optionWidget)
|
||||
self.colorBox.setObjectName(u"colorBox")
|
||||
self.autoLevelBox = QCheckBox(self.optionWidget)
|
||||
self.autoLevelBox.setObjectName(u"autoLevelBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
|
||||
|
||||
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
||||
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.disableProcessingBox, 4, 2, 1, 1)
|
||||
|
||||
self.dedupeCoverBox = QCheckBox(self.optionWidget)
|
||||
self.dedupeCoverBox.setObjectName(u"dedupeCoverBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.dedupeCoverBox, 4, 0, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
||||
@@ -139,186 +403,49 @@ class Ui_mainWindow(object):
|
||||
self.gammaSlider.setObjectName(u"gammaSlider")
|
||||
self.gammaSlider.setMaximum(250)
|
||||
self.gammaSlider.setSingleStep(5)
|
||||
self.gammaSlider.setOrientation(Qt.Horizontal)
|
||||
self.gammaSlider.setOrientation(Qt.Orientation.Horizontal)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.gammaSlider)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.gammaWidget, 6, 0, 1, 2)
|
||||
self.gridLayout.addWidget(self.gammaWidget, 7, 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.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_3.addWidget(self.croppingPowerLabel)
|
||||
self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
|
||||
|
||||
self.croppingPowerSlider = QSlider(self.croppingWidget)
|
||||
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
|
||||
self.croppingPowerSlider.setMaximum(200)
|
||||
self.croppingPowerSlider.setSingleStep(1)
|
||||
self.croppingPowerSlider.setOrientation(Qt.Horizontal)
|
||||
self.chunkSizeBox = QSpinBox(self.chunkSizeWidget)
|
||||
self.chunkSizeBox.setObjectName(u"chunkSizeBox")
|
||||
self.chunkSizeBox.setMinimum(100)
|
||||
self.chunkSizeBox.setMaximum(600)
|
||||
self.chunkSizeBox.setValue(400)
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.croppingPowerSlider)
|
||||
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.croppingWidget, 8, 0, 1, 2)
|
||||
|
||||
self.buttonWidget = QWidget(self.centralWidget)
|
||||
self.buttonWidget.setObjectName(u"buttonWidget")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.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.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.Normal, QIcon.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.Normal, QIcon.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.Normal, QIcon.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.Normal, QIcon.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.toolWidget = QWidget(self.centralWidget)
|
||||
self.toolWidget.setObjectName(u"toolWidget")
|
||||
self.horizontalLayout = QHBoxLayout(self.toolWidget)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
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.Normal, QIcon.Off)
|
||||
self.editorButton.setIcon(icon5)
|
||||
|
||||
self.horizontalLayout.addWidget(self.editorButton)
|
||||
|
||||
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.Normal, QIcon.Off)
|
||||
self.wikiButton.setIcon(icon6)
|
||||
|
||||
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"QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}")
|
||||
self.jobList.setSelectionMode(QAbstractItemView.NoSelection)
|
||||
self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||
self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
|
||||
|
||||
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
|
||||
|
||||
self.progressBar = QProgressBar(self.centralWidget)
|
||||
self.progressBar.setObjectName(u"progressBar")
|
||||
self.progressBar.setMinimumSize(QSize(0, 30))
|
||||
self.progressBar.setFont(font)
|
||||
self.progressBar.setVisible(False)
|
||||
self.progressBar.setAlignment(Qt.AlignJustify|Qt.AlignVCenter)
|
||||
|
||||
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
|
||||
|
||||
self.customWidget = QWidget(self.centralWidget)
|
||||
self.customWidget.setObjectName(u"customWidget")
|
||||
self.customWidget.setVisible(False)
|
||||
self.gridLayout_3 = QGridLayout(self.customWidget)
|
||||
self.gridLayout_3.setObjectName(u"gridLayout_3")
|
||||
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||
self.hLabel = QLabel(self.customWidget)
|
||||
self.hLabel.setObjectName(u"hLabel")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
|
||||
self.hLabel.setSizePolicy(sizePolicy1)
|
||||
|
||||
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
|
||||
|
||||
self.widthBox = QSpinBox(self.customWidget)
|
||||
self.widthBox.setObjectName(u"widthBox")
|
||||
self.widthBox.setMaximum(2160)
|
||||
|
||||
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
||||
|
||||
self.wLabel = QLabel(self.customWidget)
|
||||
self.wLabel.setObjectName(u"wLabel")
|
||||
sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
|
||||
self.wLabel.setSizePolicy(sizePolicy1)
|
||||
|
||||
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
|
||||
|
||||
self.heightBox = QSpinBox(self.customWidget)
|
||||
self.heightBox.setObjectName(u"heightBox")
|
||||
self.heightBox.setMaximum(3840)
|
||||
|
||||
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
||||
|
||||
|
||||
self.gridLayout.addWidget(self.customWidget, 7, 0, 1, 2)
|
||||
self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1)
|
||||
|
||||
mainWindow.setCentralWidget(self.centralWidget)
|
||||
self.statusBar = QStatusBar(mainWindow)
|
||||
@@ -326,9 +453,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)
|
||||
@@ -339,18 +464,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.eraseRainbowBox)
|
||||
QWidget.setTabOrder(self.eraseRainbowBox, 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)
|
||||
|
||||
@@ -360,91 +490,34 @@ class Ui_mainWindow(object):
|
||||
def retranslateUi(self, mainWindow):
|
||||
mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", 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))
|
||||
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.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", 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.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.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.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.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.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.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.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.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.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.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.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.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.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.dedupeCoverBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Don't duplicate the first page as the cover. Useful for 2 page spread alignment.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.dedupeCoverBox.setText(QCoreApplication.translate("mainWindow", u"De-dupe cover", 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"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)
|
||||
@@ -459,5 +532,112 @@ 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.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.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.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.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.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.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.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))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", 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.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.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.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.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.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.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.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.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.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.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.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.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.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Set the most common dark pixel value to be the black point for leveling on a page by page basis.</p><p>Skipped for any images that were originally color.</p><p>Use only if default autocontrast still results in very gray faded blacks. </p><p>Reccomended to use with Custom Gamma = 1.0 (Disabled).</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Aggressive Black Point", 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.5.2
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -27,7 +27,7 @@ class Ui_editorDialog(object):
|
||||
editorDialog.resize(400, 260)
|
||||
editorDialog.setMinimumSize(QSize(400, 260))
|
||||
icon = QIcon()
|
||||
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Normal, QIcon.Off)
|
||||
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
editorDialog.setWindowIcon(icon)
|
||||
self.verticalLayout = QVBoxLayout(editorDialog)
|
||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||
@@ -117,7 +117,7 @@ class Ui_editorDialog(object):
|
||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.statusLabel = QLabel(self.optionWidget)
|
||||
self.statusLabel.setObjectName(u"statusLabel")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.statusLabel.sizePolicy().hasHeightForWidth())
|
||||
@@ -129,7 +129,7 @@ class Ui_editorDialog(object):
|
||||
self.okButton.setObjectName(u"okButton")
|
||||
self.okButton.setMinimumSize(QSize(0, 30))
|
||||
icon1 = QIcon()
|
||||
icon1.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Normal, QIcon.Off)
|
||||
icon1.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.okButton.setIcon(icon1)
|
||||
|
||||
self.horizontalLayout.addWidget(self.okButton)
|
||||
@@ -138,7 +138,7 @@ class Ui_editorDialog(object):
|
||||
self.cancelButton.setObjectName(u"cancelButton")
|
||||
self.cancelButton.setMinimumSize(QSize(0, 30))
|
||||
icon2 = QIcon()
|
||||
icon2.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Normal, QIcon.Off)
|
||||
icon2.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.cancelButton.setIcon(icon2)
|
||||
|
||||
self.horizontalLayout.addWidget(self.cancelButton)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '6.1.0'
|
||||
__version__ = '9.0.0'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,72 +18,96 @@
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
|
||||
from functools import cached_property, lru_cache
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import distro
|
||||
from shutil import move
|
||||
from subprocess import STDOUT, PIPE, CalledProcessError
|
||||
from xml.dom.minidom import parseString
|
||||
from xml.parsers.expat import ExpatError
|
||||
from .shared import subprocess_run_silent
|
||||
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:
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.type = None
|
||||
if not os.path.isfile(self.filepath):
|
||||
raise OSError('File not found.')
|
||||
try:
|
||||
process = subprocess_run_silent(['7z', 'l', '-y', '-p1', self.filepath], stderr=STDOUT, stdout=PIPE)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
for line in process.stdout.splitlines():
|
||||
if b'Type =' in line:
|
||||
self.type = line.rstrip().decode().split(' = ')[1].upper()
|
||||
break
|
||||
if process.returncode != 0 and distro.id() == 'fedora':
|
||||
process = subprocess_run_silent(['unrar', 'l', '-y', '-p1', self.filepath], stderr=STDOUT, stdout=PIPE)
|
||||
for line in process.stdout.splitlines():
|
||||
if b'Details: ' in line:
|
||||
self.type = line.rstrip().decode().split(' ')[1].upper()
|
||||
break
|
||||
if process.returncode != 0:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
|
||||
@cached_property
|
||||
def type(self):
|
||||
extraction_commands = [
|
||||
[SEVENZIP, 'l', '-y', '-p1', self.filepath],
|
||||
]
|
||||
|
||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||
extraction_commands.append(
|
||||
['unrar', 'l', '-y', '-p1', self.filepath],
|
||||
)
|
||||
|
||||
for cmd in extraction_commands:
|
||||
try:
|
||||
process = subprocess_run(cmd, capture_output=True, check=True)
|
||||
for line in process.stdout.splitlines():
|
||||
if b'Type =' in line:
|
||||
return line.rstrip().decode().split(' = ')[1].upper()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except CalledProcessError:
|
||||
pass
|
||||
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
|
||||
def extract(self, targetdir):
|
||||
if not os.path.isdir(targetdir):
|
||||
raise OSError('Target directory doesn\'t exist.')
|
||||
try:
|
||||
process = subprocess_run_silent(['tar', '-xf', self.filepath, '-C', targetdir],
|
||||
stdout=PIPE, stderr=STDOUT, check=True)
|
||||
return targetdir
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
pass
|
||||
process = subprocess_run_silent(['7z', 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
|
||||
stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0 and distro.id() == 'fedora':
|
||||
process = subprocess_run_silent(['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
||||
, stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
elif process.returncode != 0:
|
||||
|
||||
missing = []
|
||||
|
||||
extraction_commands = [
|
||||
['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.filepath, '-C', targetdir],
|
||||
[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, '-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]
|
||||
)
|
||||
|
||||
for cmd in extraction_commands:
|
||||
try:
|
||||
subprocess_run(cmd, capture_output=True, check=True)
|
||||
return targetdir
|
||||
except FileNotFoundError:
|
||||
missing.append(cmd[0])
|
||||
except CalledProcessError:
|
||||
pass
|
||||
|
||||
if missing:
|
||||
raise OSError(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
|
||||
else:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
return targetdir
|
||||
|
||||
def addFile(self, sourcefile):
|
||||
if self.type in ['RAR', 'RAR5']:
|
||||
raise NotImplementedError
|
||||
process = subprocess_run_silent(['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_silent(['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)
|
||||
@@ -91,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
|
||||
|
||||
28
kindlecomicconverter/common_crop.py
Normal file
28
kindlecomicconverter/common_crop.py
Normal file
@@ -0,0 +1,28 @@
|
||||
def threshold_from_power(power):
|
||||
return 240-(power*64)
|
||||
|
||||
|
||||
'''
|
||||
Groups close values together
|
||||
'''
|
||||
def group_close_values(vals, max_dist_tolerated):
|
||||
groups = []
|
||||
|
||||
group_start = -1
|
||||
group_end = 0
|
||||
for i in range(len(vals)):
|
||||
dist = vals[i] - group_end
|
||||
if group_start == -1:
|
||||
group_start = vals[i]
|
||||
group_end = vals[i]
|
||||
elif dist <= max_dist_tolerated:
|
||||
group_end = vals[i]
|
||||
else:
|
||||
groups.append((group_start, group_end))
|
||||
group_start = -1
|
||||
group_end = -1
|
||||
|
||||
if group_start != -1:
|
||||
groups.append((group_start, group_end))
|
||||
|
||||
return groups
|
||||
@@ -136,7 +136,11 @@ def del_exth(rec0, exth_num):
|
||||
|
||||
|
||||
class DualMobiMetaFix:
|
||||
def __init__(self, infile, outfile, asin):
|
||||
def __init__(self, infile, outfile, asin, is_pdoc):
|
||||
cdetype = b'EBOK'
|
||||
if is_pdoc:
|
||||
cdetype = b'PDOC'
|
||||
|
||||
shutil.copyfile(infile, outfile)
|
||||
f = open(outfile, "r+b")
|
||||
self.datain = mmap.mmap(f.fileno(), 0)
|
||||
@@ -147,7 +151,7 @@ class DualMobiMetaFix:
|
||||
rec0 = self.datain_rec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 501, cdetype)
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
replacesection(self.datain, 0, rec0)
|
||||
|
||||
@@ -174,7 +178,7 @@ class DualMobiMetaFix:
|
||||
rec0 = self.datain_kfrec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 501, cdetype)
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
replacesection(self.datain, datain_kf8, rec0)
|
||||
|
||||
|
||||
@@ -20,9 +20,15 @@
|
||||
# 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 .rainbow_artifacts_eraser import erase_rainbow_artifacts
|
||||
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
|
||||
|
||||
AUTO_CROP_THRESHOLD = 0.015
|
||||
|
||||
@@ -78,18 +84,31 @@ class ProfileData:
|
||||
PalleteNull = [
|
||||
]
|
||||
|
||||
Profiles = {
|
||||
ProfilesKindleEBOK = {
|
||||
'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
|
||||
'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),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||
'K34': ("Kindle Keyboard/Touch", (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),
|
||||
'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.8),
|
||||
'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.8),
|
||||
}
|
||||
|
||||
ProfilesKindle = {
|
||||
**ProfilesKindleEBOK,
|
||||
**ProfilesKindlePDOC
|
||||
}
|
||||
|
||||
ProfilesKobo = {
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
|
||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
|
||||
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
|
||||
@@ -105,6 +124,18 @@ class ProfileData:
|
||||
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
||||
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8),
|
||||
'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.8),
|
||||
}
|
||||
|
||||
ProfilesRemarkable = {
|
||||
'Rmk1': ("reMarkable 1", (1404, 1872), Palette16, 1.8),
|
||||
'Rmk2': ("reMarkable 2", (1404, 1872), Palette16, 1.8),
|
||||
'RmkPP': ("reMarkable Paper Pro", (1620, 2160), Palette16, 1.8),
|
||||
}
|
||||
|
||||
Profiles = {
|
||||
**ProfilesKindle,
|
||||
**ProfilesKobo,
|
||||
**ProfilesRemarkable,
|
||||
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
||||
}
|
||||
|
||||
@@ -116,8 +147,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'):
|
||||
@@ -148,10 +183,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:
|
||||
self.payload.append(['R', self.source, self.image.rotate(90, Image.Resampling.BICUBIC, True), self.color, self.fill])
|
||||
spread = self.image
|
||||
if not self.opt.norotate:
|
||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||
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:
|
||||
@@ -166,35 +204,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:
|
||||
self.payload.append(['R', self.source, self.image.rotate(90, Image.Resampling.BICUBIC, True),
|
||||
self.color, self.fill])
|
||||
spread = self.image
|
||||
if not self.opt.norotate:
|
||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||
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:
|
||||
@@ -236,164 +254,190 @@ 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):
|
||||
if self.opt.autolevel and not self.color:
|
||||
self.convertToGrayscale()
|
||||
h = self.image.histogram()
|
||||
most_common_dark_pixel_count = max(h[:64])
|
||||
black_point = h.index(most_common_dark_pixel_count)
|
||||
bp = black_point
|
||||
self.image = self.image.point(lambda p: p if p > bp else bp)
|
||||
|
||||
# don't autocontrast grayscale pages that were originally color
|
||||
if not self.opt.forcecolor and self.color:
|
||||
return
|
||||
|
||||
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
||||
|
||||
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, eraserainbow, is_color):
|
||||
# Erase rainbow artifacts for grayscale and color images by removing spectral frequencies that cause Moire interference with color filter array
|
||||
if eraserainbow:
|
||||
self.image = erase_rainbow_artifacts(self.image, is_color)
|
||||
|
||||
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 getBoundingBox(self, tmptmg):
|
||||
min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
|
||||
max_margin = [int(0.1 * i + 0.5) for i in tmptmg.size]
|
||||
bbox = tmptmg.getbbox()
|
||||
bbox = (
|
||||
max(0, min(max_margin[0], bbox[0] - min_margin[0])),
|
||||
max(0, min(max_margin[1], bbox[1] - min_margin[1])),
|
||||
min(tmptmg.size[0],
|
||||
max(tmptmg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
|
||||
min(tmptmg.size[1],
|
||||
max(tmptmg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
|
||||
)
|
||||
return bbox
|
||||
|
||||
def 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:
|
||||
self.image = self.image.crop(box)
|
||||
|
||||
def cropPageNumber(self, power, minimum):
|
||||
if self.fill != 'white':
|
||||
tmptmg = self.image.convert(mode='L')
|
||||
else:
|
||||
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
||||
tmptmg = tmptmg.point(lambda x: x and 255)
|
||||
tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
|
||||
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
|
||||
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
||||
if tmptmg.getbbox():
|
||||
self.maybeCrop(tmptmg.getbbox(), minimum)
|
||||
bbox = get_bbox_crop_margin_page_number(self.image, power, self.fill)
|
||||
|
||||
if bbox:
|
||||
self.maybeCrop(bbox, minimum)
|
||||
|
||||
def cropMargin(self, power, minimum):
|
||||
if self.fill != 'white':
|
||||
tmptmg = self.image.convert(mode='L')
|
||||
else:
|
||||
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
||||
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
|
||||
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
||||
if tmptmg.getbbox():
|
||||
self.maybeCrop(self.getBoundingBox(tmptmg), minimum)
|
||||
bbox = get_bbox_crop_margin(self.image, power, self.fill)
|
||||
|
||||
if bbox:
|
||||
self.maybeCrop(bbox, minimum)
|
||||
|
||||
def cropInterPanelEmptySections(self, direction):
|
||||
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'):
|
||||
@@ -405,17 +449,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)
|
||||
|
||||
76
kindlecomicconverter/inter_panel_crop_alg.py
Normal file
76
kindlecomicconverter/inter_panel_crop_alg.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from PIL import Image, ImageFilter, ImageOps
|
||||
import numpy as np
|
||||
from typing import Literal
|
||||
from .common_crop import threshold_from_power, group_close_values
|
||||
|
||||
|
||||
'''
|
||||
Crops inter-panel empty spaces (ignores empty spaces near borders - for that use crop margins).
|
||||
|
||||
Parameters:
|
||||
img (PIL image): A PIL image.
|
||||
direction (horizontal or vertical or both): To crop rows (horizontal), cols (vertical) or both.
|
||||
keep (float): Distance to keep between panels after cropping (in percentage relative to the original distance).
|
||||
background_color (string): 'white' for white background, anything else for black.
|
||||
Returns:
|
||||
img (PIL image): A PIL image after cropping empty sections.
|
||||
'''
|
||||
def crop_empty_inter_panel(img, direction: Literal["horizontal", "vertical", "both"], keep=0.04, background_color='white'):
|
||||
img_temp = img
|
||||
|
||||
if img.mode != 'L':
|
||||
img_temp = ImageOps.grayscale(img)
|
||||
|
||||
if background_color != 'white':
|
||||
img_temp = ImageOps.invert(img)
|
||||
|
||||
img_mat = np.array(img)
|
||||
|
||||
power = 1
|
||||
img_temp = ImageOps.autocontrast(img_temp, 1).filter(ImageFilter.BoxBlur(1))
|
||||
img_temp = img_temp.point(lambda p: 255 if p <= threshold_from_power(power) else 0)
|
||||
|
||||
if direction in ["horizontal", "both"]:
|
||||
rows_idx_to_remove = empty_sections(img_temp, keep, horizontal=True)
|
||||
img_mat = np.delete(img_mat, rows_idx_to_remove, 0)
|
||||
|
||||
if direction in ["vertical", "both"]:
|
||||
cols_idx_to_remove = empty_sections(img_temp, keep, horizontal=False)
|
||||
img_mat = np.delete(img_mat, cols_idx_to_remove, 1)
|
||||
|
||||
return Image.fromarray(img_mat)
|
||||
|
||||
|
||||
'''
|
||||
Finds empty sections (excluding near borders).
|
||||
|
||||
Parameters:
|
||||
img (PIL image): A PIL image.
|
||||
keep (float): Distance to keep between panels after cropping (in percentage relative to the original distance).
|
||||
horizontal (boolean): True to find empty rows, False to find empty columns.
|
||||
Returns:
|
||||
Itertable (list or NumPy array): indices of rows or columns to remove.
|
||||
'''
|
||||
def empty_sections(img, keep, horizontal=True):
|
||||
axis = 1 if horizontal else 0
|
||||
|
||||
img_mat = np.array(img)
|
||||
img_mat_max = np.max(img_mat, axis=axis)
|
||||
img_mat_empty_idx = np.where(img_mat_max == 0)[0]
|
||||
|
||||
empty_sections = group_close_values(img_mat_empty_idx, 1)
|
||||
sections_to_remove = []
|
||||
for section in empty_sections:
|
||||
if section[1] < img.size[1] * 0.99 and section[0] > img.size[1] * 0.01: # if not near borders
|
||||
sections_to_remove.append(section)
|
||||
|
||||
if len(sections_to_remove) != 0:
|
||||
sections_to_remove_after_keep = [(int(x1+(keep/2)*(x2-x1)), int(x2-(keep/2)*(x2-x1))) for x1,x2 in sections_to_remove]
|
||||
idx_to_remove = np.concatenate([np.arange(x1, x2) for x1,x2 in sections_to_remove_after_keep])
|
||||
|
||||
return idx_to_remove
|
||||
|
||||
return []
|
||||
|
||||
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
import os.path
|
||||
import psutil
|
||||
|
||||
from . import image
|
||||
|
||||
|
||||
class Kindle:
|
||||
def __init__(self):
|
||||
def __init__(self, profile):
|
||||
self.profile = profile
|
||||
self.path = self.findDevice()
|
||||
if self.path:
|
||||
self.coverSupport = self.checkThumbnails()
|
||||
@@ -29,9 +32,11 @@ class Kindle:
|
||||
self.coverSupport = False
|
||||
|
||||
def findDevice(self):
|
||||
if self.profile in image.ProfileData.ProfilesKindlePDOC.keys():
|
||||
return False
|
||||
for drive in reversed(psutil.disk_partitions(False)):
|
||||
if (drive[2] == 'FAT32' and drive[3] == 'rw,removable') or \
|
||||
(drive[2] in ('vfat', 'msdos', 'FAT') and 'rw' in drive[3]):
|
||||
(drive[2] in ('vfat', 'msdos', 'FAT', 'apfs') and 'rw' in drive[3]):
|
||||
if os.path.isdir(os.path.join(drive[1], 'system')) and \
|
||||
os.path.isdir(os.path.join(drive[1], 'documents')):
|
||||
return drive[1]
|
||||
|
||||
@@ -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:
|
||||
|
||||
184
kindlecomicconverter/page_number_crop_alg.py
Normal file
184
kindlecomicconverter/page_number_crop_alg.py
Normal file
@@ -0,0 +1,184 @@
|
||||
from PIL import ImageOps, ImageFilter
|
||||
import numpy as np
|
||||
from .common_crop import threshold_from_power, group_close_values
|
||||
|
||||
|
||||
'''
|
||||
Some assupmptions on the page number sizes
|
||||
We assume that the size of the number (including all digits) is between
|
||||
'min_shape_size_tolerated_size' and 'max_shape_size_tolerated_size' relative to the image size.
|
||||
We assume the distance between the digit is no more than 'max_dist_size' (x,y), and no more than 3 digits.
|
||||
'''
|
||||
max_shape_size_tolerated_size = (0.015*3, 0.02) # percent
|
||||
min_shape_size_tolerated_size = (0.003, 0.006) # percent
|
||||
window_h_size = max_shape_size_tolerated_size[1]*1.25 # percent
|
||||
max_dist_size = (0.01, 0.002) # percent
|
||||
|
||||
|
||||
'''
|
||||
E-reader screen real-estate is an important resource.
|
||||
More available screensize means more details can be better seen, especially text.
|
||||
Text is one of the most important elements that need to be clearly readable on e-readers,
|
||||
which mostly are smaller devices where the need to zoom is unwanted.
|
||||
|
||||
By cropping the page number on the bottom of the page, 2%-5% of the page height can be regained
|
||||
that allows us to upscale the image even more.
|
||||
- Most of the times the screen height is the limiting factor in upscaling, rather than its width.
|
||||
|
||||
Parameters:
|
||||
img (PIL image): A PIL image.
|
||||
power (float): The power to 'chop' through pixels matching the background. Values in range[0,3].
|
||||
background_color (string): 'white' for white background, anything else for black.
|
||||
Returns:
|
||||
bbox (4-tuple, left|top|right|bot): The tightest bounding box calculated after trying to remove the bottom page number. Returns None if couldnt find anything satisfactory
|
||||
'''
|
||||
def get_bbox_crop_margin_page_number(img, power=1, background_color='white'):
|
||||
if img.mode != 'L':
|
||||
img = ImageOps.grayscale(img)
|
||||
|
||||
if background_color != 'white':
|
||||
img = ImageOps.invert(img)
|
||||
|
||||
'''
|
||||
Autocontrast: due to some threshold values, it's important that the blacks will be blacks and white will be whites.
|
||||
Box/MeanFilter: Allows us to reduce noise like bad a page scan or compression artifacts.
|
||||
Note: MedianFilter works better in my experience, but takes 2x-3x longer to perform.
|
||||
'''
|
||||
img = ImageOps.autocontrast(img, 1).filter(ImageFilter.BoxBlur(1))
|
||||
|
||||
'''
|
||||
The 'power' parameters determines the threshold. The higher the power, the more "force" it can crop through black pixels (in case of white background)
|
||||
and the lower the power, more sensitive to black pixels.
|
||||
'''
|
||||
threshold = threshold_from_power(power)
|
||||
bw_img = img.point(lambda p: 255 if p <= threshold else 0)
|
||||
bw_bbox = bw_img.getbbox()
|
||||
if not bw_bbox: # bbox cannot be found in case that the entire resulted image is black.
|
||||
return None
|
||||
|
||||
left, top_y_pos, right, bot_y_pos = bw_bbox
|
||||
|
||||
'''
|
||||
We inspect the lower bottom part of the image where we suspect might be a page number.
|
||||
We assume that page number consist of 1 to 3 digits and the total min and max size of the number
|
||||
is between 'min_shape_size_tolerated_size' and 'max_shape_size_tolerated_size'.
|
||||
'''
|
||||
window_h = int(img.size[1] * window_h_size)
|
||||
img_part = img.crop((left,bot_y_pos-window_h, right, bot_y_pos))
|
||||
|
||||
'''
|
||||
We detect related-pixels by proximity, with max distance defined in 'max_dist_size'.
|
||||
Related pixels (in x axis) for each image-row are then merged to boxes with adjacent rows (in y axis)
|
||||
to form bounding boxes of the detected objects (which one of them could be the page number).
|
||||
'''
|
||||
img_part_mat = np.array(img_part)
|
||||
window_groups = []
|
||||
for i in range(img_part.size[1]):
|
||||
row_groups = [(g[0], g[1], i, i) for g in group_close_values(np.where(img_part_mat[i] <= threshold)[0], img.size[0]*max_dist_size[0])]
|
||||
window_groups.extend(row_groups)
|
||||
|
||||
window_groups = np.array(window_groups)
|
||||
|
||||
boxes = merge_boxes(window_groups, (img.size[0]*max_dist_size[0], img.size[1]*max_dist_size[1]))
|
||||
'''
|
||||
We assume that the lowest part of the image that has black pixels on is the page number.
|
||||
In case that there are more than one detected object in the loewst part, we assume that one of them is probably
|
||||
manga-content and shouldn't be cropped.
|
||||
'''
|
||||
# filter all small objects
|
||||
boxes = list(filter(lambda box: box[1]-box[0] >= img.size[0]*min_shape_size_tolerated_size[0]
|
||||
and box[3]-box[2] >= img.size[1]*min_shape_size_tolerated_size[1], boxes))
|
||||
lowest_boxes = list(filter(lambda box: box[3] == window_h-1, boxes))
|
||||
|
||||
min_y_of_lowest_boxes = 0
|
||||
if len(lowest_boxes) > 0:
|
||||
min_y_of_lowest_boxes = np.min(np.array(lowest_boxes)[:,2])
|
||||
|
||||
boxes_in_same_y_range = list(filter(lambda box: box[3] >= min_y_of_lowest_boxes, boxes))
|
||||
|
||||
max_shape_size_tolerated = (img.size[0] * max_shape_size_tolerated_size[0],
|
||||
max(img.size[1] *max_shape_size_tolerated_size[1], 3))
|
||||
|
||||
should_force_crop = (
|
||||
len(boxes_in_same_y_range) == 1
|
||||
and (boxes_in_same_y_range[0][1] - boxes_in_same_y_range[0][0] <= max_shape_size_tolerated[0])
|
||||
and (boxes_in_same_y_range[0][3] - boxes_in_same_y_range[0][2] <= max_shape_size_tolerated[1])
|
||||
)
|
||||
|
||||
cropped_bbox = (0, 0, img.size[0], img.size[1])
|
||||
if should_force_crop:
|
||||
cropped_bbox = (0, 0, img.size[0], bot_y_pos-(window_h-boxes_in_same_y_range[0][2]+1))
|
||||
|
||||
cropped_bbox = bw_img.crop(cropped_bbox).getbbox()
|
||||
return cropped_bbox
|
||||
|
||||
|
||||
'''
|
||||
Parameters:
|
||||
img (PIL image): A PIL image.
|
||||
power (float): The power to 'chop' through pixels matching the background. Values in range[0,3].
|
||||
background_color (string): 'white' for white background, anything else for black.
|
||||
Returns:
|
||||
bbox (4-tuple, left|top|right|bot): The tightest bounding box calculated after trying to remove the bottom page number. Returns None if couldnt find anything satisfactory
|
||||
'''
|
||||
def get_bbox_crop_margin(img, power=1, background_color='white'):
|
||||
if img.mode != 'L':
|
||||
img = ImageOps.grayscale(img)
|
||||
|
||||
if background_color != 'white':
|
||||
img = ImageOps.invert(img)
|
||||
|
||||
'''
|
||||
Autocontrast: due to some threshold values, it's important that the blacks will be blacks and white will be whites.
|
||||
Box/MeanFilter: Allows us to reduce noise like bad a page scan or compression artifacts.
|
||||
Note: MedianFilter works better in my experience, but takes 2x-3x longer to perform.
|
||||
'''
|
||||
img = ImageOps.autocontrast(img, 1).filter(ImageFilter.BoxBlur(1))
|
||||
|
||||
'''
|
||||
The 'power' parameters determines the threshold. The higher the power, the more "force" it can crop through black pixels (in case of white background)
|
||||
and the lower the power, more sensitive to black pixels.
|
||||
'''
|
||||
threshold = threshold_from_power(power)
|
||||
bw_img = img.point(lambda p: 255 if p <= threshold else 0)
|
||||
|
||||
return bw_img.getbbox()
|
||||
|
||||
|
||||
def box_intersect(box1, box2, max_dist):
|
||||
return not (box2[0]-max_dist[0] > box1[1]
|
||||
or box2[1]+max_dist[0] < box1[0]
|
||||
or box2[2]-max_dist[1] > box1[3]
|
||||
or box2[3]+max_dist[1] < box1[2])
|
||||
|
||||
'''
|
||||
Merge close bounding boxes (left,right, top,bot) (x axis) with distance threshold defined in
|
||||
'max_dist_tolerated'. Boxes with less 'max_dist_tolerated' distance (Chebyshev distance).
|
||||
'''
|
||||
def merge_boxes(boxes, max_dist_tolerated):
|
||||
j = 0
|
||||
while j < len(boxes)-1:
|
||||
g1 = boxes[j]
|
||||
intersecting_boxes = []
|
||||
other_boxes = []
|
||||
for i in range(j+1,len(boxes)):
|
||||
g2 = boxes[i]
|
||||
if box_intersect(g1,g2, max_dist_tolerated):
|
||||
intersecting_boxes.append(g2)
|
||||
else:
|
||||
other_boxes.append(g2)
|
||||
|
||||
if len(intersecting_boxes) > 0:
|
||||
intersecting_boxes = np.array([g1, *intersecting_boxes])
|
||||
merged_box = np.array([
|
||||
np.min(intersecting_boxes[:,0]),
|
||||
np.max(intersecting_boxes[:,1]),
|
||||
np.min(intersecting_boxes[:,2]),
|
||||
np.max(intersecting_boxes[:,3])
|
||||
])
|
||||
other_boxes.append(merged_box)
|
||||
boxes = np.concatenate([boxes[:j], other_boxes])
|
||||
j = 0
|
||||
else:
|
||||
j += 1
|
||||
return boxes
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||
#
|
||||
# Based upon the code snippet by Ned Batchelder
|
||||
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
# above copyright notice and this permission notice appear in all
|
||||
# copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
|
||||
import os
|
||||
from random import choice
|
||||
from string import ascii_uppercase, digits
|
||||
|
||||
# skip stray images a few pixels in size in some PDFs
|
||||
# typical images are many thousands in length
|
||||
# https://github.com/ciromattia/kcc/pull/546
|
||||
STRAY_IMAGE_LENGTH_THRESHOLD = 300
|
||||
|
||||
|
||||
class PdfJpgExtract:
|
||||
def __init__(self, fname):
|
||||
self.fname = fname
|
||||
self.filename = os.path.splitext(fname)
|
||||
self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for _ in range(3))
|
||||
|
||||
def getPath(self):
|
||||
return self.path
|
||||
|
||||
def extract(self):
|
||||
pdf = open(self.fname, "rb").read()
|
||||
startmark = b"\xff\xd8"
|
||||
startfix = 0
|
||||
endmark = b"\xff\xd9"
|
||||
endfix = 2
|
||||
i = 0
|
||||
njpg = 0
|
||||
os.makedirs(self.path)
|
||||
while True:
|
||||
istream = pdf.find(b"stream", i)
|
||||
if istream < 0:
|
||||
break
|
||||
istart = pdf.find(startmark, istream, istream + 20)
|
||||
if istart < 0:
|
||||
i = istream + 20
|
||||
continue
|
||||
iend = pdf.find(b"endstream", istart)
|
||||
if iend < 0:
|
||||
raise Exception("Didn't find end of stream!")
|
||||
iend = pdf.find(endmark, iend - 20)
|
||||
if iend < 0:
|
||||
raise Exception("Didn't find end of JPG!")
|
||||
istart += startfix
|
||||
iend += endfix
|
||||
i = iend
|
||||
|
||||
if iend - istart < STRAY_IMAGE_LENGTH_THRESHOLD:
|
||||
continue
|
||||
|
||||
jpg = pdf[istart:iend]
|
||||
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
|
||||
jpgfile.write(jpg)
|
||||
jpgfile.close()
|
||||
njpg += 1
|
||||
|
||||
return self.path, njpg
|
||||
246
kindlecomicconverter/rainbow_artifacts_eraser.py
Normal file
246
kindlecomicconverter/rainbow_artifacts_eraser.py
Normal file
@@ -0,0 +1,246 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
def fourier_transform_image(img):
|
||||
"""
|
||||
Memory-optimized version that modifies the array in place when possible.
|
||||
"""
|
||||
# Convert with minimal copy
|
||||
img_array = np.asarray(img, dtype=np.float32)
|
||||
|
||||
# Use rfft2 if the image is real to save memory
|
||||
# and computation time (approximately 2x faster)
|
||||
fft_result = np.fft.rfft2(img_array)
|
||||
|
||||
return fft_result
|
||||
|
||||
def attenuate_diagonal_frequencies(fft_spectrum, freq_threshold=0.30, target_angle=135,
|
||||
angle_tolerance=10, attenuation_factor=0.10):
|
||||
"""
|
||||
Attenuates specific frequencies in the Fourier domain (optimized version for rfft2).
|
||||
|
||||
Args:
|
||||
fft_spectrum: Result of 2D real Fourier transform (from rfft2)
|
||||
freq_threshold: Frequency threshold in cycles/pixel (default: 0.3, theoretical max: 0.5)
|
||||
target_angle: Target angle in degrees (default: 135)
|
||||
angle_tolerance: Angular tolerance in degrees (default: 15)
|
||||
attenuation_factor: Attenuation factor (0.1 = 90% attenuation)
|
||||
|
||||
Returns:
|
||||
np.ndarray: Modified FFT with applied attenuation (same format as input)
|
||||
"""
|
||||
|
||||
# Get dimensions of the rfft2 result
|
||||
if fft_spectrum.ndim == 2:
|
||||
height, width_rfft = fft_spectrum.shape
|
||||
else: # 3D array (color channels)
|
||||
height, width_rfft = fft_spectrum.shape[:2]
|
||||
|
||||
# For rfft2, the original width is (width_rfft - 1) * 2
|
||||
width_original = (width_rfft - 1) * 2
|
||||
|
||||
# Create frequency grids for rfft2 format
|
||||
freq_y = np.fft.fftfreq(height, d=1.0)
|
||||
freq_x = np.fft.rfftfreq(width_original, d=1.0) # Use rfftfreq for the X dimension
|
||||
|
||||
|
||||
# Use broadcasting to create grids without meshgrid (more efficient)
|
||||
freq_y_grid = freq_y.reshape(-1, 1) # Column
|
||||
freq_x_grid = freq_x.reshape(1, -1) # Row
|
||||
|
||||
# Calculate squared radial frequencies (avoid sqrt)
|
||||
freq_radial_sq = freq_x_grid**2 + freq_y_grid**2
|
||||
freq_threshold_sq = freq_threshold**2
|
||||
|
||||
# Frequency condition
|
||||
freq_condition = freq_radial_sq >= freq_threshold_sq
|
||||
|
||||
# Early exit if no frequency satisfies the condition
|
||||
if not np.any(freq_condition):
|
||||
return fft_spectrum
|
||||
|
||||
# Calculate angles only where necessary
|
||||
# Use atan2 directly with broadcasting
|
||||
angles_rad = np.arctan2(freq_y_grid, freq_x_grid)
|
||||
|
||||
# Convert to degrees and normalize in a single operation
|
||||
angles_deg = np.rad2deg(angles_rad) % 360
|
||||
|
||||
# Calculation of complementary angle
|
||||
target_angle_2 = (target_angle + 180) % 360
|
||||
|
||||
# Calulation of perpendicular angles (135° + 45° to maximize compatibility until we know for sure which angle configure for each device)
|
||||
target_angle_3 = (target_angle + 90) % 360
|
||||
target_angle_4 = (target_angle_3 + 180) % 360
|
||||
|
||||
# Create angular conditions in a vectorized way
|
||||
angle_condition = np.zeros_like(angles_deg, dtype=bool)
|
||||
|
||||
# Process both angles simultaneously
|
||||
for angle in [target_angle, target_angle_2, target_angle_3, target_angle_4]:
|
||||
min_angle = (angle - angle_tolerance) % 360
|
||||
max_angle = (angle + angle_tolerance) % 360
|
||||
|
||||
if min_angle > max_angle: # Interval crosses 0°
|
||||
angle_condition |= (angles_deg >= min_angle) | (angles_deg <= max_angle)
|
||||
else: # Normal interval
|
||||
angle_condition |= (angles_deg >= min_angle) & (angles_deg <= max_angle)
|
||||
|
||||
# Combine conditions
|
||||
combined_condition = freq_condition & angle_condition
|
||||
|
||||
# Apply attenuation directly (avoid creating a full mask)
|
||||
if attenuation_factor == 0:
|
||||
# Special case: complete suppression
|
||||
if fft_spectrum.ndim == 2:
|
||||
fft_spectrum[combined_condition] = 0
|
||||
else: # 3D array
|
||||
fft_spectrum[combined_condition, :] = 0
|
||||
return fft_spectrum
|
||||
elif attenuation_factor == 1:
|
||||
# Special case: no attenuation
|
||||
return fft_spectrum
|
||||
else:
|
||||
# General case: partial attenuation
|
||||
if fft_spectrum.ndim == 2:
|
||||
fft_spectrum[combined_condition] *= attenuation_factor
|
||||
else: # 3D array
|
||||
fft_spectrum[combined_condition, :] *= attenuation_factor
|
||||
return fft_spectrum
|
||||
|
||||
def inverse_fourier_transform_image(fft_spectrum, is_color, original_shape=None):
|
||||
"""
|
||||
Performs an optimized inverse Fourier transform to reconstruct a PIL image.
|
||||
|
||||
Args:
|
||||
fft_spectrum: Fourier transform result (complex array from rfft2)
|
||||
is_color: Boolean indicating if the image is to be treated as color
|
||||
|
||||
Returns:
|
||||
PIL.Image: Reconstructed image
|
||||
"""
|
||||
# Perform inverse Fourier transform with original shape if provided
|
||||
if original_shape is not None:
|
||||
img_reconstructed = np.fft.irfft2(fft_spectrum, s=original_shape)
|
||||
else:
|
||||
img_reconstructed = np.fft.irfft2(fft_spectrum)
|
||||
|
||||
# Normalize values between 0 and 255
|
||||
img_reconstructed = np.clip(img_reconstructed, 0, 255)
|
||||
img_reconstructed = img_reconstructed.astype(np.uint8)
|
||||
|
||||
# Convert to PIL image
|
||||
if is_color and img_reconstructed.ndim == 3:
|
||||
pil_image = Image.fromarray(img_reconstructed, mode='RGB')
|
||||
else:
|
||||
pil_image = Image.fromarray(img_reconstructed, mode='L')
|
||||
|
||||
return pil_image
|
||||
|
||||
def rgb_to_yuv(rgb_array):
|
||||
"""
|
||||
Convert RGB to YUV color space.
|
||||
Y = luminance, U and V = chrominance
|
||||
"""
|
||||
# Coefficients for RGB to YUV conversion
|
||||
rgb_to_yuv_matrix = np.array([
|
||||
[0.299, 0.587, 0.114], # Y
|
||||
[-0.14713, -0.28886, 0.436], # U
|
||||
[0.615, -0.51499, -0.10001] # V
|
||||
])
|
||||
|
||||
# Reshape for matrix multiplication
|
||||
original_shape = rgb_array.shape
|
||||
rgb_flat = rgb_array.reshape(-1, 3)
|
||||
|
||||
# Apply transformation
|
||||
yuv_flat = rgb_flat @ rgb_to_yuv_matrix.T
|
||||
|
||||
# Reshape back
|
||||
yuv_array = yuv_flat.reshape(original_shape)
|
||||
|
||||
return yuv_array
|
||||
|
||||
def yuv_to_rgb(yuv_array):
|
||||
"""
|
||||
Convert YUV to RGB color space.
|
||||
"""
|
||||
# Coefficients for YUV to RGB conversion
|
||||
yuv_to_rgb_matrix = np.array([
|
||||
[1.0, 0.0, 1.13983], # R
|
||||
[1.0, -0.39465, -0.58060], # G
|
||||
[1.0, 2.03211, 0.0] # B
|
||||
])
|
||||
|
||||
# Reshape for matrix multiplication
|
||||
original_shape = yuv_array.shape
|
||||
yuv_flat = yuv_array.reshape(-1, 3)
|
||||
|
||||
# Apply transformation
|
||||
rgb_flat = yuv_flat @ yuv_to_rgb_matrix.T
|
||||
|
||||
# Reshape back
|
||||
rgb_array = rgb_flat.reshape(original_shape)
|
||||
|
||||
return rgb_array
|
||||
|
||||
def erase_rainbow_artifacts(img, is_color):
|
||||
"""
|
||||
Remove rainbow artifacts from grayscale or color images.
|
||||
|
||||
Args:
|
||||
img: PIL Image (grayscale or RGB)
|
||||
is_color: Boolean indicating if the image is to be treated as color
|
||||
|
||||
Returns:
|
||||
PIL.Image: Cleaned image
|
||||
"""
|
||||
# Auto-detect color mode if not specified
|
||||
if is_color is None:
|
||||
color = img.mode in ('RGB', 'RGBA', 'L') and len(np.array(img).shape) == 3
|
||||
|
||||
if is_color and img.mode in ('RGB', 'RGBA'):
|
||||
# Convert to RGB if needed
|
||||
if img.mode == 'RGBA':
|
||||
img = img.convert('RGB')
|
||||
|
||||
# Convert to numpy array
|
||||
img_array = np.array(img, dtype=np.float32)
|
||||
|
||||
# Convert to YUV color space
|
||||
yuv_array = rgb_to_yuv(img_array)
|
||||
|
||||
# Extract luminance channel (Y)
|
||||
luminance = yuv_array[:, :, 0]
|
||||
|
||||
# Process only the luminance channel
|
||||
fft_spectrum = fourier_transform_image(luminance)
|
||||
clean_spectrum = attenuate_diagonal_frequencies(fft_spectrum)
|
||||
clean_luminance = np.fft.irfft2(clean_spectrum, s=luminance.shape)
|
||||
|
||||
# Normalize and clip luminance
|
||||
clean_luminance = np.clip(clean_luminance, 0, 255)
|
||||
|
||||
# Replace luminance in YUV array
|
||||
yuv_array[:, :, 0] = clean_luminance
|
||||
|
||||
# Convert back to RGB
|
||||
rgb_array = yuv_to_rgb(yuv_array)
|
||||
rgb_array = np.clip(rgb_array, 0, 255).astype(np.uint8)
|
||||
|
||||
# Convert back to PIL image
|
||||
clean_image = Image.fromarray(rgb_array, mode='RGB')
|
||||
|
||||
else:
|
||||
# Grayscale processing (original behavior)
|
||||
if img.mode != 'L':
|
||||
img = img.convert('L')
|
||||
|
||||
# Get original image dimensions
|
||||
original_shape = (img.height, img.width)
|
||||
|
||||
fft_spectrum = fourier_transform_image(img)
|
||||
clean_spectrum = attenuate_diagonal_frequencies(fft_spectrum)
|
||||
clean_image = inverse_fourier_transform_image(clean_spectrum, is_color, original_shape)
|
||||
|
||||
return clean_image
|
||||
@@ -19,10 +19,9 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from hashlib import md5
|
||||
from html.parser import HTMLParser
|
||||
import subprocess
|
||||
from distutils.version import StrictVersion
|
||||
from packaging.version import Version
|
||||
from re import split
|
||||
import sys
|
||||
from traceback import format_tb
|
||||
@@ -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']:
|
||||
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))\
|
||||
@@ -103,7 +90,7 @@ def dependencyCheck(level):
|
||||
if level > 2:
|
||||
try:
|
||||
from PySide6.QtCore import qVersion as qtVersion
|
||||
if StrictVersion('6.5.1') > StrictVersion(qtVersion()):
|
||||
if Version('6.5.1') > Version(qtVersion()):
|
||||
missing.append('PySide 6.5.1+')
|
||||
except ImportError:
|
||||
missing.append('PySide 6.5.1+')
|
||||
@@ -114,7 +101,7 @@ def dependencyCheck(level):
|
||||
if level > 1:
|
||||
try:
|
||||
from psutil import __version__ as psutilVersion
|
||||
if StrictVersion('5.0.0') > StrictVersion(psutilVersion):
|
||||
if Version('5.0.0') > Version(psutilVersion):
|
||||
missing.append('psutil 5.0.0+')
|
||||
except ImportError:
|
||||
missing.append('psutil 5.0.0+')
|
||||
@@ -123,21 +110,27 @@ def dependencyCheck(level):
|
||||
from slugify import __version__ as slugifyVersion
|
||||
if isinstance(slugifyVersion, ModuleType):
|
||||
slugifyVersion = slugifyVersion.__version__
|
||||
if StrictVersion('1.2.1') > StrictVersion(slugifyVersion):
|
||||
if Version('1.2.1') > Version(slugifyVersion):
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
except ImportError:
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
try:
|
||||
from PIL import __version__ as pillowVersion
|
||||
if StrictVersion('5.2.0') > StrictVersion(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+')
|
||||
try:
|
||||
from pymupdf import __version__ as pymupdfVersion
|
||||
if Version('1.26.1') > Version(pymupdfVersion):
|
||||
missing.append('PyMuPDF 1.26.1+')
|
||||
except ImportError:
|
||||
missing.append('PyMuPDF 1.26.1+')
|
||||
if len(missing) > 0:
|
||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||
sys.exit(1)
|
||||
|
||||
def subprocess_run_silent(command, **kwargs):
|
||||
def subprocess_run(command, **kwargs):
|
||||
if (os.name == 'nt'):
|
||||
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
||||
return subprocess.run(command, **kwargs)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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
|
||||
mozjpeg-lossless-optimization>=1.1.2
|
||||
natsort[fast]>=8.4.0
|
||||
packaging>=23.2
|
||||
mozjpeg-lossless-optimization>=1.2.0
|
||||
natsort>=8.4.0
|
||||
distro>=1.8.0
|
||||
numpy>=1.22.4
|
||||
PyMuPDF>=1.26.1
|
||||
|
||||
15
setup.py
15
setup.py
@@ -14,7 +14,6 @@ import os
|
||||
import platform
|
||||
import sys
|
||||
import setuptools
|
||||
import distutils.cmd
|
||||
from kindlecomicconverter import __version__
|
||||
|
||||
NAME = 'KindleComicConverter'
|
||||
@@ -23,7 +22,7 @@ VERSION = __version__
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
class BuildBinaryCommand(distutils.cmd.Command):
|
||||
class BuildBinaryCommand(setuptools.Command):
|
||||
description = 'build binary release'
|
||||
user_options = []
|
||||
|
||||
@@ -37,16 +36,16 @@ class BuildBinaryCommand(distutils.cmd.Command):
|
||||
def run(self):
|
||||
VERSION = __version__
|
||||
if sys.platform == 'darwin':
|
||||
os.system('pyinstaller -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
||||
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
||||
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||
os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg')
|
||||
sys.exit(0)
|
||||
elif sys.platform == 'win32':
|
||||
os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py')
|
||||
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py')
|
||||
sys.exit(0)
|
||||
elif sys.platform == 'linux':
|
||||
os.system(
|
||||
'pyinstaller --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_linux_' + VERSION + ' kcc.py')
|
||||
'pyinstaller --hidden-import=_cffi_backend --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_linux_' + VERSION + ' kcc.py')
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(0)
|
||||
@@ -76,14 +75,16 @@ 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',
|
||||
'requests>=2.31.0',
|
||||
'mozjpeg-lossless-optimization>=1.1.2',
|
||||
'natsort[fast]>=8.4.0',
|
||||
'natsort>=8.4.0',
|
||||
'distro',
|
||||
'numpy>=1.22.4',
|
||||
'PyMuPDF>=1.26.1',
|
||||
],
|
||||
classifiers=[],
|
||||
zip_safe=False,
|
||||
|
||||
Reference in New Issue
Block a user