mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 13:38:46 +00:00
Compare commits
498 Commits
docker-bas
...
v9.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e56cde791 | ||
|
|
9a021ad5d4 | ||
|
|
d648081086 | ||
|
|
b2a8b364d9 | ||
|
|
1f935a0635 | ||
|
|
58b9651ff3 | ||
|
|
11a56dd892 | ||
|
|
c23af709cf | ||
|
|
87a7197c16 | ||
|
|
5813f914fc | ||
|
|
530ae410d4 | ||
|
|
a7428f18b6 | ||
|
|
06194b33ad | ||
|
|
80ea17ff02 | ||
|
|
e0466ad040 | ||
|
|
df924824aa | ||
|
|
82f1c1bb0a | ||
|
|
6f801a3334 | ||
|
|
ee8fc77ca9 | ||
|
|
1da06b43e2 | ||
|
|
d28740491e | ||
|
|
e2b98db1a2 | ||
|
|
4c5ec95a9b | ||
|
|
e56612228c | ||
|
|
3470d01367 | ||
|
|
a795a84899 | ||
|
|
ef44f6f285 | ||
|
|
6acdebae3c | ||
|
|
dc8136f6fd | ||
|
|
28ec2d23fc | ||
|
|
108363ce2f | ||
|
|
543fb81027 | ||
|
|
e6c6c05d40 | ||
|
|
18cd55f439 | ||
|
|
7ce4438886 | ||
|
|
841ec517ac | ||
|
|
4614fdd07a | ||
|
|
d0b72e1c83 | ||
|
|
e11755b118 | ||
|
|
a9bfc152bf | ||
|
|
f228ed261e | ||
|
|
b8020758a6 | ||
|
|
6540638e03 | ||
|
|
565e4aec45 | ||
|
|
0453fcfc90 | ||
|
|
a6ee867da2 | ||
|
|
1e4434fc7c | ||
|
|
7122945fa2 | ||
|
|
788163f3df | ||
|
|
3fa90735d5 | ||
|
|
e6cd26c773 | ||
|
|
e92b5c74de | ||
|
|
a031e4622e | ||
|
|
ae1989335e | ||
|
|
a7cb3565aa | ||
|
|
ec8098291f | ||
|
|
297e8757f0 | ||
|
|
2cfd6d9728 | ||
|
|
5733786dc6 | ||
|
|
8a862f11ac | ||
|
|
c32620cfeb | ||
|
|
512cac7f8a | ||
|
|
5e86acc740 | ||
|
|
20388304e8 | ||
|
|
420bed995b | ||
|
|
bc92c2dd85 | ||
|
|
a1f4e040ba | ||
|
|
137d53672a | ||
|
|
0cc75ab1e7 | ||
|
|
4cecf6fc4d | ||
|
|
2f0c9ae95d | ||
|
|
b856a176b0 | ||
|
|
e50163fb59 | ||
|
|
0ab20a5ce3 | ||
|
|
9c69a6fdcc | ||
|
|
26f0bf9989 | ||
|
|
b3db3256d6 | ||
|
|
96a92fb9bb | ||
|
|
18f02df3a1 | ||
|
|
235db43fa6 | ||
|
|
cab7cae714 | ||
|
|
a41f947030 | ||
|
|
d2828877af | ||
|
|
704dcd6dbe | ||
|
|
6a7500441d | ||
|
|
753eeb64eb | ||
|
|
a63d9e3ad0 | ||
|
|
cc01dc611a | ||
|
|
9a605c2d8a | ||
|
|
a7005748c7 | ||
|
|
55193119fb | ||
|
|
741cab68f3 | ||
|
|
7f8f3e67b7 | ||
|
|
be12661f38 | ||
|
|
06e2ee2968 | ||
|
|
27296565a3 | ||
|
|
bb70337a35 | ||
|
|
cf0586ae70 | ||
|
|
4100141b46 | ||
|
|
8eb81b7d67 | ||
|
|
e2dbc05a83 | ||
|
|
50b82786a1 | ||
|
|
88d1643f64 | ||
|
|
ddf2fa360f | ||
|
|
43e974f20d | ||
|
|
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 | ||
|
|
2fc32bae58 | ||
|
|
db25a0939c | ||
|
|
1bd7506140 | ||
|
|
4fc5cc9dfb | ||
|
|
89289412a3 | ||
|
|
193297c8fc | ||
|
|
367d71e4ad | ||
|
|
cdde0f18cd | ||
|
|
9fe82a9dd4 | ||
|
|
e958edd135 | ||
|
|
6738b70487 | ||
|
|
44094bdb21 | ||
|
|
a9a2f47e30 | ||
|
|
c35dd137ea | ||
|
|
1ea008c8f3 | ||
|
|
cbc1ed5db4 | ||
|
|
6bf662bea3 | ||
|
|
cfae306731 | ||
|
|
f6475f4c61 | ||
|
|
9646518575 | ||
|
|
937dd3984a | ||
|
|
4e6f6ec8e8 | ||
|
|
4b36fdbfa2 | ||
|
|
d8141af4eb | ||
|
|
cf38c4d445 | ||
|
|
283b2da1a0 | ||
|
|
4a9f693574 | ||
|
|
9a48887edc | ||
|
|
0981ec3c6d | ||
|
|
e0e6606736 | ||
|
|
518e67c132 | ||
|
|
6c593dac1f | ||
|
|
9d065676db | ||
|
|
9520e59a29 | ||
|
|
461a7fda88 | ||
|
|
5ccbb770a6 | ||
|
|
49bf80f5d8 | ||
|
|
33cc324381 | ||
|
|
6ba5539813 | ||
|
|
f725196106 | ||
|
|
c8ff88ed0f | ||
|
|
33fed662fe | ||
|
|
bc7cd17916 | ||
|
|
c5e1e18ac2 | ||
|
|
ee31b784cb | ||
|
|
48541404ee | ||
|
|
acbebcfd40 | ||
|
|
dd6273b864 | ||
|
|
e8502f008a | ||
|
|
169a41e7d2 | ||
|
|
57cf669cdd | ||
|
|
0b4f089b8e | ||
|
|
7eb985337c | ||
|
|
d62690e8bf | ||
|
|
3988f2012f | ||
|
|
6cdd9d5909 | ||
|
|
c29a4beac9 | ||
|
|
5f7bdef325 | ||
|
|
54b5d698ee | ||
|
|
a99c63acea | ||
|
|
251df2e7ba | ||
|
|
b1379b7c59 | ||
|
|
983bde1691 | ||
|
|
e79e5a311c | ||
|
|
39c73dbc0f | ||
|
|
915c9389ef | ||
|
|
89d887710e | ||
|
|
2e9bc5381a | ||
|
|
ab1ce158c7 | ||
|
|
6278adfb25 | ||
|
|
6e6c13047e | ||
|
|
b528dab711 | ||
|
|
2ffefee928 | ||
|
|
a5e5407363 | ||
|
|
da1ba64bd2 | ||
|
|
6dcaf9a6d1 | ||
|
|
3090a47f20 | ||
|
|
1e537915d4 | ||
|
|
7273ca25b8 | ||
|
|
68da853e42 | ||
|
|
431862a2e9 | ||
|
|
65062f8984 | ||
|
|
8122fa1e45 | ||
|
|
13fedff77b | ||
|
|
61b1207a3e | ||
|
|
60e9f075b8 | ||
|
|
424118b7cd | ||
|
|
4e45402b8f | ||
|
|
615790c278 |
@@ -7,8 +7,7 @@ KindleComicConverter.egg-info
|
|||||||
.gitignore
|
.gitignore
|
||||||
.travis.yml
|
.travis.yml
|
||||||
Dockerfile
|
Dockerfile
|
||||||
other
|
|
||||||
venv
|
venv
|
||||||
*.md
|
*.md
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
MANIFEST.in
|
MANIFEST.in
|
||||||
|
|||||||
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']
|
||||||
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
Add a screenshot of your KCC settings.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. macOS, Linux, Windows 11]
|
||||||
|
- Device [e.g. Kindle Paperwhite 3rd gen, Kobo Libra 2]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -38,11 +38,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v4
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -69,6 +69,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v4
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
15
.github/workflows/package-linux.yml
vendored
15
.github/workflows/package-linux.yml
vendored
@@ -23,19 +23,19 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
- name: Install python dependencies
|
- name: Install python dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pyqt5 python3-pip squashfs-tools libfuse2
|
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pip squashfs-tools libfuse2 libxcb-cursor0
|
||||||
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller PyQt6 --no-binary pyinstaller
|
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller --no-binary pyinstaller
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
@@ -59,17 +59,16 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync
|
UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync
|
||||||
- name: upload artifact
|
- name: upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AppImage
|
name: AppImage
|
||||||
path: './*.AppImage*'
|
path: './*.AppImage*'
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
*.AppImage*
|
*.AppImage*
|
||||||
|
|||||||
20
.github/workflows/package-macos.yml
vendored
20
.github/workflows/package-macos.yml
vendored
@@ -23,11 +23,14 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ macos-13, macos-14 ]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -66,7 +69,7 @@ jobs:
|
|||||||
# apply provisioning profile
|
# apply provisioning profile
|
||||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
|
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- run: npm install -g appdmg
|
- run: npm install -g appdmg
|
||||||
@@ -75,18 +78,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python setup.py build_binary
|
python setup.py build_binary
|
||||||
- name: upload build
|
- name: upload build
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: mac-os-build
|
name: mac-os-build-${{ runner.arch }}
|
||||||
path: dist/*.dmg
|
path: dist/*.dmg
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
- name: Clean up keychain and provisioning profile
|
- name: Clean up keychain and provisioning profile
|
||||||
@@ -95,4 +97,4 @@ jobs:
|
|||||||
# if: ${{ always() }}
|
# if: ${{ always() }}
|
||||||
run: |
|
run: |
|
||||||
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
|
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
|
||||||
rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision
|
rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision
|
||||||
|
|||||||
66
.github/workflows/package-osx-legacy.yml
vendored
Normal file
66
.github/workflows/package-osx-legacy.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: build KCC for osx legacy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
# Don't trigger if it's just a documentation update
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- '**.MD'
|
||||||
|
- '**.yml'
|
||||||
|
- '**.sh'
|
||||||
|
- 'docs/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.gitattributes'
|
||||||
|
- '.gitignore'
|
||||||
|
- '.dockerignore'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ macos-13 ]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
# We need the official Python, because the GA ones only support newer macOS versions
|
||||||
|
# The deployment target is picked up by the Python build tools automatically
|
||||||
|
PYTHON_VERSION: 3.11.9
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: '10.14'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Get Python
|
||||||
|
run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg"
|
||||||
|
- name: Install Python
|
||||||
|
run: |
|
||||||
|
sudo installer -pkg python.pkg -target /
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
python3 --version
|
||||||
|
pip3 install --upgrade pip setuptools wheel pyinstaller certifi
|
||||||
|
pip3 install --upgrade -r requirements-osx-legacy.txt
|
||||||
|
./gen_ui_files.sh
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
- run: npm install -g appdmg
|
||||||
|
- name: build binary
|
||||||
|
run: |
|
||||||
|
python3 setup.py build_binary
|
||||||
|
- name: upload build
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: osx-build-${{ runner.arch }}
|
||||||
|
path: dist/*.dmg
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
LICENSE.txt
|
||||||
|
dist/*.dmg
|
||||||
@@ -10,54 +10,53 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
# - 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
|
- name: Package Application
|
||||||
uses: JackMcKew/pyinstaller-action-windows@main
|
uses: JackMcKew/pyinstaller-action-windows@main
|
||||||
with:
|
with:
|
||||||
path: .
|
path: .
|
||||||
spec: ./kcc.spec
|
spec: ./${{ matrix.entry }}.spec
|
||||||
- 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
|
|
||||||
- name: rename binaries
|
- name: rename binaries
|
||||||
run: |
|
run: |
|
||||||
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
|
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
|
||||||
mv dist/windows/kcc.exe dist/windows/kcc_${version_built}.exe
|
mv dist/windows/${{ matrix.entry }}.exe dist/windows/${{ matrix.capital }}_${version_built}.exe
|
||||||
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-unsigned-artifact
|
||||||
- name: upload build
|
id: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-build
|
name: windows-build-${{ matrix.entry }}
|
||||||
path: dist/windows/*.exe
|
path: dist/windows/*.exe
|
||||||
|
|
||||||
|
- id: optional_step_id
|
||||||
|
uses: signpath/github-action-submit-signing-request@v1.3
|
||||||
|
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
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
dist/windows/*.exe
|
dist/windows/*.exe
|
||||||
|
|||||||
25
.github/workflows/package-windows.yml
vendored
25
.github/workflows/package-windows.yml
vendored
@@ -25,9 +25,9 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -41,18 +41,29 @@ jobs:
|
|||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
python setup.py build_binary
|
python setup.py build_binary
|
||||||
- name: upload build
|
- name: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v3
|
id: upload-unsigned-artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-build
|
name: windows-build
|
||||||
path: dist/*.exe
|
path: dist/*.exe
|
||||||
|
- id: optional_step_id
|
||||||
|
uses: signpath/github-action-submit-signing-request@v1.3
|
||||||
|
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
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
dist/*.exe
|
dist/*.exe
|
||||||
|
|||||||
60
.github/workflows/package-windows7.yml
vendored
Normal file
60
.github/workflows/package-windows7.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||||
|
|
||||||
|
name: build KCC for windows 7
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
# Don't trigger if it's just a documentation update
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- '**.MD'
|
||||||
|
- '**.yml'
|
||||||
|
- '**.sh'
|
||||||
|
- 'docs/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.gitattributes'
|
||||||
|
- '.gitignore'
|
||||||
|
- '.dockerignore'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-2022
|
||||||
|
env:
|
||||||
|
WINDOWS_7: 1
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
cache: 'pip'
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
PYINSTALLER_COMPILE_BOOTLOADER: 1
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip setuptools wheel
|
||||||
|
pip install -r requirements-win7.txt
|
||||||
|
pip install certifi pyinstaller --no-binary pyinstaller
|
||||||
|
.\gen_ui_files.bat
|
||||||
|
- name: build binary
|
||||||
|
run: |
|
||||||
|
python setup.py build_binary
|
||||||
|
- name: upload-unsigned-artifact
|
||||||
|
id: upload-unsigned-artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows7-build
|
||||||
|
path: dist/*.exe
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
dist/*.exe
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,6 +8,9 @@ dist/
|
|||||||
build/
|
build/
|
||||||
KindleComicConverter*.egg-info/
|
KindleComicConverter*.egg-info/
|
||||||
.idea/
|
.idea/
|
||||||
|
win7
|
||||||
|
osx10.11
|
||||||
/venv/
|
/venv/
|
||||||
/kindlegen*
|
/kindlegen*
|
||||||
/kcc.bat
|
/kcc.bat
|
||||||
|
.DS_Store
|
||||||
|
|||||||
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
|
# Select final stage based on TARGETARCH ARG
|
||||||
FROM ghcr.io/ciromattia/kcc:docker-base-20230514
|
FROM ghcr.io/ciromattia/kcc:docker-base-20241116
|
||||||
LABEL com.kcc.name="Kindle Comic Converter"
|
LABEL com.kcc.name="Kindle Comic Converter"
|
||||||
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
||||||
LABEL org.opencontainers.image.description='Kindle Comic Converter'
|
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
|
RUN cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
|
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 TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
@@ -8,7 +8,7 @@ RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
|||||||
COPY requirements.txt /opt/kcc/
|
COPY requirements.txt /opt/kcc/
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 python3-pyqt5 && \
|
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 && \
|
||||||
python -m pip install --upgrade pip && \
|
python -m pip install --upgrade pip && \
|
||||||
python -m venv /opt/venv && \
|
python -m venv /opt/venv && \
|
||||||
python -m pip install -r /opt/kcc/requirements.txt
|
python -m pip install -r /opt/kcc/requirements.txt
|
||||||
@@ -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 TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
@@ -28,6 +28,9 @@ ENV LC_ALL=C.UTF-8 \
|
|||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
COPY requirements.txt /opt/kcc/
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
RUN set -x && \
|
RUN set -x && \
|
||||||
TEMP_PACKAGES=() && \
|
TEMP_PACKAGES=() && \
|
||||||
KEPT_PACKAGES=() && \
|
KEPT_PACKAGES=() && \
|
||||||
@@ -55,7 +58,6 @@ RUN set -x && \
|
|||||||
KEPT_PACKAGES+=(p7zip-full) && \
|
KEPT_PACKAGES+=(p7zip-full) && \
|
||||||
KEPT_PACKAGES+=(python3) && \
|
KEPT_PACKAGES+=(python3) && \
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
KEPT_PACKAGES+=(python3-pip) && \
|
||||||
KEPT_PACKAGES+=(python3-pyqt5) && \
|
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
KEPT_PACKAGES+=(unrar-free) && \
|
||||||
# Install packages
|
# Install packages
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
@@ -65,14 +67,13 @@ RUN set -x && \
|
|||||||
&& \
|
&& \
|
||||||
# Install required python modules
|
# Install required python modules
|
||||||
python -m pip install --upgrade pip && \
|
python -m pip install --upgrade pip && \
|
||||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
|
||||||
python -m venv /opt/venv && \
|
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 TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
@@ -84,6 +85,9 @@ ENV LC_ALL=C.UTF-8 \
|
|||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
COPY requirements.txt /opt/kcc/
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
RUN set -x && \
|
RUN set -x && \
|
||||||
TEMP_PACKAGES=() && \
|
TEMP_PACKAGES=() && \
|
||||||
KEPT_PACKAGES=() && \
|
KEPT_PACKAGES=() && \
|
||||||
@@ -112,7 +116,6 @@ RUN set -x && \
|
|||||||
KEPT_PACKAGES+=(p7zip-full) && \
|
KEPT_PACKAGES+=(p7zip-full) && \
|
||||||
KEPT_PACKAGES+=(python3) && \
|
KEPT_PACKAGES+=(python3) && \
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
KEPT_PACKAGES+=(python3-pip) && \
|
||||||
KEPT_PACKAGES+=(python3-pyqt5) && \
|
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
KEPT_PACKAGES+=(unrar-free) && \
|
||||||
# Install packages
|
# Install packages
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
@@ -122,19 +125,18 @@ RUN set -x && \
|
|||||||
&& \
|
&& \
|
||||||
# Install required python modules
|
# Install required python modules
|
||||||
python -m pip install --upgrade pip && \
|
python -m pip install --upgrade pip && \
|
||||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
|
||||||
python -m venv /opt/venv && \
|
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 pymupdf
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
######################################################################################
|
||||||
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
|
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
|
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
|
COPY --from=compile-armv7 /opt/venv /opt/venv
|
||||||
######################################################################################
|
######################################################################################
|
||||||
|
|
||||||
@@ -158,5 +160,5 @@ WORKDIR /app
|
|||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
apt-get install -y p7zip-full unrar-free && \
|
apt-get install -y p7zip-full unrar-free && \
|
||||||
ln -s /app/kindlegen /bin/kindlegen && \
|
ln -s /app/kindlegen /bin/kindlegen && \
|
||||||
echo docker-base-20230514 > /IMAGE_VERSION
|
echo docker-base-20241116 > /IMAGE_VERSION
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
ISC LICENSE
|
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) 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
|
Permission to use, copy, modify, and/or distribute this software for
|
||||||
any purpose with or without fee is hereby granted, provided that the
|
any purpose with or without fee is hereby granted, provided that the
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
exclude kindlecomicconverter/sentry.py
|
|
||||||
288
README.md
288
README.md
@@ -1,15 +1,52 @@
|
|||||||
|
<img src="header.jpg" alt="Header Image" width="400">
|
||||||
|
|
||||||
# KCC
|
# KCC
|
||||||
|
|
||||||
|
[](https://github.com/ciromattia/kcc/releases)
|
||||||
|
[](https://github.com/ciromattia/kcc/pkgs/container/kcc)
|
||||||
|
[](https://github.com/ciromattia/kcc/releases)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/ciromattia/kcc/releases)
|
**Kindle Comic Converter** optimizes black & white comics and manga for E-ink ereaders
|
||||||
[](https://github.com/ciromattia/kcc/pkgs/container/kcc)
|
like Kindle, Kobo, ReMarkable, and more.
|
||||||
|
Pages display in fullscreen without margins,
|
||||||
|
with proper fixed layout support.
|
||||||
|
Supported input formats include JPG/PNG image files in folders, archives, or PDFs.
|
||||||
|
Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF.
|
||||||
|
|
||||||
|
**NEW**: PDF output is now supported for direct conversion to reMarkable devices!
|
||||||
|
When using a reMarkable profile (Rmk1, Rmk2, RmkPP), the format automatically defaults to PDF
|
||||||
|
for optimal compatibility with your device's native PDF reader.
|
||||||
|
|
||||||
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
|
The absolute highest quality source files are print quality DRM-free PDFs from Kodansha/[Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1)/Fanatical,
|
||||||
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
|
which can be directly converted by KCC.
|
||||||
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.
|
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
|
### 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.
|
**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,59 +59,97 @@ 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 can fix an open issue, fork & make a pull request.
|
||||||
|
|
||||||
If you find **KCC** valuable you can consider donating to the authors:
|
If you find **KCC** valuable you can consider donating to the authors:
|
||||||
- Ciro Mattia Gonano:
|
- Ciro Mattia Gonano (founder, active 2012-2014):
|
||||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
|
||||||
- [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||||
- Paweł Jastrzębski:
|
|
||||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
- Paweł Jastrzębski (active 2013-2019):
|
||||||
- [](https://jastrzeb.ski/donate/)
|
|
||||||
|
[](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
|
||||||
|
|
||||||
|
|
||||||
## INSTALLATION
|
## Sponsors
|
||||||
|
|
||||||
### DOWNLOADS
|
- Free code signing on Windows provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||||
You can find the latest binary at the following link:
|
|
||||||
|
## DOWNLOADS
|
||||||
|
|
||||||
- **https://github.com/ciromattia/kcc/releases**
|
- **https://github.com/ciromattia/kcc/releases**
|
||||||
- flatpak : https://flathub.org/apps/details/io.github.ciromattia.kcc
|
|
||||||
- Docker: https://github.com/ciromattia/kcc/pkgs/container/kcc
|
|
||||||
|
|
||||||
more information on [installation](https://github.com/ciromattia/kcc/wiki/Installation)
|
Click on **Assets** of the latest release.
|
||||||
|
|
||||||
### DEPENDENCIES
|
You probably want either
|
||||||
Following software is required to run Linux version of **KCC** and/or bare sources:
|
- `KCC_*.*.*.exe` (Windows)
|
||||||
- Python 3.3+
|
- `kcc_macos_arm_*.*.*.dmg` (recent Mac with Apple Silicon M1 chip or later)
|
||||||
- [PyQt5](https://pypi.python.org/pypi/PyQt5) 5.6.0+ (only needed for GUI)
|
- `kcc_macos_i386_*.*.*.dmg` (older Mac with Intel chip macOS 12+)
|
||||||
- [Pillow](https://pypi.python.org/pypi/Pillow/) 4.0.0+ (5.2.0+ needed for WebP support)
|
|
||||||
- [psutil](https://pypi.python.org/pypi/psutil) 5.0.0+
|
|
||||||
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.1+, <8.0.0
|
|
||||||
- [raven](https://pypi.python.org/pypi/raven) 6.0.0+ (only needed for GUI)
|
|
||||||
|
|
||||||
On Debian based distributions these two commands should install all needed dependencies:
|
There are also legacy macOS 10.14+ and Windows 7 experimental versions available.
|
||||||
|
|
||||||
|
The `c2e` and `c2p` versions are command line tools for power users.
|
||||||
|
|
||||||
|
On Mac, right click open to get past the security warning.
|
||||||
|
|
||||||
```bash
|
For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation
|
||||||
$ sudo apt-get install -y python3 python3-dev libpng-dev libjpeg-dev p7zip-full p7zip-rar unrar-free libgl1 python3-pyqt5 && \
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
python -m pip install --upgrade -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
- Should I use Calibre?
|
||||||
|
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) 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.15.1+, 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.
|
||||||
|
- Blank pages?
|
||||||
|
- May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast.
|
||||||
|
Going back a few pages and exiting and re-entering book should fix it temporarily.
|
||||||
|
- What output format should I use?
|
||||||
|
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable.
|
||||||
|
- 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)
|
||||||
|
- 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
|
||||||
|
- 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.
|
||||||
|
|
||||||
#### Optional dependencies
|
## PREREQUISITES
|
||||||
- Qt platform integration plugin for Deepin Desktop Environment
|
|
||||||
```bash
|
|
||||||
$ sudo apt-get install qt5dxcb-plugin
|
|
||||||
```
|
|
||||||
|
|
||||||
- KindleGen ~~[(deprecated link)](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211)~~ v2.9+ (For MOBI generation)
|
You'll need to install various tools to access important but optional features. Close and re-open KCC to get KCC to detect them.
|
||||||
- should be placed in a directory reachable by your _PATH_ or in _KCC_ directory
|
|
||||||
- `KindleGen` can be found in [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011)
|
|
||||||
- `KindleGen` can be also be found in [Kindle Comic Creator](https://www.amazon.com/b?node=23496309011)
|
|
||||||
- [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)*
|
|
||||||
- Unrar (no rar in 7z on Fedora)
|
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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 optional but will make conversions much faster.
|
||||||
|
|
||||||
|
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
|
## INPUT FORMATS
|
||||||
**KCC** can understand and convert, at the moment, the following input types:
|
**KCC** can understand and convert, at the moment, the following input types:
|
||||||
@@ -104,12 +179,14 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
|
|||||||
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
|
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
|
||||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
||||||
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
||||||
'K578': ("Kindle", (600, 800), Palette16, 1.8),
|
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.8),
|
||||||
|
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.8),
|
||||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), 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),
|
'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),
|
'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.8),
|
||||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
|
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
|
||||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
|
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
|
||||||
@@ -120,14 +197,18 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
|
|||||||
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
|
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
|
||||||
'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.8),
|
'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.8),
|
||||||
'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.8),
|
'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.8),
|
||||||
|
'KoCC': ("Kobo Clara Colour", (1072, 1448), Palette16, 1.8),
|
||||||
'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.8),
|
'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.8),
|
||||||
|
'KoLC': ("Kobo Libra Colour", (1264, 1680), Palette16, 1.8),
|
||||||
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
||||||
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8),
|
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8),
|
||||||
'KoE': ("Kobo Elipsa", (1404, 1872), 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),
|
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Standalone `kcc-c2e.py` usage:
|
### Standalone `kcc-c2e.py` usage:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -138,7 +219,8 @@ MANDATORY:
|
|||||||
|
|
||||||
MAIN:
|
MAIN:
|
||||||
-p PROFILE, --profile PROFILE
|
-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)
|
-m, --manga-style Manga style (right-to-left reading and splitting)
|
||||||
-q, --hq Try to increase the quality of magnification
|
-q, --hq Try to increase the quality of magnification
|
||||||
-2, --two-panel Display two not four panels in Panel View mode
|
-2, --two-panel Display two not four panels in Panel View mode
|
||||||
@@ -147,19 +229,25 @@ MAIN:
|
|||||||
the maximal size of output file in MB. [Default=100MB for webtoon and 400MB for others]
|
the maximal size of output file in MB. [Default=100MB for webtoon and 400MB for others]
|
||||||
|
|
||||||
PROCESSING:
|
PROCESSING:
|
||||||
-n, --noprocessing Do not modify image and ignore any profil or processing option
|
-n, --noprocessing Do not modify image and ignore any profile or processing option
|
||||||
-u, --upscale Resize images smaller than device's resolution
|
-u, --upscale Resize images smaller than device's resolution
|
||||||
-s, --stretch Stretch images to device's resolution
|
-s, --stretch Stretch images to device's resolution
|
||||||
-r SPLITTER, --splitter SPLITTER
|
-r SPLITTER, --splitter SPLITTER
|
||||||
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
|
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
|
||||||
-g GAMMA, --gamma GAMMA
|
-g GAMMA, --gamma GAMMA
|
||||||
Apply gamma correction to linearize the image [Default=Auto]
|
Apply gamma correction to linearize the image [Default=Auto]
|
||||||
|
--autolevel Set most common dark pixel value to be black point for leveling.
|
||||||
|
--noautocontrast Disable autocontrast
|
||||||
|
--colorautocontrast Force autocontrast for all pages. Skipped when near blacks and whites don't exist
|
||||||
-c CROPPING, --cropping CROPPING
|
-c CROPPING, --cropping CROPPING
|
||||||
Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
|
Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
|
||||||
--cp CROPPINGP, --croppingpower CROPPINGP
|
--cp CROPPINGP, --croppingpower CROPPINGP
|
||||||
Set cropping power [Default=1.0]
|
Set cropping power [Default=1.0]
|
||||||
|
--preservemargin After calculating crop, "back up" a specified percentage amount [Default=0]
|
||||||
--cm CROPPINGM, --croppingminimum CROPPINGM
|
--cm CROPPINGM, --croppingminimum CROPPINGM
|
||||||
Set cropping minimum area ratio [Default=0.0]
|
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
|
--blackborders Disable autodetection and force black borders
|
||||||
--whiteborders Disable autodetection and force white borders
|
--whiteborders Disable autodetection and force white borders
|
||||||
--forcecolor Don't convert images to grayscale
|
--forcecolor Don't convert images to grayscale
|
||||||
@@ -173,10 +261,18 @@ OUTPUT SETTINGS:
|
|||||||
Output generated file to specified directory or file
|
Output generated file to specified directory or file
|
||||||
-t TITLE, --title TITLE
|
-t TITLE, --title TITLE
|
||||||
Comic title [Default=filename or directory name]
|
Comic title [Default=filename or directory name]
|
||||||
|
--metadatatitle Write title using ComicInfo.xml or other embedded metadata. 0: Don't use Title from metadata 1: Combine Title with default schema 2: Use Title only [Default=0]
|
||||||
|
-a AUTHOR, --author AUTHOR
|
||||||
|
Author name [Default=KCC]
|
||||||
-f FORMAT, --format FORMAT
|
-f FORMAT, --format FORMAT
|
||||||
Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) [Default=Auto]
|
Output format (Available options: Auto, MOBI, EPUB, CBZ, PDF, KFX, MOBI+EPUB) [Default=Auto]
|
||||||
|
--nokepub If format is EPUB, output file with '.epub' extension rather than '.kepub.epub'
|
||||||
-b BATCHSPLIT, --batchsplit BATCHSPLIT
|
-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]
|
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:
|
CUSTOM PROFILE:
|
||||||
--customwidth CUSTOMWIDTH
|
--customwidth CUSTOMWIDTH
|
||||||
@@ -208,8 +304,85 @@ OTHER:
|
|||||||
-h, --help Show this help message and exit
|
-h, --help Show this help message and exit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## INSTALL FROM SOURCE
|
||||||
|
|
||||||
|
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 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 `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
|
||||||
|
|
||||||
|
One time setup and running for the first time:
|
||||||
|
```
|
||||||
|
python -m venv venv
|
||||||
|
venv\Scripts\activate.bat
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python kcc.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Every time you close Command Prompt, you will need to re-activate the virtual environment and re-run:
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python kcc.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Every time you close Terminal, you will need to reactivate the virtual environment and re-run:
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
## CREDITS
|
||||||
**KCC** is made by [Ciro Mattia Gonano](http://github.com/ciromattia), [Paweł Jastrzębski](http://github.com/AcidWeb) and [Darodi](http://github.com/darodi) .
|
**KCC** is made by
|
||||||
|
|
||||||
|
- [Ciro Mattia Gonano](http://github.com/ciromattia)
|
||||||
|
- [Paweł Jastrzębski](http://github.com/AcidWeb)
|
||||||
|
- [Darodi](http://github.com/darodi)
|
||||||
|
- [Alex Xu](http://github.com/axu2)
|
||||||
|
|
||||||
This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published [here](http://www.mobileread.com/forums/showthread.php?t=192783)).
|
This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published [here](http://www.mobileread.com/forums/showthread.php?t=192783)).
|
||||||
|
|
||||||
@@ -220,6 +393,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.
|
- 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
|
## 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 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 3 / 4 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||||
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
||||||
@@ -232,12 +411,15 @@ The app relies and includes the following scripts:
|
|||||||
|
|
||||||
## PRIVACY
|
## PRIVACY
|
||||||
**KCC** is initiating internet connections in two cases:
|
**KCC** is initiating internet connections in two cases:
|
||||||
* During startup - Version check.
|
* During startup - Version check and announcement check.
|
||||||
* When error occurs - Automatic reporting on Windows and macOS.
|
* When error occurs - Automatic reporting on Windows and macOS.
|
||||||
|
|
||||||
## KNOWN ISSUES
|
## KNOWN ISSUES
|
||||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||||
|
|
||||||
## COPYRIGHT
|
## COPYRIGHT
|
||||||
Copyright (c) 2012-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.
|
**KCC** is released under ISC LICENSE; see [LICENSE.txt](./LICENSE.txt) for further details.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
Impact-Site-Verification: ffe48fc7-4f0c-40fd-bd2e-59f4d7205180
|
||||||
|
|||||||
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,13 @@ channels:
|
|||||||
- defaults
|
- defaults
|
||||||
dependencies:
|
dependencies:
|
||||||
- python=3.11
|
- python=3.11
|
||||||
- Pillow>=5.2.0
|
- Pillow>=11.3.0
|
||||||
- psutil>=5.0.0
|
- psutil>=5.9.5
|
||||||
- python-slugify>=1.2.1
|
- python-slugify>=1.2.1
|
||||||
- raven>=6.0.0
|
- raven>=6.0.0
|
||||||
- distro
|
- distro
|
||||||
|
- natsort>=8.4.0
|
||||||
- pip
|
- pip
|
||||||
- pip:
|
- pip:
|
||||||
- mozjpeg-lossless-optimization>=1.1.2
|
- mozjpeg-lossless-optimization>=1.1.2
|
||||||
- PyQt5>=5.6.0
|
- pyside6>=6.5.1
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
|
pyside6-uic gui/KCC.ui --from-imports > kindlecomicconverter/KCC_ui.py
|
||||||
REM install qt creator
|
pyside6-uic gui/MetaEditor.ui --from-imports > kindlecomicconverter/KCC_ui_editor.py
|
||||||
REM conda create -n qtenv python=3.7
|
pyside6-rcc gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
|
||||||
REM conda activate qtenv
|
|
||||||
REM pip install PyQt5
|
|
||||||
|
|
||||||
pyuic5 gui/KCC.ui > kindlecomicconverter/KCC_ui.py
|
|
||||||
|
|
||||||
pyuic5 gui/MetaEditor.ui > kindlecomicconverter/KCC_ui_editor.py
|
|
||||||
|
|
||||||
pyrcc5 gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# PREPARE PYTHON ENV
|
pyside6-uic gui/KCC.ui --from-imports > kindlecomicconverter/KCC_ui.py
|
||||||
# conda create -n pyqt5 python=3.7
|
pyside6-uic gui/MetaEditor.ui --from-imports > kindlecomicconverter/KCC_ui_editor.py
|
||||||
# source activate pyqt5
|
pyside6-rcc gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
|
||||||
# pip install pyqt5
|
|
||||||
|
|
||||||
pyuic5 gui/KCC.ui --from-imports > kindlecomicconverter/KCC_ui.py
|
|
||||||
pyuic5 gui/MetaEditor.ui --from-imports > kindlecomicconverter/KCC_ui_editor.py
|
|
||||||
pyrcc5 gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<file>../icons/Kobo.png</file>
|
<file>../icons/Kobo.png</file>
|
||||||
<file>../icons/Other.png</file>
|
<file>../icons/Other.png</file>
|
||||||
<file>../icons/Kindle.png</file>
|
<file>../icons/Kindle.png</file>
|
||||||
|
<file>../icons/Rmk.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="Formats">
|
<qresource prefix="Formats">
|
||||||
<file>../icons/CBZ.png</file>
|
<file>../icons/CBZ.png</file>
|
||||||
@@ -27,4 +28,9 @@
|
|||||||
<file>../icons/document_new.png</file>
|
<file>../icons/document_new.png</file>
|
||||||
<file>../icons/folder_new.png</file>
|
<file>../icons/folder_new.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
<qresource prefix="Brand">
|
||||||
|
<file>../icons/kofi_symbol.png</file>
|
||||||
|
<file>../icons/Humble_H-Red.png</file>
|
||||||
|
<file>../icons/Bindle_Red.png</file>
|
||||||
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
1069
gui/KCC.ui
1069
gui/KCC.ui
File diff suppressed because it is too large
Load Diff
@@ -62,56 +62,66 @@
|
|||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QLineEdit" name="volumeLine"/>
|
<widget class="QLineEdit" name="volumeLine"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number:</string>
|
<string>Number:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="QLineEdit" name="numberLine"/>
|
<widget class="QLineEdit" name="numberLine"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QLabel" name="label_4">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Writer:</string>
|
<string>Writer:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QLineEdit" name="writerLine"/>
|
<widget class="QLineEdit" name="writerLine"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="5" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Penciller:</string>
|
<string>Penciller:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="5" column="1">
|
||||||
<widget class="QLineEdit" name="pencillerLine"/>
|
<widget class="QLineEdit" name="pencillerLine"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Inker:</string>
|
<string>Inker:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="QLineEdit" name="inkerLine"/>
|
<widget class="QLineEdit" name="inkerLine"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
<item row="7" column="0">
|
||||||
<widget class="QLabel" name="label_7">
|
<widget class="QLabel" name="label_7">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Colorist:</string>
|
<string>Colorist:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1">
|
<item row="7" column="1">
|
||||||
<widget class="QLineEdit" name="coloristLine"/>
|
<widget class="QLineEdit" name="coloristLine"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Title:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="titleLine"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
BIN
header.jpg
Normal file
BIN
header.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 921 KiB |
BIN
icons/Bindle_Red.png
Normal file
BIN
icons/Bindle_Red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
icons/Humble_H-Red.png
Normal file
BIN
icons/Humble_H-Red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 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,14 +20,17 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from kcc import modify_path
|
||||||
|
|
||||||
if sys.version_info < (3, 8, 0):
|
if sys.version_info < (3, 8, 0):
|
||||||
print('ERROR: This is a Python 3.8+ script!')
|
print('ERROR: This is a Python 3.8+ script!')
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
from multiprocessing import freeze_support, set_start_method
|
from multiprocessing import freeze_support, set_start_method
|
||||||
from kindlecomicconverter.startup import startC2E
|
from kindlecomicconverter.startup import startC2E
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
modify_path()
|
||||||
set_start_method('spawn')
|
set_start_method('spawn')
|
||||||
freeze_support()
|
freeze_support()
|
||||||
startC2E()
|
startC2E()
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ a = Analysis(['kcc-c2e.py'],
|
|||||||
pathex=['.'],
|
pathex=['.'],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[],
|
datas=[],
|
||||||
hiddenimports=[],
|
hiddenimports=['_cffi_backend'],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
excludes=[],
|
excludes=['pkg_resources'],
|
||||||
win_no_prefer_redirects=False,
|
win_no_prefer_redirects=False,
|
||||||
win_private_assemblies=False,
|
win_private_assemblies=False,
|
||||||
cipher=block_cipher,
|
cipher=block_cipher,
|
||||||
|
|||||||
@@ -20,14 +20,17 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from kcc import modify_path
|
||||||
|
|
||||||
if sys.version_info < (3, 8, 0):
|
if sys.version_info < (3, 8, 0):
|
||||||
print('ERROR: This is a Python 3.8+ script!')
|
print('ERROR: This is a Python 3.8+ script!')
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
from multiprocessing import freeze_support, set_start_method
|
from multiprocessing import freeze_support, set_start_method
|
||||||
from kindlecomicconverter.startup import startC2P
|
from kindlecomicconverter.startup import startC2P
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
modify_path()
|
||||||
set_start_method('spawn')
|
set_start_method('spawn')
|
||||||
freeze_support()
|
freeze_support()
|
||||||
startC2P()
|
startC2P()
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ a = Analysis(['kcc-c2p.py'],
|
|||||||
pathex=['.'],
|
pathex=['.'],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[],
|
datas=[],
|
||||||
hiddenimports=[],
|
hiddenimports=['_cffi_backend'],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
excludes=[],
|
excludes=['pkg_resources'],
|
||||||
win_no_prefer_redirects=False,
|
win_no_prefer_redirects=False,
|
||||||
win_private_assemblies=False,
|
win_private_assemblies=False,
|
||||||
cipher=block_cipher,
|
cipher=block_cipher,
|
||||||
|
|||||||
123
kcc.iss
123
kcc.iss
@@ -1,123 +0,0 @@
|
|||||||
#define MyAppName "Kindle Comic Converter"
|
|
||||||
#define MyAppVersion "5.5.2"
|
|
||||||
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
|
|
||||||
#define MyAppURL "http://kcc.iosphe.re/"
|
|
||||||
#define MyAppExeName "KCC.exe"
|
|
||||||
|
|
||||||
[Setup]
|
|
||||||
AppId={{7D279A59-C65E-4DA7-B165-56DD06596216}
|
|
||||||
AppName={#MyAppName}
|
|
||||||
AppVersion={#MyAppVersion}
|
|
||||||
AppPublisher={#MyAppPublisher}
|
|
||||||
AppPublisherURL={#MyAppURL}
|
|
||||||
AppSupportURL={#MyAppURL}
|
|
||||||
AppUpdatesURL={#MyAppURL}
|
|
||||||
AppCopyright=Copyright (C) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski
|
|
||||||
ArchitecturesAllowed=x64
|
|
||||||
DefaultDirName={pf}\{#MyAppName}
|
|
||||||
DefaultGroupName={#MyAppName}
|
|
||||||
AllowNoIcons=yes
|
|
||||||
LicenseFile=LICENSE.txt
|
|
||||||
OutputBaseFilename=KindleComicConverter_win_{#MyAppVersion}
|
|
||||||
SetupIconFile=icons\comic2ebook.ico
|
|
||||||
SolidCompression=yes
|
|
||||||
ShowLanguageDialog=no
|
|
||||||
LanguageDetectionMethod=none
|
|
||||||
WizardImageFile=icons\Wizard.bmp
|
|
||||||
WizardSmallImageFile=icons\Wizard-Small.bmp
|
|
||||||
UninstallDisplayName={#MyAppName}
|
|
||||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
|
||||||
ChangesAssociations=True
|
|
||||||
InfoAfterFile=other\windows\InstallWarning.rtf
|
|
||||||
SignTool=SignTool /d $q{#MyAppName}$q /du $q{#MyAppURL}$q $f
|
|
||||||
MinVersion=0,6.0
|
|
||||||
OutputDir=dist
|
|
||||||
ArchitecturesInstallIn64BitMode=x64
|
|
||||||
|
|
||||||
[Languages]
|
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
||||||
|
|
||||||
[Tasks]
|
|
||||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
|
||||||
Name: "CBZassociation"; Description: "CBZ"; GroupDescription: "File associations:"
|
|
||||||
Name: "CBRassociation"; Description: "CBR"; GroupDescription: "File associations:"
|
|
||||||
Name: "CB7association"; Description: "CB7"; GroupDescription: "File associations:"
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak
|
|
||||||
Source: "other\windows\Additional-LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
Source: "other\windows\7z.exe"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
Source: "other\windows\7z.dll"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
|
|
||||||
[Icons]
|
|
||||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
|
||||||
Name: "{group}\Readme"; Filename: "https://github.com/ciromattia/kcc#kcc"
|
|
||||||
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
|
||||||
|
|
||||||
[Run]
|
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall
|
|
||||||
|
|
||||||
[Messages]
|
|
||||||
WelcomeLabel1=Welcome to the KCC Setup Wizard
|
|
||||||
FinishedHeadingLabel=Completing the KCC Setup Wizard
|
|
||||||
|
|
||||||
[Registry]
|
|
||||||
Root: HKCR; SubKey: ".cbz"; ValueType: string; ValueData: "KCCZIP"; Flags: uninsdeletekey; Tasks: CBZassociation
|
|
||||||
Root: HKCR; SubKey: "KCCZIP"; ValueType: string; ValueData: "KCC ZIP Archive"; Flags: uninsdeletekey; Tasks: CBZassociation
|
|
||||||
Root: HKCR; SubKey: "KCCZIP\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CBZassociation
|
|
||||||
Root: HKCR; Subkey: "KCCZIP\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CBZassociation
|
|
||||||
Root: HKCR; SubKey: ".cbr"; ValueType: string; ValueData: "KCCRAR"; Flags: uninsdeletekey; Tasks: CBRassociation
|
|
||||||
Root: HKCR; SubKey: "KCCRAR"; ValueType: string; ValueData: "KCC RAR Archive"; Flags: uninsdeletekey; Tasks: CBRassociation
|
|
||||||
Root: HKCR; SubKey: "KCCRAR\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CBRassociation
|
|
||||||
Root: HKCR; Subkey: "KCCRAR\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CBRassociation
|
|
||||||
Root: HKCR; SubKey: ".cb7"; ValueType: string; ValueData: "KCCCB7"; Flags: uninsdeletekey; Tasks: CB7association
|
|
||||||
Root: HKCR; SubKey: "KCCCB7"; ValueType: string; ValueData: "KCC 7z Archive"; Flags: uninsdeletekey; Tasks: CB7association
|
|
||||||
Root: HKCR; SubKey: "KCCCB7\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CB7association
|
|
||||||
Root: HKCR; Subkey: "KCCCB7\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CB7association
|
|
||||||
|
|
||||||
[Code]
|
|
||||||
function GetUninstallString(): String;
|
|
||||||
var
|
|
||||||
sUnInstPath: String;
|
|
||||||
sUnInstallString: String;
|
|
||||||
begin
|
|
||||||
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
|
|
||||||
sUnInstallString := '';
|
|
||||||
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
|
|
||||||
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
|
|
||||||
Result := sUnInstallString;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function IsUpgrade(): Boolean;
|
|
||||||
begin
|
|
||||||
Result := (GetUninstallString() <> '');
|
|
||||||
end;
|
|
||||||
|
|
||||||
function UnInstallOldVersion(): Integer;
|
|
||||||
var
|
|
||||||
sUnInstallString: String;
|
|
||||||
iResultCode: Integer;
|
|
||||||
begin
|
|
||||||
Result := 0;
|
|
||||||
sUnInstallString := GetUninstallString();
|
|
||||||
if sUnInstallString <> '' then begin
|
|
||||||
sUnInstallString := RemoveQuotes(sUnInstallString);
|
|
||||||
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
|
|
||||||
Result := 3
|
|
||||||
else
|
|
||||||
Result := 2;
|
|
||||||
end else
|
|
||||||
Result := 1;
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure CurStepChanged(CurStep: TSetupStep);
|
|
||||||
begin
|
|
||||||
if (CurStep=ssInstall) then
|
|
||||||
begin
|
|
||||||
if (IsUpgrade()) then
|
|
||||||
begin
|
|
||||||
UnInstallOldVersion();
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
105
kcc.py
105
kcc.py
@@ -18,61 +18,76 @@
|
|||||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
if sys.version_info < (3, 8, 0):
|
if sys.version_info < (3, 8, 0):
|
||||||
print('ERROR: This is a Python 3.8+ script!')
|
print('ERROR: This is a Python 3.8+ script!')
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
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__)))
|
||||||
|
|
||||||
# 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:\\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.path.dirname(os.path.abspath(__file__)) + '/other/windows/',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
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
|
|
||||||
|
|
||||||
from multiprocessing import freeze_support, set_start_method
|
from multiprocessing import freeze_support, set_start_method
|
||||||
from kindlecomicconverter.startup import start
|
from kindlecomicconverter.startup import start
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
modify_path()
|
||||||
set_start_method('spawn')
|
set_start_method('spawn')
|
||||||
freeze_support()
|
freeze_support()
|
||||||
start()
|
start()
|
||||||
|
|||||||
4
kcc.spec
4
kcc.spec
@@ -8,10 +8,10 @@ a = Analysis(['kcc.py'],
|
|||||||
pathex=['.'],
|
pathex=['.'],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[],
|
datas=[],
|
||||||
hiddenimports=[],
|
hiddenimports=['_cffi_backend'],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
excludes=[],
|
excludes=['pkg_resources'],
|
||||||
win_no_prefer_redirects=False,
|
win_no_prefer_redirects=False,
|
||||||
win_private_assemblies=False,
|
win_private_assemblies=False,
|
||||||
cipher=block_cipher,
|
cipher=block_cipher,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,321 +1,671 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'gui/KCC.ui'
|
################################################################################
|
||||||
#
|
## Form generated from reading UI file 'KCC.ui'
|
||||||
# Created by: PyQt5 UI code generator 5.15.7
|
##
|
||||||
#
|
## Created by: Qt User Interface Compiler version 6.9.3
|
||||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
##
|
||||||
# run again. Do not edit this file unless you know what you are doing.
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
||||||
|
|
||||||
|
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||||
|
QMetaObject, QObject, QPoint, QRect,
|
||||||
|
QSize, QTime, QUrl, Qt)
|
||||||
|
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||||
|
QFont, QFontDatabase, QGradient, QIcon,
|
||||||
|
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||||
|
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||||
|
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QComboBox,
|
||||||
|
QGridLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||||
|
QListWidget, QListWidgetItem, QMainWindow, QProgressBar,
|
||||||
|
QPushButton, QSizePolicy, QSlider, QSpinBox,
|
||||||
|
QStatusBar, QWidget)
|
||||||
|
from . import KCC_rc
|
||||||
|
|
||||||
class Ui_mainWindow(object):
|
class Ui_mainWindow(object):
|
||||||
def setupUi(self, mainWindow):
|
def setupUi(self, mainWindow):
|
||||||
mainWindow.setObjectName("mainWindow")
|
if not mainWindow.objectName():
|
||||||
mainWindow.resize(450, 400)
|
mainWindow.setObjectName(u"mainWindow")
|
||||||
icon = QtGui.QIcon()
|
mainWindow.resize(566, 573)
|
||||||
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
icon = QIcon()
|
||||||
|
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
mainWindow.setWindowIcon(icon)
|
mainWindow.setWindowIcon(icon)
|
||||||
self.centralWidget = QtWidgets.QWidget(mainWindow)
|
self.centralWidget = QWidget(mainWindow)
|
||||||
self.centralWidget.setObjectName("centralWidget")
|
self.centralWidget.setObjectName(u"centralWidget")
|
||||||
self.gridLayout = QtWidgets.QGridLayout(self.centralWidget)
|
self.gridLayout = QGridLayout(self.centralWidget)
|
||||||
|
self.gridLayout.setObjectName(u"gridLayout")
|
||||||
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
|
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
|
||||||
self.gridLayout.setObjectName("gridLayout")
|
self.optionWidget = QWidget(self.centralWidget)
|
||||||
self.optionWidget = QtWidgets.QWidget(self.centralWidget)
|
self.optionWidget.setObjectName(u"optionWidget")
|
||||||
self.optionWidget.setObjectName("optionWidget")
|
self.gridLayout_2 = QGridLayout(self.optionWidget)
|
||||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.optionWidget)
|
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
self.titleEdit = QLineEdit(self.optionWidget)
|
||||||
self.upscaleBox = QtWidgets.QCheckBox(self.optionWidget)
|
self.titleEdit.setObjectName(u"titleEdit")
|
||||||
self.upscaleBox.setTristate(True)
|
sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||||
self.upscaleBox.setObjectName("upscaleBox")
|
|
||||||
self.gridLayout_2.addWidget(self.upscaleBox, 1, 1, 1, 1)
|
|
||||||
self.rotateBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.rotateBox.setTristate(True)
|
|
||||||
self.rotateBox.setObjectName("rotateBox")
|
|
||||||
self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1)
|
|
||||||
self.outputSplit = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.outputSplit.setObjectName("outputSplit")
|
|
||||||
self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
|
|
||||||
self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.webtoonBox.setObjectName("webtoonBox")
|
|
||||||
self.gridLayout_2.addWidget(self.webtoonBox, 1, 0, 1, 1)
|
|
||||||
self.colorBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.colorBox.setObjectName("colorBox")
|
|
||||||
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
|
|
||||||
self.gammaBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.gammaBox.setObjectName("gammaBox")
|
|
||||||
self.gridLayout_2.addWidget(self.gammaBox, 1, 2, 1, 1)
|
|
||||||
self.borderBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.borderBox.setTristate(True)
|
|
||||||
self.borderBox.setObjectName("borderBox")
|
|
||||||
self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1)
|
|
||||||
self.mangaBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.mangaBox.setObjectName("mangaBox")
|
|
||||||
self.gridLayout_2.addWidget(self.mangaBox, 0, 0, 1, 1)
|
|
||||||
self.qualityBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.qualityBox.setTristate(True)
|
|
||||||
self.qualityBox.setObjectName("qualityBox")
|
|
||||||
self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1)
|
|
||||||
self.mozJpegBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.mozJpegBox.setTristate(True)
|
|
||||||
self.mozJpegBox.setObjectName("mozJpegBox")
|
|
||||||
self.gridLayout_2.addWidget(self.mozJpegBox, 3, 0, 1, 1)
|
|
||||||
self.maximizeStrips = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.maximizeStrips.setObjectName("maximizeStrips")
|
|
||||||
self.gridLayout_2.addWidget(self.maximizeStrips, 3, 1, 1, 1)
|
|
||||||
self.croppingBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.croppingBox.setTristate(True)
|
|
||||||
self.croppingBox.setObjectName("croppingBox")
|
|
||||||
self.gridLayout_2.addWidget(self.croppingBox, 3, 2, 1, 1)
|
|
||||||
self.deleteBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.deleteBox.setObjectName("deleteBox")
|
|
||||||
self.gridLayout_2.addWidget(self.deleteBox, 4, 1, 1, 1)
|
|
||||||
self.disableProcessingBox = QtWidgets.QCheckBox(self.optionWidget)
|
|
||||||
self.disableProcessingBox.setObjectName("disableProcessingBox")
|
|
||||||
self.gridLayout_2.addWidget(self.disableProcessingBox, 4, 2, 1, 1)
|
|
||||||
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
|
||||||
self.gammaWidget = QtWidgets.QWidget(self.centralWidget)
|
|
||||||
self.gammaWidget.setVisible(False)
|
|
||||||
self.gammaWidget.setObjectName("gammaWidget")
|
|
||||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.gammaWidget)
|
|
||||||
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
|
||||||
self.gammaLabel = QtWidgets.QLabel(self.gammaWidget)
|
|
||||||
self.gammaLabel.setObjectName("gammaLabel")
|
|
||||||
self.horizontalLayout_2.addWidget(self.gammaLabel)
|
|
||||||
self.gammaSlider = QtWidgets.QSlider(self.gammaWidget)
|
|
||||||
self.gammaSlider.setMaximum(250)
|
|
||||||
self.gammaSlider.setSingleStep(5)
|
|
||||||
self.gammaSlider.setOrientation(QtCore.Qt.Horizontal)
|
|
||||||
self.gammaSlider.setObjectName("gammaSlider")
|
|
||||||
self.horizontalLayout_2.addWidget(self.gammaSlider)
|
|
||||||
self.gridLayout.addWidget(self.gammaWidget, 6, 0, 1, 2)
|
|
||||||
self.croppingWidget = QtWidgets.QWidget(self.centralWidget)
|
|
||||||
self.croppingWidget.setVisible(False)
|
|
||||||
self.croppingWidget.setObjectName("croppingWidget")
|
|
||||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.croppingWidget)
|
|
||||||
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
|
||||||
self.croppingPowerLabel = QtWidgets.QLabel(self.croppingWidget)
|
|
||||||
self.croppingPowerLabel.setObjectName("croppingPowerLabel")
|
|
||||||
self.horizontalLayout_3.addWidget(self.croppingPowerLabel)
|
|
||||||
self.croppingPowerSlider = QtWidgets.QSlider(self.croppingWidget)
|
|
||||||
self.croppingPowerSlider.setMaximum(200)
|
|
||||||
self.croppingPowerSlider.setSingleStep(1)
|
|
||||||
self.croppingPowerSlider.setOrientation(QtCore.Qt.Horizontal)
|
|
||||||
self.croppingPowerSlider.setObjectName("croppingPowerSlider")
|
|
||||||
self.horizontalLayout_3.addWidget(self.croppingPowerSlider)
|
|
||||||
self.gridLayout.addWidget(self.croppingWidget, 8, 0, 1, 2)
|
|
||||||
self.buttonWidget = QtWidgets.QWidget(self.centralWidget)
|
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
|
||||||
sizePolicy.setHorizontalStretch(0)
|
sizePolicy.setHorizontalStretch(0)
|
||||||
sizePolicy.setVerticalStretch(0)
|
sizePolicy.setVerticalStretch(0)
|
||||||
sizePolicy.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
|
sizePolicy.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth())
|
||||||
self.buttonWidget.setSizePolicy(sizePolicy)
|
self.titleEdit.setSizePolicy(sizePolicy)
|
||||||
self.buttonWidget.setObjectName("buttonWidget")
|
self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
|
||||||
self.gridLayout_4 = QtWidgets.QGridLayout(self.buttonWidget)
|
self.titleEdit.setClearButtonEnabled(False)
|
||||||
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.gridLayout_4.setObjectName("gridLayout_4")
|
self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1)
|
||||||
self.directoryButton = QtWidgets.QPushButton(self.buttonWidget)
|
|
||||||
self.directoryButton.setMinimumSize(QtCore.QSize(0, 30))
|
self.mangaBox = QCheckBox(self.optionWidget)
|
||||||
icon1 = QtGui.QIcon()
|
self.mangaBox.setObjectName(u"mangaBox")
|
||||||
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/folder_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
||||||
self.directoryButton.setIcon(icon1)
|
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
||||||
self.directoryButton.setObjectName("directoryButton")
|
|
||||||
self.gridLayout_4.addWidget(self.directoryButton, 0, 0, 1, 1)
|
self.webtoonBox = QCheckBox(self.optionWidget)
|
||||||
self.fileButton = QtWidgets.QPushButton(self.buttonWidget)
|
self.webtoonBox.setObjectName(u"webtoonBox")
|
||||||
self.fileButton.setMinimumSize(QtCore.QSize(0, 30))
|
|
||||||
icon2 = QtGui.QIcon()
|
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
||||||
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/document_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
||||||
self.fileButton.setIcon(icon2)
|
self.croppingBox = QCheckBox(self.optionWidget)
|
||||||
self.fileButton.setObjectName("fileButton")
|
self.croppingBox.setObjectName(u"croppingBox")
|
||||||
self.gridLayout_4.addWidget(self.fileButton, 0, 3, 1, 1)
|
self.croppingBox.setTristate(True)
|
||||||
self.deviceBox = QtWidgets.QComboBox(self.buttonWidget)
|
|
||||||
self.deviceBox.setMinimumSize(QtCore.QSize(0, 28))
|
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
||||||
self.deviceBox.setObjectName("deviceBox")
|
|
||||||
self.gridLayout_4.addWidget(self.deviceBox, 1, 0, 1, 1)
|
self.maximizeStrips = QCheckBox(self.optionWidget)
|
||||||
self.formatBox = QtWidgets.QComboBox(self.buttonWidget)
|
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||||
self.formatBox.setMinimumSize(QtCore.QSize(0, 28))
|
|
||||||
self.formatBox.setObjectName("formatBox")
|
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
||||||
self.gridLayout_4.addWidget(self.formatBox, 1, 3, 1, 1)
|
|
||||||
self.convertButton = QtWidgets.QPushButton(self.buttonWidget)
|
self.deleteBox = QCheckBox(self.optionWidget)
|
||||||
self.convertButton.setMinimumSize(QtCore.QSize(0, 30))
|
self.deleteBox.setObjectName(u"deleteBox")
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setBold(True)
|
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
||||||
self.convertButton.setFont(font)
|
|
||||||
icon3 = QtGui.QIcon()
|
self.rotateFirstBox = QCheckBox(self.optionWidget)
|
||||||
icon3.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
self.rotateFirstBox.setObjectName(u"rotateFirstBox")
|
||||||
self.convertButton.setIcon(icon3)
|
|
||||||
self.convertButton.setObjectName("convertButton")
|
self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1)
|
||||||
self.gridLayout_4.addWidget(self.convertButton, 1, 2, 1, 1)
|
|
||||||
self.clearButton = QtWidgets.QPushButton(self.buttonWidget)
|
self.outputSplit = QCheckBox(self.optionWidget)
|
||||||
self.clearButton.setMinimumSize(QtCore.QSize(0, 30))
|
self.outputSplit.setObjectName(u"outputSplit")
|
||||||
icon4 = QtGui.QIcon()
|
|
||||||
icon4.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
||||||
self.clearButton.setIcon(icon4)
|
|
||||||
self.clearButton.setObjectName("clearButton")
|
self.borderBox = QCheckBox(self.optionWidget)
|
||||||
self.gridLayout_4.addWidget(self.clearButton, 0, 2, 1, 1)
|
self.borderBox.setObjectName(u"borderBox")
|
||||||
self.directoryButton.raise_()
|
self.borderBox.setTristate(True)
|
||||||
self.clearButton.raise_()
|
|
||||||
self.fileButton.raise_()
|
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
||||||
self.deviceBox.raise_()
|
|
||||||
self.convertButton.raise_()
|
self.gammaBox = QCheckBox(self.optionWidget)
|
||||||
self.formatBox.raise_()
|
self.gammaBox.setObjectName(u"gammaBox")
|
||||||
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
|
|
||||||
self.toolWidget = QtWidgets.QWidget(self.centralWidget)
|
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
||||||
self.toolWidget.setObjectName("toolWidget")
|
|
||||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.toolWidget)
|
self.upscaleBox = QCheckBox(self.optionWidget)
|
||||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
self.upscaleBox.setObjectName(u"upscaleBox")
|
||||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
self.upscaleBox.setTristate(True)
|
||||||
self.editorButton = QtWidgets.QPushButton(self.toolWidget)
|
|
||||||
self.editorButton.setMinimumSize(QtCore.QSize(0, 30))
|
self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
|
||||||
icon5 = QtGui.QIcon()
|
|
||||||
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
|
||||||
self.editorButton.setIcon(icon5)
|
self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
|
||||||
self.editorButton.setObjectName("editorButton")
|
|
||||||
self.horizontalLayout.addWidget(self.editorButton)
|
self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
|
||||||
self.wikiButton = QtWidgets.QPushButton(self.toolWidget)
|
|
||||||
self.wikiButton.setMinimumSize(QtCore.QSize(0, 30))
|
self.colorBox = QCheckBox(self.optionWidget)
|
||||||
icon6 = QtGui.QIcon()
|
self.colorBox.setObjectName(u"colorBox")
|
||||||
icon6.addPixmap(QtGui.QPixmap(":/Other/icons/wiki.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
||||||
self.wikiButton.setIcon(icon6)
|
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
|
||||||
self.wikiButton.setObjectName("wikiButton")
|
|
||||||
self.horizontalLayout.addWidget(self.wikiButton)
|
self.rotateBox = QCheckBox(self.optionWidget)
|
||||||
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
|
self.rotateBox.setObjectName(u"rotateBox")
|
||||||
self.jobList = QtWidgets.QListWidget(self.centralWidget)
|
self.rotateBox.setTristate(True)
|
||||||
self.jobList.setStyleSheet("QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}")
|
|
||||||
self.jobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
||||||
self.jobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
|
||||||
self.jobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
||||||
self.jobList.setObjectName("jobList")
|
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.eraseRainbowBox = QCheckBox(self.optionWidget)
|
||||||
|
self.eraseRainbowBox.setObjectName(u"eraseRainbowBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1)
|
||||||
|
|
||||||
|
self.noRotateBox = QCheckBox(self.optionWidget)
|
||||||
|
self.noRotateBox.setObjectName(u"noRotateBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
||||||
|
|
||||||
|
self.fileFusionBox = QCheckBox(self.optionWidget)
|
||||||
|
self.fileFusionBox.setObjectName(u"fileFusionBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
|
||||||
|
|
||||||
|
self.authorEdit = QLineEdit(self.optionWidget)
|
||||||
|
self.authorEdit.setObjectName(u"authorEdit")
|
||||||
|
sizePolicy.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth())
|
||||||
|
self.authorEdit.setSizePolicy(sizePolicy)
|
||||||
|
self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
|
||||||
|
self.authorEdit.setClearButtonEnabled(False)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 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.interPanelCropBox = QCheckBox(self.optionWidget)
|
||||||
|
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
|
||||||
|
self.interPanelCropBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
|
||||||
|
|
||||||
|
self.metadataTitleBox = QCheckBox(self.optionWidget)
|
||||||
|
self.metadataTitleBox.setObjectName(u"metadataTitleBox")
|
||||||
|
self.metadataTitleBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1)
|
||||||
|
|
||||||
|
self.mozJpegBox = QCheckBox(self.optionWidget)
|
||||||
|
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
||||||
|
self.mozJpegBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
||||||
|
|
||||||
|
self.autoLevelBox = QCheckBox(self.optionWidget)
|
||||||
|
self.autoLevelBox.setObjectName(u"autoLevelBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1)
|
||||||
|
|
||||||
|
self.autocontrastBox = QCheckBox(self.optionWidget)
|
||||||
|
self.autocontrastBox.setObjectName(u"autocontrastBox")
|
||||||
|
self.autocontrastBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
||||||
|
|
||||||
|
self.jobList = QListWidget(self.centralWidget)
|
||||||
|
self.jobList.setObjectName(u"jobList")
|
||||||
|
self.jobList.setMinimumSize(QSize(0, 150))
|
||||||
|
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.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
|
||||||
self.progressBar = QtWidgets.QProgressBar(self.centralWidget)
|
|
||||||
self.progressBar.setMinimumSize(QtCore.QSize(0, 30))
|
self.customWidget = QWidget(self.centralWidget)
|
||||||
font = QtGui.QFont()
|
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.Policy.Fixed, QSizePolicy.Policy.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(3200)
|
||||||
|
|
||||||
|
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(5120)
|
||||||
|
|
||||||
|
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2)
|
||||||
|
|
||||||
|
self.gammaWidget = QWidget(self.centralWidget)
|
||||||
|
self.gammaWidget.setObjectName(u"gammaWidget")
|
||||||
|
self.gammaWidget.setVisible(False)
|
||||||
|
self.horizontalLayout_2 = QHBoxLayout(self.gammaWidget)
|
||||||
|
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||||
|
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gammaLabel = QLabel(self.gammaWidget)
|
||||||
|
self.gammaLabel.setObjectName(u"gammaLabel")
|
||||||
|
|
||||||
|
self.horizontalLayout_2.addWidget(self.gammaLabel)
|
||||||
|
|
||||||
|
self.gammaSlider = QSlider(self.gammaWidget)
|
||||||
|
self.gammaSlider.setObjectName(u"gammaSlider")
|
||||||
|
self.gammaSlider.setMaximum(250)
|
||||||
|
self.gammaSlider.setSingleStep(5)
|
||||||
|
self.gammaSlider.setOrientation(Qt.Orientation.Horizontal)
|
||||||
|
|
||||||
|
self.horizontalLayout_2.addWidget(self.gammaSlider)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.gammaWidget, 7, 0, 1, 2)
|
||||||
|
|
||||||
|
self.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":/Brand/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.progressBar = QProgressBar(self.centralWidget)
|
||||||
|
self.progressBar.setObjectName(u"progressBar")
|
||||||
|
self.progressBar.setMinimumSize(QSize(0, 30))
|
||||||
|
font = QFont()
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.progressBar.setFont(font)
|
self.progressBar.setFont(font)
|
||||||
self.progressBar.setVisible(False)
|
self.progressBar.setVisible(False)
|
||||||
self.progressBar.setAlignment(QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter)
|
self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter)
|
||||||
self.progressBar.setObjectName("progressBar")
|
|
||||||
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
|
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
|
||||||
self.customWidget = QtWidgets.QWidget(self.centralWidget)
|
|
||||||
self.customWidget.setVisible(False)
|
self.croppingWidget = QWidget(self.centralWidget)
|
||||||
self.customWidget.setObjectName("customWidget")
|
self.croppingWidget.setObjectName(u"croppingWidget")
|
||||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.customWidget)
|
self.croppingWidget.setVisible(False)
|
||||||
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
|
self.gridLayout_5 = QGridLayout(self.croppingWidget)
|
||||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
self.gridLayout_5.setObjectName(u"gridLayout_5")
|
||||||
self.hLabel = QtWidgets.QLabel(self.customWidget)
|
self.gridLayout_5.setContentsMargins(0, 0, 0, 0)
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
self.preserveMarginLabel = QLabel(self.croppingWidget)
|
||||||
sizePolicy.setHorizontalStretch(0)
|
self.preserveMarginLabel.setObjectName(u"preserveMarginLabel")
|
||||||
sizePolicy.setVerticalStretch(0)
|
|
||||||
sizePolicy.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
|
self.gridLayout_5.addWidget(self.preserveMarginLabel, 1, 0, 1, 1)
|
||||||
self.hLabel.setSizePolicy(sizePolicy)
|
|
||||||
self.hLabel.setObjectName("hLabel")
|
self.croppingPowerLabel = QLabel(self.croppingWidget)
|
||||||
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
|
self.croppingPowerLabel.setObjectName(u"croppingPowerLabel")
|
||||||
self.widthBox = QtWidgets.QSpinBox(self.customWidget)
|
|
||||||
self.widthBox.setMaximum(2160)
|
self.gridLayout_5.addWidget(self.croppingPowerLabel, 0, 0, 1, 1)
|
||||||
self.widthBox.setObjectName("widthBox")
|
|
||||||
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
self.croppingPowerSlider = QSlider(self.croppingWidget)
|
||||||
self.wLabel = QtWidgets.QLabel(self.customWidget)
|
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
self.croppingPowerSlider.setMaximum(300)
|
||||||
sizePolicy.setHorizontalStretch(0)
|
self.croppingPowerSlider.setSingleStep(1)
|
||||||
sizePolicy.setVerticalStretch(0)
|
self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal)
|
||||||
sizePolicy.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
|
|
||||||
self.wLabel.setSizePolicy(sizePolicy)
|
self.gridLayout_5.addWidget(self.croppingPowerSlider, 0, 1, 1, 1)
|
||||||
self.wLabel.setObjectName("wLabel")
|
|
||||||
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
|
self.preserveMarginBox = QSpinBox(self.croppingWidget)
|
||||||
self.heightBox = QtWidgets.QSpinBox(self.customWidget)
|
self.preserveMarginBox.setObjectName(u"preserveMarginBox")
|
||||||
self.heightBox.setMaximum(3840)
|
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||||
self.heightBox.setObjectName("heightBox")
|
sizePolicy2.setHorizontalStretch(0)
|
||||||
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
sizePolicy2.setVerticalStretch(0)
|
||||||
self.gridLayout.addWidget(self.customWidget, 7, 0, 1, 2)
|
sizePolicy2.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth())
|
||||||
|
self.preserveMarginBox.setSizePolicy(sizePolicy2)
|
||||||
|
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.buttonWidget = QWidget(self.centralWidget)
|
||||||
|
self.buttonWidget.setObjectName(u"buttonWidget")
|
||||||
|
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
||||||
|
sizePolicy3.setHorizontalStretch(0)
|
||||||
|
sizePolicy3.setVerticalStretch(0)
|
||||||
|
sizePolicy3.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
|
||||||
|
self.buttonWidget.setSizePolicy(sizePolicy3)
|
||||||
|
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))
|
||||||
|
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")
|
||||||
|
sizePolicy2.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
|
||||||
|
self.defaultOutputFolderBox.setSizePolicy(sizePolicy2)
|
||||||
|
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.chunkSizeWidget = QWidget(self.centralWidget)
|
||||||
|
self.chunkSizeWidget.setObjectName(u"chunkSizeWidget")
|
||||||
|
sizePolicy.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth())
|
||||||
|
self.chunkSizeWidget.setSizePolicy(sizePolicy)
|
||||||
|
self.chunkSizeWidget.setVisible(False)
|
||||||
|
self.horizontalLayout_4 = QHBoxLayout(self.chunkSizeWidget)
|
||||||
|
self.horizontalLayout_4.setSpacing(0)
|
||||||
|
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
|
||||||
|
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.chunkSizeLabel = QLabel(self.chunkSizeWidget)
|
||||||
|
self.chunkSizeLabel.setObjectName(u"chunkSizeLabel")
|
||||||
|
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
||||||
|
sizePolicy4.setHorizontalStretch(0)
|
||||||
|
sizePolicy4.setVerticalStretch(0)
|
||||||
|
sizePolicy4.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
|
||||||
|
self.chunkSizeLabel.setSizePolicy(sizePolicy4)
|
||||||
|
|
||||||
|
self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
|
||||||
|
|
||||||
|
self.chunkSizeBox = QSpinBox(self.chunkSizeWidget)
|
||||||
|
self.chunkSizeBox.setObjectName(u"chunkSizeBox")
|
||||||
|
self.chunkSizeBox.setMinimum(100)
|
||||||
|
self.chunkSizeBox.setMaximum(600)
|
||||||
|
self.chunkSizeBox.setValue(400)
|
||||||
|
|
||||||
|
self.horizontalLayout_4.addWidget(self.chunkSizeBox)
|
||||||
|
|
||||||
|
self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget)
|
||||||
|
self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel")
|
||||||
|
sizePolicy4.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
|
||||||
|
self.chunkSizeWarnLabel.setSizePolicy(sizePolicy4)
|
||||||
|
|
||||||
|
self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1)
|
||||||
|
|
||||||
mainWindow.setCentralWidget(self.centralWidget)
|
mainWindow.setCentralWidget(self.centralWidget)
|
||||||
self.statusBar = QtWidgets.QStatusBar(mainWindow)
|
self.statusBar = QStatusBar(mainWindow)
|
||||||
|
self.statusBar.setObjectName(u"statusBar")
|
||||||
self.statusBar.setSizeGripEnabled(False)
|
self.statusBar.setSizeGripEnabled(False)
|
||||||
self.statusBar.setObjectName("statusBar")
|
|
||||||
mainWindow.setStatusBar(self.statusBar)
|
mainWindow.setStatusBar(self.statusBar)
|
||||||
|
QWidget.setTabOrder(self.convertButton, self.clearButton)
|
||||||
|
QWidget.setTabOrder(self.clearButton, self.deviceBox)
|
||||||
|
QWidget.setTabOrder(self.deviceBox, self.formatBox)
|
||||||
|
QWidget.setTabOrder(self.formatBox, self.mangaBox)
|
||||||
|
QWidget.setTabOrder(self.mangaBox, self.rotateBox)
|
||||||
|
QWidget.setTabOrder(self.rotateBox, self.qualityBox)
|
||||||
|
QWidget.setTabOrder(self.qualityBox, self.webtoonBox)
|
||||||
|
QWidget.setTabOrder(self.webtoonBox, self.upscaleBox)
|
||||||
|
QWidget.setTabOrder(self.upscaleBox, self.gammaBox)
|
||||||
|
QWidget.setTabOrder(self.gammaBox, self.borderBox)
|
||||||
|
QWidget.setTabOrder(self.borderBox, self.outputSplit)
|
||||||
|
QWidget.setTabOrder(self.outputSplit, self.colorBox)
|
||||||
|
QWidget.setTabOrder(self.colorBox, self.mozJpegBox)
|
||||||
|
QWidget.setTabOrder(self.mozJpegBox, self.maximizeStrips)
|
||||||
|
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.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)
|
||||||
|
|
||||||
self.retranslateUi(mainWindow)
|
self.retranslateUi(mainWindow)
|
||||||
QtCore.QMetaObject.connectSlotsByName(mainWindow)
|
|
||||||
mainWindow.setTabOrder(self.convertButton, self.clearButton)
|
QMetaObject.connectSlotsByName(mainWindow)
|
||||||
mainWindow.setTabOrder(self.clearButton, self.directoryButton)
|
# setupUi
|
||||||
mainWindow.setTabOrder(self.directoryButton, self.fileButton)
|
|
||||||
mainWindow.setTabOrder(self.fileButton, self.deviceBox)
|
|
||||||
mainWindow.setTabOrder(self.deviceBox, self.formatBox)
|
|
||||||
mainWindow.setTabOrder(self.formatBox, self.mangaBox)
|
|
||||||
mainWindow.setTabOrder(self.mangaBox, self.rotateBox)
|
|
||||||
mainWindow.setTabOrder(self.rotateBox, self.qualityBox)
|
|
||||||
mainWindow.setTabOrder(self.qualityBox, self.webtoonBox)
|
|
||||||
mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox)
|
|
||||||
mainWindow.setTabOrder(self.upscaleBox, self.gammaBox)
|
|
||||||
mainWindow.setTabOrder(self.gammaBox, self.borderBox)
|
|
||||||
mainWindow.setTabOrder(self.borderBox, self.outputSplit)
|
|
||||||
mainWindow.setTabOrder(self.outputSplit, self.colorBox)
|
|
||||||
mainWindow.setTabOrder(self.colorBox, self.croppingBox)
|
|
||||||
mainWindow.setTabOrder(self.croppingBox, self.mozJpegBox)
|
|
||||||
mainWindow.setTabOrder(self.mozJpegBox, self.maximizeStrips)
|
|
||||||
mainWindow.setTabOrder(self.maximizeStrips, self.deleteBox)
|
|
||||||
mainWindow.setTabOrder(self.deleteBox, self.disableProcessingBox)
|
|
||||||
mainWindow.setTabOrder(self.disableProcessingBox, self.editorButton)
|
|
||||||
mainWindow.setTabOrder(self.editorButton, self.wikiButton)
|
|
||||||
mainWindow.setTabOrder(self.wikiButton, self.jobList)
|
|
||||||
mainWindow.setTabOrder(self.jobList, self.gammaSlider)
|
|
||||||
mainWindow.setTabOrder(self.gammaSlider, self.widthBox)
|
|
||||||
mainWindow.setTabOrder(self.widthBox, self.heightBox)
|
|
||||||
mainWindow.setTabOrder(self.heightBox, self.croppingPowerSlider)
|
|
||||||
|
|
||||||
def retranslateUi(self, mainWindow):
|
def retranslateUi(self, mainWindow):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None))
|
||||||
mainWindow.setWindowTitle(_translate("mainWindow", "Kindle Comic Converter"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.upscaleBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>"))
|
self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Default Title</p></body></html>", None))
|
||||||
self.upscaleBox.setText(_translate("mainWindow", "Stretch/Upscale"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>"))
|
self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None))
|
||||||
self.rotateBox.setText(_translate("mainWindow", "Spread splitter"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.outputSplit.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>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>"))
|
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
|
||||||
self.outputSplit.setText(_translate("mainWindow", "Output split"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>"))
|
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left mode", None))
|
||||||
self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>"))
|
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))
|
||||||
self.colorBox.setText(_translate("mainWindow", "Color mode"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.gammaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable automatic gamma correction.</p></body></html>"))
|
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
||||||
self.gammaBox.setText(_translate("mainWindow", "Custom gamma"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.borderBox.setToolTip(_translate("mainWindow", "<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>"))
|
self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>", None))
|
||||||
self.borderBox.setText(_translate("mainWindow", "W/B margins"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.mangaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
|
self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
||||||
self.mangaBox.setText(_translate("mainWindow", "Manga mode"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>"))
|
self.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))
|
||||||
self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2/HQ"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.mozJpegBox.setToolTip(_translate("mainWindow", "<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>"))
|
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
||||||
self.mozJpegBox.setText(_translate("mainWindow", "JPEG/PNG/mozJpeg"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.maximizeStrips.setToolTip(_translate("mainWindow", "<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>"))
|
self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
|
||||||
self.maximizeStrips.setText(_translate("mainWindow", "1x4 to 2x2 strips"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.croppingBox.setToolTip(_translate("mainWindow", "<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>"))
|
self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
|
||||||
self.croppingBox.setText(_translate("mainWindow", "Cropping mode"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.deleteBox.setToolTip(_translate("mainWindow", "Delete input file(s) or directory. It\'s not recoverable!"))
|
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))
|
||||||
self.deleteBox.setText(_translate("mainWindow", "Delete input"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.disableProcessingBox.setToolTip(_translate("mainWindow", "<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>"))
|
self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None))
|
||||||
self.disableProcessingBox.setText(_translate("mainWindow", "Disable processing"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))
|
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))
|
||||||
self.croppingPowerLabel.setText(_translate("mainWindow", "Cropping power:"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.directoryButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>"))
|
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
||||||
self.directoryButton.setText(_translate("mainWindow", "Add directory"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.fileButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>"))
|
self.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 untouched.</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))
|
||||||
self.fileButton.setText(_translate("mainWindow", "Add file"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.deviceBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>"))
|
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
|
||||||
self.formatBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.convertButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>"))
|
self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>< 1.0 makes the image brighter.<br/>> 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html>", None))
|
||||||
self.convertButton.setText(_translate("mainWindow", "Convert"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.clearButton.setText(_translate("mainWindow", "Clear list"))
|
self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
|
||||||
self.editorButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to edit directory.</p></body></html>"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.editorButton.setText(_translate("mainWindow", "Editor"))
|
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))
|
||||||
self.wikiButton.setText(_translate("mainWindow", "Wiki"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.hLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
|
||||||
self.hLabel.setText(_translate("mainWindow", "Custom height:"))
|
#if QT_CONFIG(tooltip)
|
||||||
self.widthBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
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))
|
||||||
self.wLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.wLabel.setText(_translate("mainWindow", "Custom width:"))
|
self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None))
|
||||||
self.heightBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
#if QT_CONFIG(tooltip)
|
||||||
from . import KCC_rc
|
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.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.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.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.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.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.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.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.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Don't use metadata Title<br/></span>Write default title.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Add metadata Title to the default schema<br/></span>Write default title with Title from ComicInfo.xml or other embedded metadata.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Use metadata Title only<br/></span>Write Title from ComicInfo.xml or other embedded metadata.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", 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>By default, KCC maps the darkest pixel value to pure black (the black point.)</p><p>Extreme black point sets the black point to be the most common dark pixel value.</p><p>Useful when text is black but artwork is gray.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
#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)
|
||||||
|
self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.widthBox.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.wLabel.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)
|
||||||
|
self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None))
|
||||||
|
#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)
|
||||||
|
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", 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))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
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.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.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.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)
|
||||||
|
#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
|
||||||
|
|
||||||
|
|||||||
@@ -1,118 +1,179 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'gui/MetaEditor.ui'
|
################################################################################
|
||||||
#
|
## Form generated from reading UI file 'MetaEditor.ui'
|
||||||
# Created by: PyQt5 UI code generator 5.15.2
|
##
|
||||||
#
|
## Created by: Qt User Interface Compiler version 6.9.3
|
||||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
##
|
||||||
# run again. Do not edit this file unless you know what you are doing.
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
||||||
|
|
||||||
|
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||||
|
QMetaObject, QObject, QPoint, QRect,
|
||||||
|
QSize, QTime, QUrl, Qt)
|
||||||
|
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||||
|
QFont, QFontDatabase, QGradient, QIcon,
|
||||||
|
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||||
|
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||||
|
from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout, QHBoxLayout,
|
||||||
|
QLabel, QLineEdit, QPushButton, QSizePolicy,
|
||||||
|
QVBoxLayout, QWidget)
|
||||||
|
from . import KCC_rc
|
||||||
|
|
||||||
class Ui_editorDialog(object):
|
class Ui_editorDialog(object):
|
||||||
def setupUi(self, editorDialog):
|
def setupUi(self, editorDialog):
|
||||||
editorDialog.setObjectName("editorDialog")
|
if not editorDialog.objectName():
|
||||||
|
editorDialog.setObjectName(u"editorDialog")
|
||||||
editorDialog.resize(400, 260)
|
editorDialog.resize(400, 260)
|
||||||
editorDialog.setMinimumSize(QtCore.QSize(400, 260))
|
editorDialog.setMinimumSize(QSize(400, 260))
|
||||||
icon = QtGui.QIcon()
|
icon = QIcon()
|
||||||
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
editorDialog.setWindowIcon(icon)
|
editorDialog.setWindowIcon(icon)
|
||||||
self.verticalLayout = QtWidgets.QVBoxLayout(editorDialog)
|
self.verticalLayout = QVBoxLayout(editorDialog)
|
||||||
|
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||||
self.verticalLayout.setContentsMargins(-1, -1, -1, 5)
|
self.verticalLayout.setContentsMargins(-1, -1, -1, 5)
|
||||||
self.verticalLayout.setObjectName("verticalLayout")
|
self.editorWidget = QWidget(editorDialog)
|
||||||
self.editorWidget = QtWidgets.QWidget(editorDialog)
|
self.editorWidget.setObjectName(u"editorWidget")
|
||||||
self.editorWidget.setObjectName("editorWidget")
|
self.gridLayout = QGridLayout(self.editorWidget)
|
||||||
self.gridLayout = QtWidgets.QGridLayout(self.editorWidget)
|
self.gridLayout.setObjectName(u"gridLayout")
|
||||||
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.gridLayout.setObjectName("gridLayout")
|
self.label_1 = QLabel(self.editorWidget)
|
||||||
self.label_1 = QtWidgets.QLabel(self.editorWidget)
|
self.label_1.setObjectName(u"label_1")
|
||||||
self.label_1.setObjectName("label_1")
|
|
||||||
self.gridLayout.addWidget(self.label_1, 0, 0, 1, 1)
|
self.gridLayout.addWidget(self.label_1, 0, 0, 1, 1)
|
||||||
self.seriesLine = QtWidgets.QLineEdit(self.editorWidget)
|
|
||||||
self.seriesLine.setObjectName("seriesLine")
|
self.seriesLine = QLineEdit(self.editorWidget)
|
||||||
|
self.seriesLine.setObjectName(u"seriesLine")
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.seriesLine, 0, 1, 1, 1)
|
self.gridLayout.addWidget(self.seriesLine, 0, 1, 1, 1)
|
||||||
self.label_2 = QtWidgets.QLabel(self.editorWidget)
|
|
||||||
self.label_2.setObjectName("label_2")
|
self.label_2 = QLabel(self.editorWidget)
|
||||||
|
self.label_2.setObjectName(u"label_2")
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
|
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
|
||||||
self.volumeLine = QtWidgets.QLineEdit(self.editorWidget)
|
|
||||||
self.volumeLine.setObjectName("volumeLine")
|
self.volumeLine = QLineEdit(self.editorWidget)
|
||||||
|
self.volumeLine.setObjectName(u"volumeLine")
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.volumeLine, 1, 1, 1, 1)
|
self.gridLayout.addWidget(self.volumeLine, 1, 1, 1, 1)
|
||||||
self.label_3 = QtWidgets.QLabel(self.editorWidget)
|
|
||||||
self.label_3.setObjectName("label_3")
|
self.label_3 = QLabel(self.editorWidget)
|
||||||
self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
|
self.label_3.setObjectName(u"label_3")
|
||||||
self.numberLine = QtWidgets.QLineEdit(self.editorWidget)
|
|
||||||
self.numberLine.setObjectName("numberLine")
|
self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1)
|
||||||
self.gridLayout.addWidget(self.numberLine, 2, 1, 1, 1)
|
|
||||||
self.label_4 = QtWidgets.QLabel(self.editorWidget)
|
self.numberLine = QLineEdit(self.editorWidget)
|
||||||
self.label_4.setObjectName("label_4")
|
self.numberLine.setObjectName(u"numberLine")
|
||||||
self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
|
|
||||||
self.writerLine = QtWidgets.QLineEdit(self.editorWidget)
|
self.gridLayout.addWidget(self.numberLine, 3, 1, 1, 1)
|
||||||
self.writerLine.setObjectName("writerLine")
|
|
||||||
self.gridLayout.addWidget(self.writerLine, 3, 1, 1, 1)
|
self.label_4 = QLabel(self.editorWidget)
|
||||||
self.label_5 = QtWidgets.QLabel(self.editorWidget)
|
self.label_4.setObjectName(u"label_4")
|
||||||
self.label_5.setObjectName("label_5")
|
|
||||||
self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1)
|
self.gridLayout.addWidget(self.label_4, 4, 0, 1, 1)
|
||||||
self.pencillerLine = QtWidgets.QLineEdit(self.editorWidget)
|
|
||||||
self.pencillerLine.setObjectName("pencillerLine")
|
self.writerLine = QLineEdit(self.editorWidget)
|
||||||
self.gridLayout.addWidget(self.pencillerLine, 4, 1, 1, 1)
|
self.writerLine.setObjectName(u"writerLine")
|
||||||
self.label_6 = QtWidgets.QLabel(self.editorWidget)
|
|
||||||
self.label_6.setObjectName("label_6")
|
self.gridLayout.addWidget(self.writerLine, 4, 1, 1, 1)
|
||||||
self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1)
|
|
||||||
self.inkerLine = QtWidgets.QLineEdit(self.editorWidget)
|
self.label_5 = QLabel(self.editorWidget)
|
||||||
self.inkerLine.setObjectName("inkerLine")
|
self.label_5.setObjectName(u"label_5")
|
||||||
self.gridLayout.addWidget(self.inkerLine, 5, 1, 1, 1)
|
|
||||||
self.label_7 = QtWidgets.QLabel(self.editorWidget)
|
self.gridLayout.addWidget(self.label_5, 5, 0, 1, 1)
|
||||||
self.label_7.setObjectName("label_7")
|
|
||||||
self.gridLayout.addWidget(self.label_7, 6, 0, 1, 1)
|
self.pencillerLine = QLineEdit(self.editorWidget)
|
||||||
self.coloristLine = QtWidgets.QLineEdit(self.editorWidget)
|
self.pencillerLine.setObjectName(u"pencillerLine")
|
||||||
self.coloristLine.setObjectName("coloristLine")
|
|
||||||
self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1)
|
self.gridLayout.addWidget(self.pencillerLine, 5, 1, 1, 1)
|
||||||
|
|
||||||
|
self.label_6 = QLabel(self.editorWidget)
|
||||||
|
self.label_6.setObjectName(u"label_6")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.label_6, 6, 0, 1, 1)
|
||||||
|
|
||||||
|
self.inkerLine = QLineEdit(self.editorWidget)
|
||||||
|
self.inkerLine.setObjectName(u"inkerLine")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.inkerLine, 6, 1, 1, 1)
|
||||||
|
|
||||||
|
self.label_7 = QLabel(self.editorWidget)
|
||||||
|
self.label_7.setObjectName(u"label_7")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.label_7, 7, 0, 1, 1)
|
||||||
|
|
||||||
|
self.coloristLine = QLineEdit(self.editorWidget)
|
||||||
|
self.coloristLine.setObjectName(u"coloristLine")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.coloristLine, 7, 1, 1, 1)
|
||||||
|
|
||||||
|
self.label_8 = QLabel(self.editorWidget)
|
||||||
|
self.label_8.setObjectName(u"label_8")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.label_8, 2, 0, 1, 1)
|
||||||
|
|
||||||
|
self.titleLine = QLineEdit(self.editorWidget)
|
||||||
|
self.titleLine.setObjectName(u"titleLine")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.titleLine, 2, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
self.verticalLayout.addWidget(self.editorWidget)
|
self.verticalLayout.addWidget(self.editorWidget)
|
||||||
self.optionWidget = QtWidgets.QWidget(editorDialog)
|
|
||||||
self.optionWidget.setObjectName("optionWidget")
|
self.optionWidget = QWidget(editorDialog)
|
||||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.optionWidget)
|
self.optionWidget.setObjectName(u"optionWidget")
|
||||||
|
self.horizontalLayout = QHBoxLayout(self.optionWidget)
|
||||||
|
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
self.statusLabel = QLabel(self.optionWidget)
|
||||||
self.statusLabel = QtWidgets.QLabel(self.optionWidget)
|
self.statusLabel.setObjectName(u"statusLabel")
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
sizePolicy = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
|
||||||
sizePolicy.setHorizontalStretch(0)
|
sizePolicy.setHorizontalStretch(0)
|
||||||
sizePolicy.setVerticalStretch(0)
|
sizePolicy.setVerticalStretch(0)
|
||||||
sizePolicy.setHeightForWidth(self.statusLabel.sizePolicy().hasHeightForWidth())
|
sizePolicy.setHeightForWidth(self.statusLabel.sizePolicy().hasHeightForWidth())
|
||||||
self.statusLabel.setSizePolicy(sizePolicy)
|
self.statusLabel.setSizePolicy(sizePolicy)
|
||||||
self.statusLabel.setText("")
|
|
||||||
self.statusLabel.setObjectName("statusLabel")
|
|
||||||
self.horizontalLayout.addWidget(self.statusLabel)
|
self.horizontalLayout.addWidget(self.statusLabel)
|
||||||
self.okButton = QtWidgets.QPushButton(self.optionWidget)
|
|
||||||
self.okButton.setMinimumSize(QtCore.QSize(0, 30))
|
self.okButton = QPushButton(self.optionWidget)
|
||||||
icon1 = QtGui.QIcon()
|
self.okButton.setObjectName(u"okButton")
|
||||||
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
self.okButton.setMinimumSize(QSize(0, 30))
|
||||||
|
icon1 = QIcon()
|
||||||
|
icon1.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.okButton.setIcon(icon1)
|
self.okButton.setIcon(icon1)
|
||||||
self.okButton.setObjectName("okButton")
|
|
||||||
self.horizontalLayout.addWidget(self.okButton)
|
self.horizontalLayout.addWidget(self.okButton)
|
||||||
self.cancelButton = QtWidgets.QPushButton(self.optionWidget)
|
|
||||||
self.cancelButton.setMinimumSize(QtCore.QSize(0, 30))
|
self.cancelButton = QPushButton(self.optionWidget)
|
||||||
icon2 = QtGui.QIcon()
|
self.cancelButton.setObjectName(u"cancelButton")
|
||||||
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
self.cancelButton.setMinimumSize(QSize(0, 30))
|
||||||
|
icon2 = QIcon()
|
||||||
|
icon2.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.cancelButton.setIcon(icon2)
|
self.cancelButton.setIcon(icon2)
|
||||||
self.cancelButton.setObjectName("cancelButton")
|
|
||||||
self.horizontalLayout.addWidget(self.cancelButton)
|
self.horizontalLayout.addWidget(self.cancelButton)
|
||||||
|
|
||||||
|
|
||||||
self.verticalLayout.addWidget(self.optionWidget)
|
self.verticalLayout.addWidget(self.optionWidget)
|
||||||
|
|
||||||
|
|
||||||
self.retranslateUi(editorDialog)
|
self.retranslateUi(editorDialog)
|
||||||
QtCore.QMetaObject.connectSlotsByName(editorDialog)
|
|
||||||
|
QMetaObject.connectSlotsByName(editorDialog)
|
||||||
|
# setupUi
|
||||||
|
|
||||||
def retranslateUi(self, editorDialog):
|
def retranslateUi(self, editorDialog):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
editorDialog.setWindowTitle(QCoreApplication.translate("editorDialog", u"Metadata editor", None))
|
||||||
editorDialog.setWindowTitle(_translate("editorDialog", "Metadata editor"))
|
self.label_1.setText(QCoreApplication.translate("editorDialog", u"Series:", None))
|
||||||
self.label_1.setText(_translate("editorDialog", "Series:"))
|
self.label_2.setText(QCoreApplication.translate("editorDialog", u"Volume:", None))
|
||||||
self.label_2.setText(_translate("editorDialog", "Volume:"))
|
self.label_3.setText(QCoreApplication.translate("editorDialog", u"Number:", None))
|
||||||
self.label_3.setText(_translate("editorDialog", "Number:"))
|
self.label_4.setText(QCoreApplication.translate("editorDialog", u"Writer:", None))
|
||||||
self.label_4.setText(_translate("editorDialog", "Writer:"))
|
self.label_5.setText(QCoreApplication.translate("editorDialog", u"Penciller:", None))
|
||||||
self.label_5.setText(_translate("editorDialog", "Penciller:"))
|
self.label_6.setText(QCoreApplication.translate("editorDialog", u"Inker:", None))
|
||||||
self.label_6.setText(_translate("editorDialog", "Inker:"))
|
self.label_7.setText(QCoreApplication.translate("editorDialog", u"Colorist:", None))
|
||||||
self.label_7.setText(_translate("editorDialog", "Colorist:"))
|
self.label_8.setText(QCoreApplication.translate("editorDialog", u"Title:", None))
|
||||||
self.okButton.setText(_translate("editorDialog", "Save"))
|
self.statusLabel.setText("")
|
||||||
self.cancelButton.setText(_translate("editorDialog", "Cancel"))
|
self.okButton.setText(QCoreApplication.translate("editorDialog", u"Save", None))
|
||||||
from . import KCC_rc
|
self.cancelButton.setText(QCoreApplication.translate("editorDialog", u"Cancel", None))
|
||||||
|
# retranslateUi
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '5.6.3'
|
__version__ = '9.2.0'
|
||||||
__license__ = 'ISC'
|
__license__ = 'ISC'
|
||||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,17 +18,15 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from shutil import rmtree, copytree, move
|
from shutil import rmtree
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from PIL import Image, ImageChops, ImageOps, ImageDraw
|
from PIL import Image, ImageChops, ImageOps, ImageDraw, ImageFilter
|
||||||
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
|
from PIL.Image import Dither
|
||||||
try:
|
from .shared import dot_clean, getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||||
from PyQt5 import QtCore
|
|
||||||
except ImportError:
|
|
||||||
QtCore = None
|
|
||||||
|
|
||||||
|
|
||||||
def mergeDirectoryTick(output):
|
def mergeDirectoryTick(output):
|
||||||
@@ -48,6 +46,7 @@ def mergeDirectory(work):
|
|||||||
imagesValid = []
|
imagesValid = []
|
||||||
sizes = []
|
sizes = []
|
||||||
targetHeight = 0
|
targetHeight = 0
|
||||||
|
dot_clean(directory)
|
||||||
for root, _, files in walkLevel(directory, 0):
|
for root, _, files in walkLevel(directory, 0):
|
||||||
for name in files:
|
for name in files:
|
||||||
if getImageFileName(name) is not None:
|
if getImageFileName(name) is not None:
|
||||||
@@ -61,8 +60,8 @@ def mergeDirectory(work):
|
|||||||
imagesValid.append(i[0])
|
imagesValid.append(i[0])
|
||||||
# Silently drop directories that contain too many images
|
# Silently drop directories that contain too many images
|
||||||
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
|
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
|
||||||
if targetHeight > 131072:
|
if targetHeight > 131072 * 2:
|
||||||
return None
|
raise RuntimeError(f'Image too tall at {targetHeight} pixels.')
|
||||||
result = Image.new('RGB', (targetWidth, targetHeight))
|
result = Image.new('RGB', (targetWidth, targetHeight))
|
||||||
y = 0
|
y = 0
|
||||||
for i in imagesValid:
|
for i in imagesValid:
|
||||||
@@ -104,7 +103,11 @@ def splitImage(work):
|
|||||||
Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
|
Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
|
||||||
Image.MAX_IMAGE_PIXELS = 1000000000
|
Image.MAX_IMAGE_PIXELS = 1000000000
|
||||||
imgOrg = Image.open(filePath).convert('RGB')
|
imgOrg = Image.open(filePath).convert('RGB')
|
||||||
imgProcess = Image.open(filePath).convert('1')
|
# I experimented with custom vertical edge kernel [-1, 2, -1] but got poor results
|
||||||
|
imgEdges = Image.open(filePath).convert('L').filter(ImageFilter.FIND_EDGES)
|
||||||
|
# threshold of 8 is too high. 5 is too low.
|
||||||
|
imgProcess = imgEdges.point(lambda p: 255 if p > 6 else 0).convert('1', dither=Dither.NONE)
|
||||||
|
|
||||||
widthImg, heightImg = imgOrg.size
|
widthImg, heightImg = imgOrg.size
|
||||||
if heightImg > opt.height:
|
if heightImg > opt.height:
|
||||||
if opt.debug:
|
if opt.debug:
|
||||||
@@ -115,47 +118,71 @@ def splitImage(work):
|
|||||||
yWork = 0
|
yWork = 0
|
||||||
panelDetected = False
|
panelDetected = False
|
||||||
panels = []
|
panels = []
|
||||||
|
# check git history for how these constant values changed
|
||||||
|
h_pad = int(widthImg / 20)
|
||||||
|
v_pad = int(widthImg / 80)
|
||||||
|
if v_pad % 2:
|
||||||
|
v_pad += 1
|
||||||
while yWork < heightImg:
|
while yWork < heightImg:
|
||||||
tmpImg = imgProcess.crop((4, yWork, widthImg-4, yWork + 4))
|
tmpImg = imgProcess.crop((h_pad, yWork, widthImg - h_pad, yWork + v_pad))
|
||||||
solid = detectSolid(tmpImg)
|
solid = detectSolid(tmpImg)
|
||||||
if not solid and not panelDetected:
|
if not solid and not panelDetected:
|
||||||
panelDetected = True
|
panelDetected = True
|
||||||
panelY1 = yWork - 2
|
panelY1 = yWork
|
||||||
if heightImg - yWork <= 5:
|
if heightImg - yWork <= (v_pad // 2):
|
||||||
if not solid and panelDetected:
|
if not solid and panelDetected:
|
||||||
panelY2 = heightImg
|
panelY2 = heightImg
|
||||||
panelDetected = False
|
panelDetected = False
|
||||||
panels.append((panelY1, panelY2, panelY2 - panelY1))
|
panels.append((panelY1, panelY2, panelY2 - panelY1))
|
||||||
if solid and panelDetected:
|
if solid and panelDetected:
|
||||||
panelDetected = False
|
panelDetected = False
|
||||||
panelY2 = yWork + 6
|
panelY2 = yWork
|
||||||
|
# skip short panel at start
|
||||||
|
if panelY1 < v_pad * 2 and panelY2 - panelY1 < v_pad * 2:
|
||||||
|
continue
|
||||||
panels.append((panelY1, panelY2, panelY2 - panelY1))
|
panels.append((panelY1, panelY2, panelY2 - panelY1))
|
||||||
yWork += 5
|
yWork += v_pad // 2
|
||||||
|
|
||||||
|
max_width = 1072
|
||||||
|
virtual_width = min((max_width, opt.width, widthImg))
|
||||||
|
if opt.width > max_width:
|
||||||
|
virtual_height = int(opt.height/max_width*virtual_width)
|
||||||
|
else:
|
||||||
|
virtual_height = int(opt.height/opt.width*virtual_width)
|
||||||
|
opt.height = virtual_height
|
||||||
|
|
||||||
# Split too big panels
|
# Split too big panels
|
||||||
panelsProcessed = []
|
panelsProcessed = []
|
||||||
for panel in panels:
|
for panel in panels:
|
||||||
|
# 1.52 too high
|
||||||
if panel[2] <= opt.height * 1.5:
|
if panel[2] <= opt.height * 1.5:
|
||||||
panelsProcessed.append(panel)
|
panelsProcessed.append(panel)
|
||||||
elif panel[2] < opt.height * 2:
|
elif panel[2] <= opt.height * 2:
|
||||||
diff = panel[2] - opt.height
|
diff = panel[2] - opt.height
|
||||||
panelsProcessed.append((panel[0], panel[1] - diff, opt.height))
|
panelsProcessed.append((panel[0], panel[1] - diff, opt.height))
|
||||||
panelsProcessed.append((panel[1] - opt.height, panel[1], opt.height))
|
panelsProcessed.append((panel[1] - opt.height, panel[1], opt.height))
|
||||||
else:
|
else:
|
||||||
parts = round(panel[2] / opt.height)
|
# split super long panels with overlap
|
||||||
|
parts = math.ceil(panel[2] / opt.height)
|
||||||
diff = panel[2] // parts
|
diff = panel[2] // parts
|
||||||
for x in range(0, parts):
|
panelsProcessed.append((panel[0], panel[0] + opt.height, opt.height))
|
||||||
panelsProcessed.append((panel[0] + (x * diff), panel[1] - ((parts - x - 1) * diff), diff))
|
for x in range(1, parts - 1):
|
||||||
|
start = panel[0] + (x * diff)
|
||||||
|
panelsProcessed.append((start, start + opt.height, opt.height))
|
||||||
|
panelsProcessed.append((panel[1] - opt.height, panel[1], opt.height))
|
||||||
|
|
||||||
if opt.debug:
|
if opt.debug:
|
||||||
for panel in panelsProcessed:
|
for panel in panelsProcessed:
|
||||||
draw.rectangle(((0, panel[0]), (widthImg, panel[1])), (0, 255, 0, 128), (0, 0, 255, 255))
|
draw.rectangle(((0, panel[0]), (widthImg, panel[1])), (0, 255, 0, 128), (0, 0, 255, 255))
|
||||||
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
|
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
|
||||||
|
# debugImage.show()
|
||||||
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
|
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
|
||||||
|
|
||||||
# Create virtual pages
|
# Create virtual pages
|
||||||
pages = []
|
pages = []
|
||||||
currentPage = []
|
currentPage = []
|
||||||
|
# TODO: 1.25 way too high, 1.1 too high, 1.05 slightly too high(?), optimized for 2 page landscape reading
|
||||||
|
# opt.height = max_height = virtual_height * 1.00
|
||||||
pageLeft = opt.height
|
pageLeft = opt.height
|
||||||
panelNumber = 0
|
panelNumber = 0
|
||||||
for panel in panelsProcessed:
|
for panel in panelsProcessed:
|
||||||
@@ -185,7 +212,7 @@ def splitImage(work):
|
|||||||
panelImg = imgOrg.crop((0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]))
|
panelImg = imgOrg.crop((0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]))
|
||||||
newPage.paste(panelImg, (0, targetHeight))
|
newPage.paste(panelImg, (0, targetHeight))
|
||||||
targetHeight += panelsProcessed[panel][2]
|
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
|
pageNumber += 1
|
||||||
os.remove(filePath)
|
os.remove(filePath)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -204,6 +231,8 @@ def main(argv=None, qtgui=None):
|
|||||||
" with spaces.")
|
" with spaces.")
|
||||||
main_options.add_argument("-y", "--height", type=int, dest="height", default=0,
|
main_options.add_argument("-y", "--height", type=int, dest="height", default=0,
|
||||||
help="Height of the target device screen")
|
help="Height of the target device screen")
|
||||||
|
main_options.add_argument("-x", "--width", type=int, dest="width", default=0,
|
||||||
|
help="Width of the target device screen")
|
||||||
main_options.add_argument("-i", "--in-place", action="store_true", dest="inPlace", default=False,
|
main_options.add_argument("-i", "--in-place", action="store_true", dest="inPlace", default=False,
|
||||||
help="Overwrite source directory")
|
help="Overwrite source directory")
|
||||||
main_options.add_argument("-m", "--merge", action="store_true", dest="merge", default=False,
|
main_options.add_argument("-m", "--merge", action="store_true", dest="merge", default=False,
|
||||||
@@ -225,7 +254,7 @@ def main(argv=None, qtgui=None):
|
|||||||
targetDir = sourceDir + "-Splitted"
|
targetDir = sourceDir + "-Splitted"
|
||||||
if os.path.isdir(sourceDir):
|
if os.path.isdir(sourceDir):
|
||||||
rmtree(targetDir, True)
|
rmtree(targetDir, True)
|
||||||
copytree(sourceDir, targetDir)
|
os.renames(sourceDir, targetDir)
|
||||||
work = []
|
work = []
|
||||||
pagenumber = 1
|
pagenumber = 1
|
||||||
splitWorkerOutput = []
|
splitWorkerOutput = []
|
||||||
@@ -257,6 +286,7 @@ def main(argv=None, qtgui=None):
|
|||||||
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
||||||
mergeWorkerOutput[0][1])
|
mergeWorkerOutput[0][1])
|
||||||
print("Splitting images...")
|
print("Splitting images...")
|
||||||
|
dot_clean(targetDir)
|
||||||
for root, _, files in os.walk(targetDir, False):
|
for root, _, files in os.walk(targetDir, False):
|
||||||
for name in files:
|
for name in files:
|
||||||
if getImageFileName(name) is not None:
|
if getImageFileName(name) is not None:
|
||||||
@@ -273,6 +303,7 @@ def main(argv=None, qtgui=None):
|
|||||||
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
|
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
|
||||||
splitWorkerPool.close()
|
splitWorkerPool.close()
|
||||||
splitWorkerPool.join()
|
splitWorkerPool.join()
|
||||||
|
dot_clean(targetDir)
|
||||||
if GUI and not GUI.conversionAlive:
|
if GUI and not GUI.conversionAlive:
|
||||||
rmtree(targetDir, True)
|
rmtree(targetDir, True)
|
||||||
raise UserWarning("Conversion interrupted.")
|
raise UserWarning("Conversion interrupted.")
|
||||||
@@ -281,11 +312,10 @@ def main(argv=None, qtgui=None):
|
|||||||
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
||||||
splitWorkerOutput[0][1])
|
splitWorkerOutput[0][1])
|
||||||
if args.inPlace:
|
if args.inPlace:
|
||||||
rmtree(sourceDir)
|
os.renames(targetDir, sourceDir)
|
||||||
move(targetDir, sourceDir)
|
|
||||||
else:
|
else:
|
||||||
rmtree(targetDir, True)
|
rmtree(targetDir, True)
|
||||||
raise UserWarning("Source directory is empty.")
|
raise UserWarning("C2P: Source directory is empty.")
|
||||||
else:
|
else:
|
||||||
raise UserWarning("Provided input is not a directory.")
|
raise UserWarning("Provided input is not a directory.")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -18,87 +18,114 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from functools import cached_property, lru_cache
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
|
||||||
import distro
|
import distro
|
||||||
from psutil import Popen
|
from subprocess import STDOUT, PIPE, CalledProcessError
|
||||||
from shutil import move
|
|
||||||
from subprocess import STDOUT, PIPE
|
|
||||||
from xml.dom.minidom import parseString
|
from xml.dom.minidom import parseString
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
|
from .shared import subprocess_run
|
||||||
|
|
||||||
|
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
|
||||||
|
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
|
||||||
|
|
||||||
|
|
||||||
class ComicArchive:
|
class ComicArchive:
|
||||||
def __init__(self, filepath):
|
def __init__(self, filepath):
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
self.type = None
|
|
||||||
if not os.path.isfile(self.filepath):
|
if not os.path.isfile(self.filepath):
|
||||||
raise OSError('File not found.')
|
raise OSError('File not found.')
|
||||||
process = Popen('7z l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True)
|
self.dirname, self.basename = os.path.split(filepath)
|
||||||
for line in process.stdout:
|
|
||||||
if b'Type =' in line:
|
@cached_property
|
||||||
self.type = line.rstrip().decode().split(' = ')[1].upper()
|
def type(self):
|
||||||
break
|
extraction_commands = [
|
||||||
process.communicate()
|
[SEVENZIP, 'l', '-y', '-p1', self.basename],
|
||||||
if process.returncode != 0 and distro.id() == 'fedora':
|
]
|
||||||
process = Popen('unrar l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True)
|
|
||||||
for line in process.stdout:
|
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||||
if b'Details: ' in line:
|
extraction_commands.append(
|
||||||
self.type = line.rstrip().decode().split(' ')[1].upper()
|
['unrar', 'l', '-y', '-p1', self.basename],
|
||||||
break
|
)
|
||||||
process.communicate()
|
|
||||||
if process.returncode != 0:
|
for cmd in extraction_commands:
|
||||||
raise OSError('Archive is corrupted or encrypted.')
|
try:
|
||||||
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
|
process = subprocess_run(cmd, capture_output=True, check=True, cwd=self.dirname)
|
||||||
raise OSError('Unsupported archive format.')
|
for line in process.stdout.splitlines():
|
||||||
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
|
if b'Type =' in line:
|
||||||
raise OSError('Unsupported archive format.')
|
return line.rstrip().decode().split(' = ')[1].upper()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except CalledProcessError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise OSError(EXTRACTION_ERROR)
|
||||||
|
|
||||||
def extract(self, targetdir):
|
def extract(self, targetdir):
|
||||||
if not os.path.isdir(targetdir):
|
if not os.path.isdir(targetdir):
|
||||||
raise OSError('Target directory doesn\'t exist.')
|
raise OSError('Target directory doesn\'t exist.')
|
||||||
process = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' +
|
|
||||||
self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
missing = []
|
||||||
process.communicate()
|
|
||||||
if process.returncode != 0 and distro.id() == 'fedora':
|
extraction_commands = [
|
||||||
process = Popen('unrar x -y -x__MACOSX -x.DS_Store -xthumbs.db -xThumbs.db "' + self.filepath + '" "' +
|
['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.basename, '-C', targetdir],
|
||||||
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
[SEVENZIP, 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.basename],
|
||||||
process.communicate()
|
]
|
||||||
if process.returncode != 0:
|
|
||||||
raise OSError('Failed to extract archive.')
|
if platform.system() == 'Darwin':
|
||||||
elif process.returncode != 0 and platform.system() == 'Darwin':
|
extraction_commands.append(
|
||||||
process = subprocess.run(f"unar '{self.filepath}' -f -o '{targetdir}'",
|
['unar', self.basename, '-D', '-f', '-o', targetdir]
|
||||||
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
)
|
||||||
if process.returncode != 0:
|
|
||||||
raise Exception(process.stdout.decode("utf-8"))
|
extraction_commands.reverse()
|
||||||
elif process.returncode != 0:
|
|
||||||
raise OSError('Failed to extract archive. Check if p7zip-rar is installed.')
|
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||||
tdir = os.listdir(targetdir)
|
extraction_commands.append(
|
||||||
if 'ComicInfo.xml' in tdir:
|
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.basename, targetdir]
|
||||||
tdir.remove('ComicInfo.xml')
|
)
|
||||||
if len(tdir) == 1 and os.path.isdir(os.path.join(targetdir, tdir[0])):
|
|
||||||
for f in os.listdir(os.path.join(targetdir, tdir[0])):
|
for cmd in extraction_commands:
|
||||||
move(os.path.join(targetdir, tdir[0], f), targetdir)
|
try:
|
||||||
os.rmdir(os.path.join(targetdir, tdir[0]))
|
subprocess_run(cmd, capture_output=True, check=True, cwd=self.dirname)
|
||||||
return targetdir
|
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)
|
||||||
|
|
||||||
def addFile(self, sourcefile):
|
def addFile(self, sourcefile):
|
||||||
if self.type in ['RAR', 'RAR5']:
|
if self.type in ['RAR', 'RAR5']:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
process = Popen('7z a -y "' + self.filepath + '" "' + sourcefile + '"',
|
process = subprocess_run([SEVENZIP, 'a', '-y', self.basename, sourcefile],
|
||||||
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
stdout=PIPE, stderr=STDOUT, cwd=self.dirname)
|
||||||
process.communicate()
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
raise OSError('Failed to add the file.')
|
raise OSError('Failed to add the file.')
|
||||||
|
|
||||||
def extractMetadata(self):
|
def extractMetadata(self):
|
||||||
process = Popen('7z x -y -so "' + self.filepath + '" ComicInfo.xml',
|
process = subprocess_run([SEVENZIP, 'x', '-y', '-so', self.basename, 'ComicInfo.xml'],
|
||||||
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
stdout=PIPE, stderr=STDOUT, cwd=self.dirname)
|
||||||
xml = process.communicate()
|
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
raise OSError('Failed to extract archive.')
|
raise OSError(EXTRACTION_ERROR)
|
||||||
try:
|
try:
|
||||||
return parseString(xml[0])
|
return parseString(process.stdout)
|
||||||
except ExpatError:
|
except ExpatError:
|
||||||
return None
|
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:
|
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)
|
shutil.copyfile(infile, outfile)
|
||||||
f = open(outfile, "r+b")
|
f = open(outfile, "r+b")
|
||||||
self.datain = mmap.mmap(f.fileno(), 0)
|
self.datain = mmap.mmap(f.fileno(), 0)
|
||||||
@@ -147,7 +151,7 @@ class DualMobiMetaFix:
|
|||||||
rec0 = self.datain_rec0
|
rec0 = self.datain_rec0
|
||||||
rec0 = del_exth(rec0, 501)
|
rec0 = del_exth(rec0, 501)
|
||||||
rec0 = del_exth(rec0, 113)
|
rec0 = del_exth(rec0, 113)
|
||||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
rec0 = add_exth(rec0, 501, cdetype)
|
||||||
rec0 = add_exth(rec0, 113, asin)
|
rec0 = add_exth(rec0, 113, asin)
|
||||||
replacesection(self.datain, 0, rec0)
|
replacesection(self.datain, 0, rec0)
|
||||||
|
|
||||||
@@ -174,7 +178,7 @@ class DualMobiMetaFix:
|
|||||||
rec0 = self.datain_kfrec0
|
rec0 = self.datain_kfrec0
|
||||||
rec0 = del_exth(rec0, 501)
|
rec0 = del_exth(rec0, 501)
|
||||||
rec0 = del_exth(rec0, 113)
|
rec0 = del_exth(rec0, 113)
|
||||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
rec0 = add_exth(rec0, 501, cdetype)
|
||||||
rec0 = add_exth(rec0, 113, asin)
|
rec0 = add_exth(rec0, 113, asin)
|
||||||
replacesection(self.datain, datain_kf8, rec0)
|
replacesection(self.datain, datain_kf8, rec0)
|
||||||
|
|
||||||
|
|||||||
@@ -20,16 +20,17 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import numpy as np
|
||||||
|
from pathlib import Path
|
||||||
|
from functools import cached_property
|
||||||
import mozjpeg_lossless_optimization
|
import mozjpeg_lossless_optimization
|
||||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter, ImageDraw
|
||||||
from .shared import md5Checksum
|
|
||||||
|
|
||||||
# 0.045 was determined by
|
from .rainbow_artifacts_eraser import erase_rainbow_artifacts
|
||||||
# 1200 / 824 = 1.456 (Kindle DX resolution)
|
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
||||||
# 2250 / 1500 = 1.5 (Typical manga page resolution)
|
from .inter_panel_crop_alg import crop_empty_inter_panel
|
||||||
# 1.5 - 1.456 < 0.045
|
|
||||||
# 0.045 / 1.5 = 0.03 (So maximum 3% of is cropped)
|
AUTO_CROP_THRESHOLD = 0.015
|
||||||
AUTO_CROP_THRESHOLD = 0.045
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileData:
|
class ProfileData:
|
||||||
@@ -83,32 +84,61 @@ class ProfileData:
|
|||||||
PalleteNull = [
|
PalleteNull = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ProfilesKindleEBOK = {
|
||||||
|
'K1': ("Kindle 1", (600, 670), Palette4, 1.0),
|
||||||
|
'K2': ("Kindle 2", (600, 670), Palette15, 1.0),
|
||||||
|
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.0),
|
||||||
|
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.0),
|
||||||
|
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.0),
|
||||||
|
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.0),
|
||||||
|
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilesKindlePDOC = {
|
||||||
|
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.0),
|
||||||
|
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0),
|
||||||
|
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
|
||||||
|
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0),
|
||||||
|
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0),
|
||||||
|
'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.0),
|
||||||
|
'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilesKindle = {
|
||||||
|
**ProfilesKindleEBOK,
|
||||||
|
**ProfilesKindlePDOC
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilesKobo = {
|
||||||
|
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.0),
|
||||||
|
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.0),
|
||||||
|
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.0),
|
||||||
|
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.0),
|
||||||
|
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.0),
|
||||||
|
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.0),
|
||||||
|
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.0),
|
||||||
|
'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.0),
|
||||||
|
'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.0),
|
||||||
|
'KoCC': ("Kobo Clara Colour", (1072, 1448), Palette16, 1.0),
|
||||||
|
'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.0),
|
||||||
|
'KoLC': ("Kobo Libra Colour", (1264, 1680), Palette16, 1.0),
|
||||||
|
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.0),
|
||||||
|
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.0),
|
||||||
|
'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilesRemarkable = {
|
||||||
|
'Rmk1': ("reMarkable 1", (1404, 1872), Palette16, 1.0),
|
||||||
|
'Rmk2': ("reMarkable 2", (1404, 1872), Palette16, 1.0),
|
||||||
|
'RmkPP': ("reMarkable Paper Pro", (1620, 2160), Palette16, 1.0),
|
||||||
|
'RmkPPMove': ("reMarkable Paper Pro Move", (954, 1696), Palette16, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
Profiles = {
|
Profiles = {
|
||||||
'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
|
**ProfilesKindle,
|
||||||
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
|
**ProfilesKobo,
|
||||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
**ProfilesRemarkable,
|
||||||
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
'OTHER': ("Other", (0, 0), Palette16, 1.0),
|
||||||
'K578': ("Kindle", (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),
|
|
||||||
'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),
|
|
||||||
'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),
|
|
||||||
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8),
|
|
||||||
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8),
|
|
||||||
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8),
|
|
||||||
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
|
|
||||||
'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.8),
|
|
||||||
'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.8),
|
|
||||||
'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.8),
|
|
||||||
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
|
||||||
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8),
|
|
||||||
'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.8),
|
|
||||||
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -119,8 +149,13 @@ class ComicPageParser:
|
|||||||
self.source = source
|
self.source = source
|
||||||
self.size = self.opt.profileData[1]
|
self.size = self.opt.profileData[1]
|
||||||
self.payload = []
|
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()
|
||||||
|
with Image.open(srcImgPath) as im:
|
||||||
|
self.image = im.copy()
|
||||||
|
|
||||||
self.fill = self.fillCheck()
|
self.fill = self.fillCheck()
|
||||||
# backwards compatibility for Pillow >9.1.0
|
# backwards compatibility for Pillow >9.1.0
|
||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
@@ -151,10 +186,13 @@ class ComicPageParser:
|
|||||||
new_image = Image.new("RGB", (int(width / 2), int(height*2)))
|
new_image = Image.new("RGB", (int(width / 2), int(height*2)))
|
||||||
new_image.paste(pageone, (0, 0))
|
new_image.paste(pageone, (0, 0))
|
||||||
new_image.paste(pagetwo, (0, height))
|
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 \
|
elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
|
||||||
and not self.opt.webtoon and self.opt.splitter == 1:
|
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:
|
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
||||||
if self.opt.splitter != 1:
|
if self.opt.splitter != 1:
|
||||||
if width > height:
|
if width > height:
|
||||||
@@ -169,35 +207,15 @@ class ComicPageParser:
|
|||||||
else:
|
else:
|
||||||
pageone = self.image.crop(leftbox)
|
pageone = self.image.crop(leftbox)
|
||||||
pagetwo = self.image.crop(rightbox)
|
pagetwo = self.image.crop(rightbox)
|
||||||
self.payload.append(['S1', self.source, pageone, self.color, self.fill])
|
self.payload.append(['S1', self.source, pageone, self.fill])
|
||||||
self.payload.append(['S2', self.source, pagetwo, self.color, self.fill])
|
self.payload.append(['S2', self.source, pagetwo, self.fill])
|
||||||
if self.opt.splitter > 0:
|
if self.opt.splitter > 0:
|
||||||
self.payload.append(['R', self.source, self.image.rotate(90, Image.Resampling.BICUBIC, True),
|
spread = self.image
|
||||||
self.color, self.fill])
|
if not self.opt.norotate:
|
||||||
|
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||||
|
self.payload.append(['R', self.source, spread, self.fill])
|
||||||
else:
|
else:
|
||||||
self.payload.append(['N', self.source, self.image, self.color, self.fill])
|
self.payload.append(['N', self.source, self.image, 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
|
|
||||||
|
|
||||||
def fillCheck(self):
|
def fillCheck(self):
|
||||||
if self.opt.bordersColor:
|
if self.opt.bordersColor:
|
||||||
@@ -239,79 +257,238 @@ class ComicPageParser:
|
|||||||
|
|
||||||
|
|
||||||
class ComicPage:
|
class ComicPage:
|
||||||
def __init__(self, options, mode, path, image, color, fill):
|
def __init__(self, options, mode, path, image, fill):
|
||||||
self.opt = options
|
self.opt = options
|
||||||
_, self.size, self.palette, self.gamma = self.opt.profileData
|
_, self.size, self.palette, self.gamma = self.opt.profileData
|
||||||
if self.opt.hq:
|
if self.opt.hq:
|
||||||
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
||||||
self.image = image
|
self.kindle_scribe_azw3 = (options.profile == 'KS') and (options.format in ('MOBI', 'EPUB'))
|
||||||
self.color = color
|
self.original_color_mode = image.mode
|
||||||
|
# TODO: color check earlier
|
||||||
|
self.image = image.convert("RGB")
|
||||||
self.fill = fill
|
self.fill = fill
|
||||||
self.rotated = False
|
self.rotated = False
|
||||||
self.orgPath = os.path.join(path[0], path[1])
|
self.orgPath = os.path.join(path[0], path[1])
|
||||||
|
self.targetPathStart = os.path.join(path[0], os.path.splitext(path[1])[0])
|
||||||
if 'N' in mode:
|
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:
|
elif 'R' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
|
self.targetPathOrder = '-kcc-a' if options.rotatefirst else '-kcc-d'
|
||||||
self.rotated = True
|
if not options.norotate:
|
||||||
|
self.rotated = True
|
||||||
elif 'S1' in mode:
|
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:
|
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
|
# backwards compatibility for Pillow >9.1.0
|
||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
Image.Resampling = Image
|
Image.Resampling = Image
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def color(self):
|
||||||
|
if self.original_color_mode in ("L", "1"):
|
||||||
|
return False
|
||||||
|
if self.opt.webtoon:
|
||||||
|
return True
|
||||||
|
if self.calculate_color():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# cut off pixels from both ends of the histogram to remove jpg compression artifacts
|
||||||
|
# for better accuracy, you could split the image in half and analyze each half separately
|
||||||
|
def histograms_cutoff(self, cb, cr, cutoff=(2, 2)):
|
||||||
|
cb_hist = cb.histogram()
|
||||||
|
cr_hist = cr.histogram()
|
||||||
|
|
||||||
|
for h in cb_hist, cr_hist:
|
||||||
|
# get number of pixels
|
||||||
|
n = sum(h)
|
||||||
|
# remove cutoff% pixels from the low end
|
||||||
|
cut = int(n * cutoff[0] // 100)
|
||||||
|
for lo in range(256):
|
||||||
|
if cut > h[lo]:
|
||||||
|
cut = cut - h[lo]
|
||||||
|
h[lo] = 0
|
||||||
|
else:
|
||||||
|
h[lo] -= cut
|
||||||
|
cut = 0
|
||||||
|
if cut <= 0:
|
||||||
|
break
|
||||||
|
# remove cutoff% samples from the high end
|
||||||
|
cut = int(n * cutoff[1] // 100)
|
||||||
|
for hi in range(255, -1, -1):
|
||||||
|
if cut > h[hi]:
|
||||||
|
cut = cut - h[hi]
|
||||||
|
h[hi] = 0
|
||||||
|
else:
|
||||||
|
h[hi] -= cut
|
||||||
|
cut = 0
|
||||||
|
if cut <= 0:
|
||||||
|
break
|
||||||
|
return cb_hist, cr_hist
|
||||||
|
|
||||||
|
def calculate_color(self):
|
||||||
|
img = self.image.convert("YCbCr")
|
||||||
|
_, cb, cr = img.split()
|
||||||
|
|
||||||
|
# get rid of some jpg compression
|
||||||
|
cutoff = (.2, .2)
|
||||||
|
cb_hist, cr_hist = self.histograms_cutoff(cb, cr, cutoff)
|
||||||
|
|
||||||
|
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]
|
||||||
|
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
||||||
|
|
||||||
|
# bias adjustment
|
||||||
|
SPREAD_THRESHOLD = 5
|
||||||
|
if not self.opt.forcecolor and cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# check for large amount of extreme colors
|
||||||
|
# 11 if too high. 10 is barely enough. If needed make it magnitude of both
|
||||||
|
DIFF_THRESHOLD = 10
|
||||||
|
if any([
|
||||||
|
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# get ride of most jpg compression
|
||||||
|
cutoff = (2, 2)
|
||||||
|
cb_hist, cr_hist = self.histograms_cutoff(cb, cr, cutoff)
|
||||||
|
|
||||||
|
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]
|
||||||
|
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
||||||
|
|
||||||
|
# bias adjustment
|
||||||
|
SPREAD_THRESHOLD = 5
|
||||||
|
if not self.opt.forcecolor and cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# check for any amount of mild colors still remaining
|
||||||
|
DIFF_THRESHOLD = 6
|
||||||
|
if any([
|
||||||
|
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
]):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def saveToDir(self):
|
def saveToDir(self):
|
||||||
try:
|
try:
|
||||||
flags = []
|
flags = []
|
||||||
if not self.opt.forcecolor and not self.opt.forcepng:
|
|
||||||
self.image = self.image.convert('L')
|
|
||||||
if self.rotated:
|
if self.rotated:
|
||||||
flags.append('Rotated')
|
flags.append('Rotated')
|
||||||
if self.fill != 'white':
|
if self.fill != 'white':
|
||||||
flags.append('BlackBackground')
|
flags.append('BlackBackground')
|
||||||
if self.opt.forcepng:
|
if self.opt.kindle_scribe_azw3 and self.image.size[1] > 1920:
|
||||||
self.image.info["transparency"] = None
|
w, h = self.image.size
|
||||||
self.targetPath += '.png'
|
targetPath = self.save_with_codec(self.image.crop((0, 0, w, 1920)), self.targetPathStart + self.targetPathOrder + '-above')
|
||||||
self.image.save(self.targetPath, 'PNG', optimize=1)
|
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:
|
else:
|
||||||
self.targetPath += '.jpg'
|
targetPath = self.save_with_codec(self.image, self.targetPathStart + self.targetPathOrder)
|
||||||
if self.opt.mozjpeg:
|
if os.path.isfile(self.orgPath):
|
||||||
with io.BytesIO() as output:
|
os.remove(self.orgPath)
|
||||||
self.image.save(output, format="JPEG", optimize=1, quality=85)
|
return [Path(targetPath).name, flags]
|
||||||
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]
|
|
||||||
except IOError as err:
|
except IOError as err:
|
||||||
raise RuntimeError('Cannot save image. ' + str(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
|
gamma = self.opt.gamma
|
||||||
if gamma < 0.1:
|
if gamma < 0.1:
|
||||||
gamma = self.gamma
|
gamma = self.gamma
|
||||||
if self.gamma != 1.0 and self.color:
|
if self.gamma != 1.0 and self.color:
|
||||||
gamma = 1.0
|
gamma = 1.0
|
||||||
if gamma == 1.0:
|
if gamma == 1.0:
|
||||||
self.image = ImageOps.autocontrast(self.image)
|
pass
|
||||||
else:
|
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.webtoon:
|
||||||
|
return
|
||||||
|
if self.opt.noautocontrast:
|
||||||
|
return
|
||||||
|
if self.color and not self.opt.colorautocontrast:
|
||||||
|
return
|
||||||
|
|
||||||
|
# if image is extremely low contrast, that was probably intentional
|
||||||
|
extrema = self.image.convert('L').getextrema()
|
||||||
|
if extrema[1] - extrema[0] < (255 - 32 * 3):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.opt.autolevel:
|
||||||
|
self.autolevelImage()
|
||||||
|
|
||||||
|
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
||||||
|
|
||||||
|
def autolevelImage(self):
|
||||||
|
img = self.image
|
||||||
|
if self.color:
|
||||||
|
img = self.image.convert("YCbCr")
|
||||||
|
y, cb, cr = img.split()
|
||||||
|
img = y
|
||||||
|
else:
|
||||||
|
img = img.convert('L')
|
||||||
|
h = img.histogram()
|
||||||
|
most_common_dark_pixel_count = max(h[:64])
|
||||||
|
black_point = h.index(most_common_dark_pixel_count)
|
||||||
|
bp = black_point
|
||||||
|
img = img.point(lambda p: p if p > bp else bp)
|
||||||
|
if self.color:
|
||||||
|
self.image = Image.merge(mode='YCbCr', bands=[img, cb, cr]).convert('RGB')
|
||||||
|
else:
|
||||||
|
self.image = img
|
||||||
|
|
||||||
|
def convertToGrayscale(self):
|
||||||
|
self.image = self.image.convert('L')
|
||||||
|
|
||||||
def quantizeImage(self):
|
def quantizeImage(self):
|
||||||
colors = len(self.palette) // 3
|
# remove all color pixels from image, since colorCheck() has some tolerance
|
||||||
if colors < 256:
|
# quantize with a small number of color pixels in a mostly b/w image can have unexpected results
|
||||||
self.palette += self.palette[:3] * (256 - colors)
|
self.image = self.image.convert("RGB")
|
||||||
|
|
||||||
palImg = Image.new('P', (1, 1))
|
palImg = Image.new('P', (1, 1))
|
||||||
palImg.putpalette(self.palette)
|
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)
|
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 and all(dim > 1 for dim in self.image.size):
|
||||||
|
self.image = erase_rainbow_artifacts(self.image, is_color)
|
||||||
|
|
||||||
def resizeImage(self):
|
def resizeImage(self):
|
||||||
ratio_device = float(self.size[1]) / float(self.size[0])
|
ratio_device = float(self.size[1]) / float(self.size[0])
|
||||||
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
|
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
|
||||||
@@ -319,79 +496,59 @@ class ComicPage:
|
|||||||
if self.opt.stretch:
|
if self.opt.stretch:
|
||||||
self.image = self.image.resize(self.size, method)
|
self.image = self.image.resize(self.size, method)
|
||||||
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
||||||
if self.opt.format == 'CBZ' or self.opt.kfx:
|
pass
|
||||||
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)
|
|
||||||
else: # if image bigger than device resolution or smaller with upscaling
|
else: # if image bigger than device resolution or smaller with upscaling
|
||||||
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||||
elif self.opt.format == 'CBZ' or self.opt.kfx:
|
elif (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
|
||||||
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
||||||
else:
|
else:
|
||||||
self.image = ImageOps.contain(self.image, self.size, method=method)
|
self.image = ImageOps.contain(self.image, self.size, method=method)
|
||||||
|
|
||||||
def resize_method(self):
|
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]:
|
||||||
method = Image.Resampling.BICUBIC
|
return Image.Resampling.BICUBIC
|
||||||
else:
|
else:
|
||||||
method = Image.Resampling.LANCZOS
|
return Image.Resampling.LANCZOS
|
||||||
return method
|
|
||||||
|
|
||||||
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):
|
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])
|
box_area = (box[2] - box[0]) * (box[3] - box[1])
|
||||||
image_area = self.image.size[0] * self.image.size[1]
|
image_area = self.image.size[0] * self.image.size[1]
|
||||||
if (box_area / image_area) >= minimum:
|
if (box_area / image_area) >= minimum:
|
||||||
self.image = self.image.crop(box)
|
self.image = self.image.crop(box)
|
||||||
|
|
||||||
def cropPageNumber(self, power, minimum):
|
def cropPageNumber(self, power, minimum):
|
||||||
if self.fill != 'white':
|
bbox = get_bbox_crop_margin_page_number(self.image, power, self.fill)
|
||||||
tmptmg = self.image.convert(mode='L')
|
|
||||||
else:
|
if bbox:
|
||||||
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
w, h = self.image.size
|
||||||
tmptmg = tmptmg.point(lambda x: x and 255)
|
left, upper, right, lower = bbox
|
||||||
tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
|
# don't crop more than 10% of image
|
||||||
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
|
bbox = (min(0.1*w, left), min(0.1*h, upper), max(0.9*w, right), max(0.9*h, lower))
|
||||||
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
self.maybeCrop(bbox, minimum)
|
||||||
if tmptmg.getbbox():
|
|
||||||
self.maybeCrop(tmptmg.getbbox(), minimum)
|
|
||||||
|
|
||||||
def cropMargin(self, power, minimum):
|
def cropMargin(self, power, minimum):
|
||||||
if self.fill != 'white':
|
bbox = get_bbox_crop_margin(self.image, power, self.fill)
|
||||||
tmptmg = self.image.convert(mode='L')
|
|
||||||
else:
|
if bbox:
|
||||||
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
w, h = self.image.size
|
||||||
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
|
left, upper, right, lower = bbox
|
||||||
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
# don't crop more than 10% of image
|
||||||
if tmptmg.getbbox():
|
bbox = (min(0.1*w, left), min(0.1*h, upper), max(0.9*w, right), max(0.9*h, lower))
|
||||||
self.maybeCrop(self.getBoundingBox(tmptmg), minimum)
|
self.maybeCrop(bbox, minimum)
|
||||||
|
|
||||||
|
def cropInterPanelEmptySections(self, direction):
|
||||||
|
self.image = crop_empty_inter_panel(self.image, direction, background_color=self.fill)
|
||||||
|
|
||||||
class Cover:
|
class Cover:
|
||||||
def __init__(self, source, target, opt, tomeid):
|
def __init__(self, source, opt):
|
||||||
self.options = opt
|
self.options = opt
|
||||||
self.source = source
|
self.source = source
|
||||||
self.target = target
|
|
||||||
if tomeid == 0:
|
|
||||||
self.tomeid = 1
|
|
||||||
else:
|
|
||||||
self.tomeid = tomeid
|
|
||||||
self.image = Image.open(source)
|
self.image = Image.open(source)
|
||||||
# backwards compatibility for Pillow >9.1.0
|
# backwards compatibility for Pillow >9.1.0
|
||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
@@ -403,17 +560,49 @@ class Cover:
|
|||||||
self.image = ImageOps.autocontrast(self.image)
|
self.image = ImageOps.autocontrast(self.image)
|
||||||
if not self.options.forcecolor:
|
if not self.options.forcecolor:
|
||||||
self.image = self.image.convert('L')
|
self.image = self.image.convert('L')
|
||||||
self.image.thumbnail(self.options.profileData[1], Image.Resampling.LANCZOS)
|
self.crop_main_cover()
|
||||||
self.save()
|
|
||||||
|
|
||||||
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.34:
|
||||||
|
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:
|
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)
|
||||||
except IOError:
|
except IOError:
|
||||||
raise RuntimeError('Failed to save cover.')
|
raise RuntimeError('Failed to save cover.')
|
||||||
|
|
||||||
def saveToKindle(self, kindle, asin):
|
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:
|
try:
|
||||||
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
|
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
|
||||||
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=85)
|
'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_temp)
|
||||||
|
|
||||||
|
if background_color != 'white':
|
||||||
|
img_temp = ImageOps.invert(img_temp)
|
||||||
|
|
||||||
|
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 os.path
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
|
from . import image
|
||||||
|
|
||||||
|
|
||||||
class Kindle:
|
class Kindle:
|
||||||
def __init__(self):
|
def __init__(self, profile):
|
||||||
|
self.profile = profile
|
||||||
self.path = self.findDevice()
|
self.path = self.findDevice()
|
||||||
if self.path:
|
if self.path:
|
||||||
self.coverSupport = self.checkThumbnails()
|
self.coverSupport = self.checkThumbnails()
|
||||||
@@ -29,10 +32,11 @@ class Kindle:
|
|||||||
self.coverSupport = False
|
self.coverSupport = False
|
||||||
|
|
||||||
def findDevice(self):
|
def findDevice(self):
|
||||||
|
if self.profile in image.ProfileData.ProfilesKindlePDOC.keys():
|
||||||
|
return False
|
||||||
for drive in reversed(psutil.disk_partitions(False)):
|
for drive in reversed(psutil.disk_partitions(False)):
|
||||||
if (drive[2] == 'FAT32' and drive[3] == 'rw,removable') or \
|
if (drive[2] == 'FAT32' and drive[3] == 'rw,removable') or \
|
||||||
(drive[2] == 'vfat' and 'rw' in drive[3]) or \
|
(drive[2] in ('vfat', 'msdos', 'FAT', 'apfs') and 'rw' in drive[3]):
|
||||||
(drive[2] == 'msdos' and 'rw' in drive[3]):
|
|
||||||
if os.path.isdir(os.path.join(drive[1], 'system')) and \
|
if os.path.isdir(os.path.join(drive[1], 'system')) and \
|
||||||
os.path.isdir(os.path.join(drive[1], 'documents')):
|
os.path.isdir(os.path.join(drive[1], 'documents')):
|
||||||
return drive[1]
|
return drive[1]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import os
|
|||||||
from xml.dom.minidom import parse, Document
|
from xml.dom.minidom import parse, Document
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
|
from xml.sax.saxutils import unescape
|
||||||
from . import comicarchive
|
from . import comicarchive
|
||||||
|
|
||||||
|
|
||||||
@@ -52,19 +53,19 @@ class MetadataParser:
|
|||||||
|
|
||||||
def parseXML(self):
|
def parseXML(self):
|
||||||
if len(self.rawdata.getElementsByTagName('Series')) != 0:
|
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:
|
if len(self.rawdata.getElementsByTagName('Volume')) != 0:
|
||||||
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
|
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
|
||||||
if len(self.rawdata.getElementsByTagName('Number')) != 0:
|
if len(self.rawdata.getElementsByTagName('Number')) != 0:
|
||||||
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
|
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
|
||||||
if len(self.rawdata.getElementsByTagName('Summary')) != 0:
|
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:
|
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']:
|
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
|
||||||
if len(self.rawdata.getElementsByTagName(field)) != 0:
|
if len(self.rawdata.getElementsByTagName(field)) != 0:
|
||||||
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
|
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'] = list(set(self.data[field + 's']))
|
||||||
self.data[field + 's'].sort()
|
self.data[field + 's'].sort()
|
||||||
if len(self.rawdata.getElementsByTagName('Page')) != 0:
|
if len(self.rawdata.getElementsByTagName('Page')) != 0:
|
||||||
@@ -122,4 +123,4 @@ class MetadataParser:
|
|||||||
cbx.addFile(tmpXML)
|
cbx.addFile(tmpXML)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise UserWarning(e)
|
raise UserWarning(e)
|
||||||
rmtree(workdir)
|
rmtree(workdir, True)
|
||||||
|
|||||||
202
kindlecomicconverter/page_number_crop_alg.py
Normal file
202
kindlecomicconverter/page_number_crop_alg.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
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)
|
||||||
|
ignore_pixels_near_edge(bw_img)
|
||||||
|
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)
|
||||||
|
|
||||||
|
ignore_pixels_near_edge(bw_img)
|
||||||
|
|
||||||
|
return bw_img.getbbox()
|
||||||
|
|
||||||
|
def ignore_pixels_near_edge(bw_img):
|
||||||
|
w, h = bw_img.size
|
||||||
|
edge_bbox = [
|
||||||
|
(0, 0, w, int(0.02 * h)),
|
||||||
|
(0, int(0.98 * h), w, h),
|
||||||
|
(0, 0, int(0.02 * w), h),
|
||||||
|
(int(0.98 * w), 0, w, h)
|
||||||
|
]
|
||||||
|
for box in edge_bbox:
|
||||||
|
edge = bw_img.crop(box)
|
||||||
|
h = edge.histogram()
|
||||||
|
imperfections = h[255] / (edge.height * edge.width)
|
||||||
|
if imperfections > 0 and imperfections < .02:
|
||||||
|
bw_img.paste(im=0, box=box)
|
||||||
|
|
||||||
|
|
||||||
|
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,11 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from hashlib import md5
|
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from distutils.version import StrictVersion
|
import subprocess
|
||||||
|
from packaging.version import Version
|
||||||
from re import split
|
from re import split
|
||||||
|
import sys
|
||||||
from traceback import format_tb
|
from traceback import format_tb
|
||||||
|
|
||||||
|
|
||||||
@@ -44,11 +45,17 @@ class HTMLStripper(HTMLParser):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def dot_clean(filetree):
|
||||||
|
for root, _, files in os.walk(filetree, topdown=False):
|
||||||
|
for name in files:
|
||||||
|
if name.startswith('._') or name == '.DS_Store':
|
||||||
|
if os.path.exists(os.path.join(root, name)):
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
|
|
||||||
|
|
||||||
def getImageFileName(imgfile):
|
def getImageFileName(imgfile):
|
||||||
name, ext = os.path.splitext(imgfile)
|
name, ext = os.path.splitext(imgfile)
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
if (name.startswith('.') and len(name) == 1) or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp']:
|
|
||||||
return None
|
|
||||||
return [name, ext]
|
return [name, ext]
|
||||||
|
|
||||||
|
|
||||||
@@ -72,16 +79,6 @@ def walkLevel(some_dir, level=1):
|
|||||||
del dirs[:]
|
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):
|
def sanitizeTrace(traceback):
|
||||||
return ''.join(format_tb(traceback))\
|
return ''.join(format_tb(traceback))\
|
||||||
@@ -100,11 +97,11 @@ def dependencyCheck(level):
|
|||||||
missing = []
|
missing = []
|
||||||
if level > 2:
|
if level > 2:
|
||||||
try:
|
try:
|
||||||
from PyQt5.QtCore import qVersion as qtVersion
|
from PySide6.QtCore import qVersion as qtVersion
|
||||||
if StrictVersion('5.6.0') > StrictVersion(qtVersion()):
|
if Version('6.0.0') > Version(qtVersion()):
|
||||||
missing.append('PyQt 5.6.0+')
|
missing.append('PySide 6.0.0')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
missing.append('PyQt 5.6.0+')
|
missing.append('PySide 6.0.0+')
|
||||||
try:
|
try:
|
||||||
import raven
|
import raven
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -112,7 +109,7 @@ def dependencyCheck(level):
|
|||||||
if level > 1:
|
if level > 1:
|
||||||
try:
|
try:
|
||||||
from psutil import __version__ as psutilVersion
|
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+')
|
missing.append('psutil 5.0.0+')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
missing.append('psutil 5.0.0+')
|
missing.append('psutil 5.0.0+')
|
||||||
@@ -121,16 +118,27 @@ def dependencyCheck(level):
|
|||||||
from slugify import __version__ as slugifyVersion
|
from slugify import __version__ as slugifyVersion
|
||||||
if isinstance(slugifyVersion, ModuleType):
|
if isinstance(slugifyVersion, ModuleType):
|
||||||
slugifyVersion = slugifyVersion.__version__
|
slugifyVersion = slugifyVersion.__version__
|
||||||
if StrictVersion('1.2.1') > StrictVersion(slugifyVersion):
|
if Version('1.2.1') > Version(slugifyVersion):
|
||||||
missing.append('python-slugify 1.2.1+')
|
missing.append('python-slugify 1.2.1+')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
missing.append('python-slugify 1.2.1+')
|
missing.append('python-slugify 1.2.1+')
|
||||||
try:
|
try:
|
||||||
from PIL import __version__ as pillowVersion
|
from PIL import __version__ as pillowVersion
|
||||||
if StrictVersion('5.2.0') > StrictVersion(pillowVersion):
|
if Version('8.3.0') > Version(pillowVersion):
|
||||||
missing.append('Pillow 5.2.0+')
|
missing.append('Pillow 8.3.0+')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
missing.append('Pillow 5.2.0+')
|
missing.append('Pillow 8.3.0+')
|
||||||
|
try:
|
||||||
|
from pymupdf import __version__ as pymupdfVersion
|
||||||
|
if Version('1.16.1') > Version(pymupdfVersion):
|
||||||
|
missing.append('PyMuPDF 1.16.1+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('PyMuPDF 1.16.1+')
|
||||||
if len(missing) > 0:
|
if len(missing) > 0:
|
||||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
def subprocess_run(command, **kwargs):
|
||||||
|
if (os.name == 'nt'):
|
||||||
|
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
||||||
|
return subprocess.run(command, **kwargs)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,90 +0,0 @@
|
|||||||
7-Zip
|
|
||||||
~~~~~
|
|
||||||
License for use and distribution
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
7-Zip Copyright (C) 1999-2018 Igor Pavlov.
|
|
||||||
|
|
||||||
The licenses for files are:
|
|
||||||
|
|
||||||
1) 7z.dll:
|
|
||||||
- The "GNU LGPL" as main license for most of the code
|
|
||||||
- The "GNU LGPL" with "unRAR license restriction" for some code
|
|
||||||
- The "BSD 3-clause License" for some code
|
|
||||||
2) All other files: the "GNU LGPL".
|
|
||||||
|
|
||||||
Redistributions in binary form must reproduce related license information from this file.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
You can use 7-Zip on any computer, including a computer in a commercial
|
|
||||||
organization. You don't need to register or pay for 7-Zip.
|
|
||||||
|
|
||||||
|
|
||||||
GNU LGPL information
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You can receive a copy of the GNU Lesser General Public License from
|
|
||||||
http://www.gnu.org/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BSD 3-clause License
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression.
|
|
||||||
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
|
|
||||||
that also uses the "BSD 3-clause License":
|
|
||||||
|
|
||||||
----
|
|
||||||
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
|
|
||||||
from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
||||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
unRAR license restriction
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
The decompression engine for RAR archives was developed using source
|
|
||||||
code of unRAR program.
|
|
||||||
All copyrights to original unRAR code are owned by Alexander Roshal.
|
|
||||||
|
|
||||||
The license for original unRAR code has the following restriction:
|
|
||||||
|
|
||||||
The unRAR sources cannot be used to re-create the RAR compression algorithm,
|
|
||||||
which is proprietary. Distribution of modified unRAR sources in separate form
|
|
||||||
or as a part of other software is permitted, provided that it is clearly
|
|
||||||
stated in the documentation and source comments that the code may
|
|
||||||
not be used to develop a RAR (WinRAR) compatible archiver.
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
Igor Pavlov
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
{\rtf1\adeflang1025\ansi\ansicpg1250\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang1045\deflangfe1045\themelang1045\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
|
||||||
{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f37\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}
|
|
||||||
{\flomajor\f31500\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
|
||||||
{\fhimajor\f31502\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
|
||||||
{\flominor\f31504\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
|
||||||
{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f44\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
|
||||||
{\f43\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f45\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f46\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f47\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
|
||||||
{\f48\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f49\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f50\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f44\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
|
||||||
{\f43\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f45\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f46\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f47\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
|
||||||
{\f48\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f49\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f50\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f414\fbidi \fswiss\fcharset0\fprq2 Calibri;}
|
|
||||||
{\f413\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f415\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f416\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f417\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}
|
|
||||||
{\f418\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\f419\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f420\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
|
||||||
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
|
||||||
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
|
||||||
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
|
||||||
{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
|
||||||
{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
|
|
||||||
{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}
|
|
||||||
{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}
|
|
||||||
{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31540\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
|
||||||
{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
|
||||||
{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
|
||||||
{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31550\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
|
||||||
{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
|
||||||
{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
|
|
||||||
{\fdbminor\f31560\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
|
||||||
{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
|
||||||
{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31570\fbidi \fswiss\fcharset0\fprq2 Calibri;}
|
|
||||||
{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}
|
|
||||||
{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
|
|
||||||
{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31580\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
|
||||||
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
|
||||||
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}
|
|
||||||
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;
|
|
||||||
\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\chyperlink\ctint255\cshade255\red5\green99\blue193;\cfollowedhyperlink\ctint255\cshade255\red149\green79\blue114;}{\*\defchp
|
|
||||||
\f31506\fs22\lang1045\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1
|
|
||||||
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive
|
|
||||||
\ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
|
|
||||||
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
|
|
||||||
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive
|
|
||||||
\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf17 \sbasedon10 \sunhideused \styrsid3562894 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf18 \sbasedon10 \ssemihidden \sunhideused \styrsid7678248 FollowedHyperlink;}}{\*\rsidtbl \rsid1081196
|
|
||||||
\rsid3146412\rsid3562894\rsid5731975\rsid7678248\rsid9265883\rsid11107340\rsid11629590\rsid12600926\rsid13187577}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info
|
|
||||||
{\author Pawe\'b3 Jastrz\'eabski}{\operator Pawe\'b3 Jastrz\'eabski}{\creatim\yr2013\mo10\dy29\hr15\min17}{\revtim\yr2017\mo8\dy20\hr17\min40}{\version9}{\edmins8}{\nofpages1}{\nofwords33}{\nofchars201}{\nofcharsws233}{\vern39}}{\*\xmlnstbl {\xmlns1 http:
|
|
||||||
//schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect
|
|
||||||
\deftab708\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0
|
|
||||||
\showxmlerrors1\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1
|
|
||||||
\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct
|
|
||||||
\asianbrkrule\rsidroot11107340\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0
|
|
||||||
{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2
|
|
||||||
\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6
|
|
||||||
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang
|
|
||||||
{\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0
|
|
||||||
\f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\fs52\cf6\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid3562894 Warning!}{\rtlch\fcs1 \af0 \ltrch\fcs0
|
|
||||||
\b\fs52\cf6\lang2057\langfe1033\langnp2057\insrsid13187577\charrsid3562894
|
|
||||||
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid1081196 Creation of}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 MOBI}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 files }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 require}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid11629590 s}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 additional software.}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
|
|
||||||
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 Please download: }{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 HYPERLINK "http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211" }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
|
|
||||||
{\*\datafield
|
|
||||||
00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b9600000068007400740070003a002f002f007700770077002e0061006d0061007a006f006e002e0063006f006d002f00670070002f0066006500610074007500720065002e00680074006d006c003f00690065003d00
|
|
||||||
5500540046003800260064006f006300490064003d0031003000300030003700360035003200310031000000795881f43b1d7f48af2c825dc485276300000000a5ab00000000}}}{\fldrslt {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\cs15\b\fs28\ul\cf17\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 KindleGen}}}\sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
|
|
||||||
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 And place }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \i\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 kindlegen.exe}{\rtlch\fcs1
|
|
||||||
\af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 inside }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412\charrsid3146412 Kindle}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412\charrsid3146412 Comic}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412\charrsid3146412 Converter}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
|
|
||||||
\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 directory.
|
|
||||||
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
|
|
||||||
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
|
|
||||||
5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
|
|
||||||
b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0
|
|
||||||
0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6
|
|
||||||
a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f
|
|
||||||
c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512
|
|
||||||
0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462
|
|
||||||
a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865
|
|
||||||
6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b
|
|
||||||
4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b
|
|
||||||
4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b7e72e45da060000a81a0000160000007468656d652f7468656d652f
|
|
||||||
7468656d65312e786d6cec595d8b1b37147d2ff43f0cf3eef86bc61f4bbcc11edbd936bbc9123b2979d4dab24759cdc88ce4dd981028c963a1509a963e34d0b7
|
|
||||||
3e94b68104fa92fe9a6d53da14f2177aa5198f255bdb4d96149692352c63f9dcaba37bef9cab195dbe722fa2ce114e386171cb2d5f2ab90e8e476c4ce269cbbd
|
|
||||||
35ec171aaec3058ac788b218b7dc05e6ee95ed0f3fb88cb6448823ec807dccb750cb0d85986d158b7c04c3885f62331cc36f13964448c0d7645a1c27e818fc46
|
|
||||||
b45829956ac50891d875621481db3d2616c7ce0c1d122ce6ce8dc9848cb0bbbd9ca44761a658703930a2c9404e8133cb7d65a39b8c0fcb12c8173ca089738468
|
|
||||||
cb8569c7ec7888ef09d7a1880bf8a1e596d49f5bdcbe5c445b991115a7d86a767df597d96506e3c38a9a33991ee4937a9eefd5dab97f05a06213d7abf76abd5a
|
|
||||||
ee4f01d068040b4eb9e83efd4eb3d3f533ac064a2f2dbebbf56eb56ce035ffd50dce6d5f7e0cbc02a5febd0d7cbf1f40140dbc02a5787f03ef79f54ae0197805
|
|
||||||
4af1b50d7cbdd4ee7a7503af402125f1e106bae4d7aac172b53964c2e88e15def4bd7ebd92395fa1a01af22293534c582cce28b908dd65491f70124f9120b123
|
|
||||||
16333c4123a8ed005172901067974c43a8bf198a1987e152a5d42f55e1bffc78ea4a05066d61a4594b7a40886f0c495a0e1f2564265aeec7e0d5d520af5ffcf8
|
|
||||||
fac533e7e4e1f39387bf9c3c7a74f2f0e7d49161b583e2a96ef5eafb2ffe7ef2a9f3d7b3ef5e3dfeca8ee73afef79f3efbedd72fed4058e92a042fbf7efac7f3
|
|
||||||
a72fbff9fccf1f1e5be0ed041de8f021893077aee363e7268b60612a0426737c90bc9dc5304444b768c7538e622467b1f8ef89d0405f5f208a2cb80e3623783b
|
|
||||||
01a5b101afceef1a8407613217c4e2f15a1819c03dc6688725d6285c937369611ecee3a97df264aee36e2274649b3b40b191dfde7c064a4b6c2e83101b34f729
|
|
||||||
8a059ae2180b47fec60e31b6acee0e21465cf7c828619c4d847387381d44ac21199203a39a56463b2482bc2c6c0421df466cf66e3b1d466dabeee22313097705
|
|
||||||
a216f2434c8d305e457381229bcb218aa81ef05d24421bc9c12219e9b81e1790e929a6cce98d31e7369b1b09ac574bfa3590177bdaf7e8223291892087369fbb
|
|
||||||
88311dd965874188a2990d3b2071a8633fe28750a2c8d967c206df63e61d22bf431e507c6aba6fc31e409fe06c35b805caaa5bac0a44fe324f2cb9bc8a9951bf
|
|
||||||
83059d20aca406f4dfd0f388c4678afb9aacfbffadac8390befcf68965551755d0db09b1de513b6b327e1a6e5dbc03968cc9c5d7ee2e9ac7fb186e97cd06f65e
|
|
||||||
badf4bb7fbbf97eed3eee7772fd82b8d06f9965bc574c7aef6efd159dbf709a174201614ef72b583e7d0a0c67d1894e6ea8116e74f75b3102ee50d0df318b869
|
|
||||||
82948d9330f10911e1204433d8e6975de964ca33d753eecc1887ddbf1ab6fa96783a8ff6d8387d782d97e5836aaa211c89d578c9cfc7e18943a4e85a7df54096
|
|
||||||
bb576ca7eaf9794940dabe0d096d329344d542a2be1c9441524feb10340b09b5b277c2a26961d190ee97a9da6001d4f2acc00eca817d57cbf53d30012378b042
|
|
||||||
148f659ed2542fb3ab92f92e337d5a308d0a80edc4b20256996e4aaea72e4fae2e2db537c8b441422b3793848a8c6a653c44639c55a71c7d131a6f9bebe62aa5
|
|
||||||
063d190a351f94d68a46bdf16f2cce9b6bb05bd7061aeb4a4163e7b8e5d6aa3e94cc08cd5aee049efee1329a41ed70b9f345740a2fd64622496ff8f328cb2ce1
|
|
||||||
a28b7898065c894eaa061111387128895aae5c7e9e061a2b0d51dcca1510840b4bae09b272d1c841d2cd24e3c9048f849e766d44463afd0a0a9f6a85f557657e
|
|
||||||
7eb0b4647348f7201c1f3b07749edc4450627ebd2c0338261c5e0295d3688e09bcdccc856c557f6b8d29935dfdeda2aaa1741cd15988b28ea28b790a57529ed3
|
|
||||||
51dff21868dfb2354340b590648df0602a1bac1e54a39be65d23e5706ad73ddb48464e13cd55cf345445764dbb8a19332cdbc05a2ccfd7e43556cb1083a6e91d
|
|
||||||
3e95ee75c96d2eb56e6d9f90770908781e3f4bd77d8386a0515b4d6650938c3765586a76366af68ee502cfa0f6264d4253fddad2ed5adcf21e619d0e06cfd5f9
|
|
||||||
c16ebd6a6168b2dc5eaa48ab4311fdbc821ddc05f1e8c2bbe039155ca5128e2112041ba281da93a4b201b7c83d91dd1a70e5cc13d272ef97fcb61754fca0506a
|
|
||||||
f8bd8257f54a8586dfae16dabe5f2df7fc72a9dba93c80c622c2a8eca707327d78234517d9b18c1adf389a89962fdd2e8d585464eab0a5a888aba39972c5389a
|
|
||||||
490f639ca13c73711d02a273bf56e937abcd4eadd0acb6fb05afdb69149a41ad53e8d6827ab7df0dfc46b3ffc0758e14d86b5703afd66b146ae5202878b592a4
|
|
||||||
df6816ea5ea5d2f6eaed46cf6b3fc8b631b0f2543eb258407815afed7f000000ffff0300504b0304140006000800000021000dd1909fb60000001b0100002700
|
|
||||||
00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0ad
|
|
||||||
d40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b
|
|
||||||
284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f16
|
|
||||||
5dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c0200001300000000
|
|
||||||
000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b000000
|
|
||||||
00000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000
|
|
||||||
000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b7e72e45da060000a81a000016
|
|
||||||
00000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b
|
|
||||||
0100002700000000000000000000000000e40900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000df0a00000000}
|
|
||||||
{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d
|
|
||||||
617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
|
|
||||||
6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
|
|
||||||
656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
|
|
||||||
{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;
|
|
||||||
\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;
|
|
||||||
\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
|
|
||||||
\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;
|
|
||||||
\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;
|
|
||||||
\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;
|
|
||||||
\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;
|
|
||||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;
|
|
||||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;
|
|
||||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;
|
|
||||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;
|
|
||||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;
|
|
||||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;
|
|
||||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;
|
|
||||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;
|
|
||||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;
|
|
||||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;
|
|
||||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5;
|
|
||||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;
|
|
||||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;
|
|
||||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6;
|
|
||||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;
|
|
||||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;
|
|
||||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;
|
|
||||||
\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4;
|
|
||||||
\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4;
|
|
||||||
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1;
|
|
||||||
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1;
|
|
||||||
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2;
|
|
||||||
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2;
|
|
||||||
\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3;
|
|
||||||
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4;
|
|
||||||
\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4;
|
|
||||||
\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5;
|
|
||||||
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5;
|
|
||||||
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6;
|
|
||||||
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6;
|
|
||||||
\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark;
|
|
||||||
\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1;
|
|
||||||
\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1;
|
|
||||||
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2;
|
|
||||||
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3;
|
|
||||||
\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3;
|
|
||||||
\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4;
|
|
||||||
\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4;
|
|
||||||
\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5;
|
|
||||||
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
|
|
||||||
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
|
|
||||||
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
|
|
||||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000
|
|
||||||
4d73786d6c322e534158584d4c5265616465722e362e30000000000000000000000e0000
|
|
||||||
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000000200000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
fffffffffffffffffdffffff04000000feffffff05000000fefffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff010000000c6ad98892f1d411a65f0040963251e5000000000000000000000000b00f
|
|
||||||
9da8ca19d30103000000c0020000000000004d0073006f004400610074006100530074006f0072006500000000000000000000000000000000000000000000000000000000000000000000000000000000001a000101ffffffffffffffff020000000000000000000000000000000000000000000000b00f9da8ca19d301
|
|
||||||
b00f9da8ca19d301000000000000000000000000ca0041004300c300d300d300c70058004d00d4003000c9004d00c200590043003100320055004a00300051003d003d000000000000000000000000000000000032000101ffffffffffffffff030000000000000000000000000000000000000000000000b00f9da8ca19
|
|
||||||
d301b00f9da8ca19d3010000000000000000000000004900740065006d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000
|
|
||||||
00000000000000000000000000000000320100000000000001000000020000000300000004000000feffffff060000000700000008000000090000000a000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
||||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e3c623a536f75726365732053656c65637465645374796c653d225c41504153697874684564697469
|
|
||||||
6f6e4f66666963654f6e6c696e652e78736c22205374796c654e616d653d22415041222056657273696f6e3d22362220786d6c6e733a623d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222078
|
|
||||||
6d6c6e733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879223e3c2f623a536f75726365733e00000000000000000000000000003c3f786d6c2076657273696f6e3d22312e302220656e636f6469
|
|
||||||
6e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e0d0a3c64733a6461746173746f72654974656d2064733a6974656d49443d227b43464133303041382d443733392d343633332d413933322d3236303236444335303936397d2220786d6c6e733a64733d22687474703a2f2f736368656d61732e6f70
|
|
||||||
656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f637573500072006f007000650072007400690065007300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000200ffffffffffffffffffffffff000000000000
|
|
||||||
0000000000000000000000000000000000000000000000000000000000000500000055010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000
|
|
||||||
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000
|
|
||||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
|
|
||||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746f6d586d6c223e3c64733a736368656d61526566733e3c64733a736368656d615265662064733a7572693d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f7267
|
|
||||||
2f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222f3e3c2f64733a736368656d61526566733e3c2f64733a6461746173746f72654974656d3e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
||||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
||||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
||||||
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105000000000000}}
|
|
||||||
12
requirements-osx-legacy.txt
Normal file
12
requirements-osx-legacy.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
PySide6==6.5.2
|
||||||
|
Pillow>=11.3.0
|
||||||
|
psutil>=5.9.5
|
||||||
|
requests>=2.31.0
|
||||||
|
python-slugify>=1.2.1
|
||||||
|
raven>=6.0.0
|
||||||
|
packaging>=23.2
|
||||||
|
mozjpeg-lossless-optimization>=1.2.0
|
||||||
|
natsort>=8.4.0
|
||||||
|
distro>=1.8.0
|
||||||
|
numpy<2
|
||||||
|
PyMuPDF>=1.26.1
|
||||||
12
requirements-win7.txt
Normal file
12
requirements-win7.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
PySide6==6.1.3
|
||||||
|
Pillow>=9
|
||||||
|
psutil>=5.9.5
|
||||||
|
requests>=2.31.0
|
||||||
|
python-slugify>=1.2.1
|
||||||
|
raven>=6.0.0
|
||||||
|
packaging>=23.2
|
||||||
|
mozjpeg-lossless-optimization>=1.2.0
|
||||||
|
natsort>=8.4.0
|
||||||
|
distro>=1.8.0
|
||||||
|
numpy==1.23.0
|
||||||
|
PyMuPDF>=1.16
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
PyQt5>=5.6.0
|
PySide6<6.10
|
||||||
Pillow>=5.2.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.0.0
|
psutil>=5.9.5
|
||||||
|
requests>=2.31.0
|
||||||
python-slugify>=1.2.1
|
python-slugify>=1.2.1
|
||||||
raven>=6.0.0
|
raven>=6.0.0
|
||||||
# PyQt5-tools
|
packaging>=23.2
|
||||||
mozjpeg-lossless-optimization>=1.1.2
|
mozjpeg-lossless-optimization>=1.2.0
|
||||||
distro
|
natsort>=8.4.0
|
||||||
|
distro>=1.8.0
|
||||||
|
numpy>=1.22.4
|
||||||
|
PyMuPDF>=1.18.0
|
||||||
|
|||||||
40
setup.py
40
setup.py
@@ -11,10 +11,9 @@ Create EXE/APP:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
|
||||||
import setuptools
|
import setuptools
|
||||||
import distutils.cmd
|
|
||||||
from kindlecomicconverter import __version__
|
from kindlecomicconverter import __version__
|
||||||
|
|
||||||
NAME = 'KindleComicConverter'
|
NAME = 'KindleComicConverter'
|
||||||
@@ -23,7 +22,7 @@ VERSION = __version__
|
|||||||
|
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
class BuildBinaryCommand(distutils.cmd.Command):
|
class BuildBinaryCommand(setuptools.Command):
|
||||||
description = 'build binary release'
|
description = 'build binary release'
|
||||||
user_options = []
|
user_options = []
|
||||||
|
|
||||||
@@ -37,19 +36,26 @@ class BuildBinaryCommand(distutils.cmd.Command):
|
|||||||
def run(self):
|
def run(self):
|
||||||
VERSION = __version__
|
VERSION = __version__
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
os.system('pyinstaller -y -F -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
|
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||||
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
|
min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET')
|
||||||
exit(0)
|
if min_os:
|
||||||
|
os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg')
|
||||||
|
else:
|
||||||
|
os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg')
|
||||||
|
sys.exit(0)
|
||||||
elif sys.platform == 'win32':
|
elif sys.platform == 'win32':
|
||||||
os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py')
|
if os.getenv('WINDOWS_7'):
|
||||||
exit(0)
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_win7_legacy_' + VERSION + ' -w --noupx kcc.py')
|
||||||
|
else:
|
||||||
|
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':
|
elif sys.platform == 'linux':
|
||||||
os.system(
|
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')
|
||||||
exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
@@ -75,12 +81,18 @@ setuptools.setup(
|
|||||||
},
|
},
|
||||||
packages=['kindlecomicconverter'],
|
packages=['kindlecomicconverter'],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'PyQt5>=5.6.0',
|
'pyside6>=6.0.0',
|
||||||
'Pillow>=5.2.0',
|
'Pillow>=9.3.0',
|
||||||
'psutil>=5.0.0',
|
'PyMuPDF>=1.18.0',
|
||||||
|
'psutil>=5.9.5',
|
||||||
'python-slugify>=1.2.1,<9.0.0',
|
'python-slugify>=1.2.1,<9.0.0',
|
||||||
'raven>=6.0.0',
|
'raven>=6.0.0',
|
||||||
|
'requests>=2.31.0',
|
||||||
'mozjpeg-lossless-optimization>=1.1.2',
|
'mozjpeg-lossless-optimization>=1.1.2',
|
||||||
|
'natsort>=8.4.0',
|
||||||
|
'distro',
|
||||||
|
'numpy>=1.22.4',
|
||||||
|
'PyMuPDF>=1.16.1',
|
||||||
],
|
],
|
||||||
classifiers=[],
|
classifiers=[],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|||||||
Reference in New Issue
Block a user