Compare commits
813 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
515b83637f | ||
|
|
c3dad087d3 | ||
|
|
67e913ed9e | ||
|
|
6ce0f76fe0 | ||
|
|
5f5157c1d4 | ||
|
|
4c13ef0f6c | ||
|
|
50dc7fbffe | ||
|
|
a060498ac7 | ||
|
|
35bba68a72 | ||
|
|
0b056a8fa8 | ||
|
|
a6f9e84251 | ||
|
|
259800e48b | ||
|
|
28e170f1d2 | ||
|
|
7120c76025 | ||
|
|
4891913b5c | ||
|
|
cd83b2899c | ||
|
|
535c2c220b | ||
|
|
409f077c3e | ||
|
|
3ecb2ba877 | ||
|
|
c07a9657ef | ||
|
|
7f719a22ad | ||
|
|
332d3d455e | ||
|
|
2070a977ae | ||
|
|
8f8d0d68a3 | ||
|
|
5a8deb4623 | ||
|
|
a7ea795df5 | ||
|
|
a2ffd259b8 | ||
|
|
93e6b51466 | ||
|
|
7904662f25 | ||
|
|
6792c2d366 | ||
|
|
ef4a91e44d | ||
|
|
a2a405e5f5 | ||
|
|
a63a46a741 | ||
|
|
2591b53a09 | ||
|
|
1c615ffc20 | ||
|
|
6eb05b3a8f | ||
|
|
968b083fb2 | ||
|
|
205907ef1e | ||
|
|
33ef8275c3 | ||
|
|
eec2099515 | ||
|
|
9027265b7c | ||
|
|
34e2af3389 | ||
|
|
5ecddaceab | ||
|
|
fe554c20aa | ||
|
|
6acf1a1802 | ||
|
|
ac81a6be4b | ||
|
|
829a5f25e7 | ||
|
|
a695a4c151 | ||
|
|
7c0b78350d | ||
|
|
aa978ab246 | ||
|
|
7524c50657 | ||
|
|
9d8663a925 | ||
|
|
08ed304f8e | ||
|
|
658d2f3281 | ||
|
|
f44bf5993e | ||
|
|
458c28281f | ||
|
|
968a1afa1d | ||
|
|
2c875673bd | ||
|
|
1a0be83d63 | ||
|
|
36a7dc49ec | ||
|
|
40d1ae63da | ||
|
|
3d3621c6ec | ||
|
|
d77f04a84e | ||
|
|
ec51d6fc51 | ||
|
|
b7861d9d9e | ||
|
|
0b4503af21 | ||
|
|
437ffb9b10 | ||
|
|
066d1401bd | ||
|
|
9babe68d2a | ||
|
|
67de77538c | ||
|
|
c3e950f2ec | ||
|
|
ac2934aba2 | ||
|
|
a5064a0c0a | ||
|
|
2d712e796d | ||
|
|
cc3da40fd7 | ||
|
|
a53c272bd0 | ||
|
|
6526b139fd | ||
|
|
283d6101cd | ||
|
|
02dab3c6ee | ||
|
|
1895aa127d | ||
|
|
c01ff83fce | ||
|
|
4b670f3754 | ||
|
|
23b1560fa2 | ||
|
|
62350608dc | ||
|
|
8048b91fa8 | ||
|
|
2e9b3389e4 | ||
|
|
40e1ab4cf3 | ||
|
|
d2c12c89e6 | ||
|
|
4647fd1f1d | ||
|
|
010ad3c88c | ||
|
|
4b0a94a8a0 | ||
|
|
807a2d1dff | ||
|
|
e1470cca15 | ||
|
|
02b9081e37 | ||
|
|
495db88a9e | ||
|
|
2bea546a9d | ||
|
|
ee042ef98d | ||
|
|
aea7c0fafb | ||
|
|
45c1afcad4 | ||
|
|
b8e314f6ca | ||
|
|
d76eea9f43 | ||
|
|
2e55f22355 | ||
|
|
30b8770e34 | ||
|
|
9ad161489f | ||
|
|
bdb459cfab | ||
|
|
2e85556543 | ||
|
|
93ebbbd0af | ||
|
|
dd5c907bad | ||
|
|
64fb4a9eca | ||
|
|
284c577894 | ||
|
|
c68c9892e4 | ||
|
|
aa00ea3aa2 | ||
|
|
88f005824c | ||
|
|
2a2bfae112 | ||
|
|
583eec787f | ||
|
|
9ce691aecb | ||
|
|
d1a07d7ffa | ||
|
|
b545f7ad48 | ||
|
|
9e01797d28 | ||
|
|
c68c5f25bf | ||
|
|
a04bf5262f | ||
|
|
b09b2527d9 | ||
|
|
94b372f47d | ||
|
|
b978adcc7c | ||
|
|
9dee4432ad | ||
|
|
15055c6c0c | ||
|
|
3f948a10b0 | ||
|
|
1c942d81db | ||
|
|
b36a5a0a93 | ||
|
|
2ca07430a0 | ||
|
|
3132aa8a21 | ||
|
|
e4dccfe603 | ||
|
|
4c56141b80 | ||
|
|
73c2e4b136 | ||
|
|
c28e9a6ef0 | ||
|
|
558bf07f7f | ||
|
|
eaa458a9c7 | ||
|
|
91b6869638 | ||
|
|
25331f5d81 | ||
|
|
9b25182393 | ||
|
|
189c03529a | ||
|
|
c9cf635229 | ||
|
|
f78dc3cd8f | ||
|
|
4079314b61 | ||
|
|
04cf732d50 | ||
|
|
9015614b1a | ||
|
|
e817c8b258 | ||
|
|
cb9059c231 | ||
|
|
75d8be0951 | ||
|
|
161abdab18 | ||
|
|
4b1c7b3124 | ||
|
|
5481c0cfe5 | ||
|
|
9543b573e3 | ||
|
|
df0bafe4b6 | ||
|
|
b2e58127cb | ||
|
|
21174338ff | ||
|
|
241801f9cb | ||
|
|
a2086618a2 | ||
|
|
7d81de6834 | ||
|
|
7305ccffc5 | ||
|
|
72b5027021 | ||
|
|
1152655061 | ||
|
|
f25c25a121 | ||
|
|
fff7eeca2b | ||
|
|
a93da2136b | ||
|
|
dc3498b74c | ||
|
|
f317a5c430 | ||
|
|
3bedc3b928 | ||
|
|
a2651747cd | ||
|
|
af2c4e7250 | ||
|
|
f93ced8939 | ||
|
|
8a0ba682c3 | ||
|
|
1a7b6baaf3 | ||
|
|
0f8daaf9f3 | ||
|
|
1fa6d315b1 | ||
|
|
112917754a | ||
|
|
65774c6f12 | ||
|
|
7b3ce8827f | ||
|
|
b12825045b | ||
|
|
ac9f3a5d87 | ||
|
|
00969a3739 | ||
|
|
21f738b44a | ||
|
|
f2238b16a6 | ||
|
|
14f677ec68 | ||
|
|
7b3bf4618f | ||
|
|
eab63a0f74 | ||
|
|
2128104db7 | ||
|
|
c6179b0064 | ||
|
|
1d4319be2e | ||
|
|
f5a738e2d4 | ||
|
|
477d834a91 | ||
|
|
c8698f6d99 | ||
|
|
0988601842 | ||
|
|
57e9637c81 | ||
|
|
a7440e06a9 | ||
|
|
a9ed1e7610 | ||
|
|
b1bc140ad3 | ||
|
|
9014ed53d4 | ||
|
|
cad05904f3 | ||
|
|
10386d8af3 | ||
|
|
c991feb9ce | ||
|
|
d26eb7cdcd | ||
|
|
351084b703 | ||
|
|
e861e7f6e8 | ||
|
|
370c9d4df7 | ||
|
|
8e5704683c | ||
|
|
c65e1c8dea | ||
|
|
677622c103 | ||
|
|
af0ebb85a0 | ||
|
|
8af029ac92 | ||
|
|
a268e12a90 | ||
|
|
d621335e6c | ||
|
|
ec1d9c2d93 | ||
|
|
85b9dbbf83 | ||
|
|
feeced44bf | ||
|
|
cbea18398b | ||
|
|
4c9857f14d | ||
|
|
6b58ef4557 | ||
|
|
24d697c965 | ||
|
|
8b07d4eb69 | ||
|
|
e6c5ac915f | ||
|
|
b22e4757a3 | ||
|
|
91b06016bb | ||
|
|
5631391245 | ||
|
|
c33887b7b7 | ||
|
|
8d82f58f09 | ||
|
|
36985f5169 | ||
|
|
9d190c1585 | ||
|
|
3834850317 | ||
|
|
84fc23b979 | ||
|
|
77748afdbd | ||
|
|
431e2ffaf2 | ||
|
|
16df4cd083 | ||
|
|
1aa34347c1 | ||
|
|
561af90b06 | ||
|
|
00d239e1d8 | ||
|
|
26bd2d3ed0 | ||
|
|
e7aa49b70c | ||
|
|
da41edc2f1 | ||
|
|
ecbf60fb28 | ||
|
|
57b571b6c2 | ||
|
|
44bdc0245b | ||
|
|
1ec07fe4ec | ||
|
|
5f8f7e0919 | ||
|
|
f404b9090d | ||
|
|
68521f7c63 | ||
|
|
f5dd813c4c | ||
|
|
7924c492b3 | ||
|
|
2fc21c33e2 | ||
|
|
cb76504acb | ||
|
|
db6b0eddfe | ||
|
|
7d529a2acc | ||
|
|
ad3ff35aaa | ||
|
|
c62eeeb712 | ||
|
|
5a36a13105 | ||
|
|
12684d6562 | ||
|
|
c5f68ae12a | ||
|
|
7bd9c766cc | ||
|
|
c6b1417d9c | ||
|
|
98bf28a713 | ||
|
|
f2d6d5b458 | ||
|
|
5de492ffb6 | ||
|
|
5c2c6ed825 | ||
|
|
c2730ab01c | ||
|
|
bfba66d47d | ||
|
|
b5bc2f8e00 | ||
|
|
917eaef548 | ||
|
|
3187ebb054 | ||
|
|
b9276e9ede | ||
|
|
147d815057 | ||
|
|
180123fee2 | ||
|
|
2768e622f2 | ||
|
|
b629b45d46 | ||
|
|
f66c83425c | ||
|
|
68b4b7114d | ||
|
|
36f8c82eaf | ||
|
|
bd665c3261 | ||
|
|
94586fa590 | ||
|
|
89806dfcfc | ||
|
|
c9eb73ab90 | ||
|
|
2edcc0369a | ||
|
|
bc0a52b848 | ||
|
|
5ae72bf06d | ||
|
|
24c32643c1 | ||
|
|
40b988f964 | ||
|
|
ac794eff85 | ||
|
|
eaa387a9d6 | ||
|
|
d0f5d6dac4 | ||
|
|
b174534c1c | ||
|
|
19d486a4ee | ||
|
|
652762b709 | ||
|
|
e6df87f8fd | ||
|
|
1a9cd0beb5 | ||
|
|
108e351126 | ||
|
|
dfe3e10470 | ||
|
|
787ad9fa66 | ||
|
|
f10e8869a9 | ||
|
|
715ada328f | ||
|
|
56f23ab488 | ||
|
|
996af59e00 | ||
|
|
37aa84c4aa | ||
|
|
50574632e6 | ||
|
|
0afb9e8c0b | ||
|
|
7511c7eed6 | ||
|
|
836a4146f9 | ||
|
|
15a240ccea | ||
|
|
0722ddf8b0 | ||
|
|
b3159b94e7 | ||
|
|
ef5207c990 | ||
|
|
db77d89817 | ||
|
|
4571fadadb | ||
|
|
94f56238ae | ||
|
|
5efb5d6dbb | ||
|
|
623f615dd9 | ||
|
|
39fbbc42b3 | ||
|
|
99405ab8a6 | ||
|
|
aadfca8306 | ||
|
|
5450502c2a | ||
|
|
c976b06413 | ||
|
|
b6facda95b | ||
|
|
3f608eb602 | ||
|
|
104cd04994 | ||
|
|
b323204628 | ||
|
|
56195d301d | ||
|
|
287723ca6f | ||
|
|
90490149c7 | ||
|
|
2210f484df | ||
|
|
d5502e85b0 | ||
|
|
59b26cfc8b | ||
|
|
a722e5fa49 | ||
|
|
fce3072dca | ||
|
|
f53cdf9cd7 | ||
|
|
de74318c69 | ||
|
|
b137e69510 | ||
|
|
888663fa4c | ||
|
|
8a2ba96ac5 | ||
|
|
cb6b0e0a7b | ||
|
|
49d2a7fbab | ||
|
|
3d84b27d58 | ||
|
|
e98a23d0c0 | ||
|
|
7f80dacea8 | ||
|
|
6efb3dcef3 | ||
|
|
942a828b7e | ||
|
|
df5ee1badf | ||
|
|
e3ab28642d | ||
|
|
a1831c45d5 | ||
|
|
52bea9957a | ||
|
|
a71339ec34 | ||
|
|
c5f09c44b8 | ||
|
|
64b199ef74 | ||
|
|
8dd0b0e694 | ||
|
|
181a2e8ab4 | ||
|
|
3fdff845b7 | ||
|
|
2ee5dc310b | ||
|
|
f32e9560b5 | ||
|
|
621827c1c2 | ||
|
|
dc312f36c2 | ||
|
|
4573ff6ec2 | ||
|
|
d77498405b | ||
|
|
e491fca445 | ||
|
|
d22ee1a488 | ||
|
|
7ebcccd8a2 | ||
|
|
9a691c3c63 | ||
|
|
2b04a0298e | ||
|
|
9867f63d00 | ||
|
|
866f8898be | ||
|
|
9f2ac7a176 | ||
|
|
634213f380 | ||
|
|
ce82b5ec66 | ||
|
|
062b239f2f | ||
|
|
f5f5c05f1e | ||
|
|
a229ba44bf | ||
|
|
27e5cc3b00 | ||
|
|
63d752280a | ||
|
|
60b7a90589 | ||
|
|
63413fa4ba | ||
|
|
3a536df626 | ||
|
|
35bc4a2987 | ||
|
|
3bb8cc7778 | ||
|
|
bd53c6108d | ||
|
|
3d8bcb4020 | ||
|
|
162c146bed | ||
|
|
e4750fc965 | ||
|
|
ebe7d910de | ||
|
|
aa96381eb5 | ||
|
|
a2b9b5aa8b | ||
|
|
1e5bfc9f66 | ||
|
|
25a68ebdea | ||
|
|
786d2a9e1f | ||
|
|
4133cd21ba | ||
|
|
237ef728e1 | ||
|
|
991bf5d4a9 | ||
|
|
aa8b78b4e4 | ||
|
|
c2e6a0b791 | ||
|
|
42cf9e099f | ||
|
|
d99064596a | ||
|
|
b1e7a88353 | ||
|
|
f8610e1cd7 | ||
|
|
562dad02fa | ||
|
|
66b867c1ca | ||
|
|
b46ada9596 | ||
|
|
aaa2de81fc | ||
|
|
b0c6315fee | ||
|
|
509d78c0a6 | ||
|
|
65ef77bace | ||
|
|
170064853c | ||
|
|
c7e9d883fa | ||
|
|
a8521dbc9a | ||
|
|
0f44629273 | ||
|
|
f12361e7cf | ||
|
|
450076e6e8 | ||
|
|
6d445ae151 | ||
|
|
921511dcf2 | ||
|
|
d76a624a82 | ||
|
|
cf3df581e1 | ||
|
|
87009f27a6 | ||
|
|
cccbd36463 | ||
|
|
19b438844d | ||
|
|
878e92b527 | ||
|
|
116dce09fd | ||
|
|
3af30faee9 | ||
|
|
cf0b6b3484 | ||
|
|
96a8c1d354 | ||
|
|
20712b6c42 | ||
|
|
84d836bf0e | ||
|
|
d944d6385e | ||
|
|
a38013eabc | ||
|
|
def9e42a61 | ||
|
|
34aaeab8b1 | ||
|
|
eaff6cc633 | ||
|
|
54f48d2156 | ||
|
|
42d845cf07 | ||
|
|
e5e53d3aa7 | ||
|
|
f952634971 | ||
|
|
19ff6a51cc | ||
|
|
8fbe558f65 | ||
|
|
6bdb0ab942 | ||
|
|
7656a85708 | ||
|
|
d016bade8e | ||
|
|
3cc99c6221 | ||
|
|
93f5d105cf | ||
|
|
c1c44bdf88 | ||
|
|
72132ea908 | ||
|
|
29f901f92a | ||
|
|
22b7258aa3 | ||
|
|
c0f788bd67 | ||
|
|
8f10e93c08 | ||
|
|
4a473e3716 | ||
|
|
89d31cb8e5 | ||
|
|
80b65b12b7 | ||
|
|
e835502837 | ||
|
|
5bcdc78725 | ||
|
|
acb4dfad8f | ||
|
|
7f5de29174 | ||
|
|
11007402cd | ||
|
|
0cf92fc48f | ||
|
|
953942ca00 | ||
|
|
c46ca8b507 | ||
|
|
48d3bee225 | ||
|
|
3b0e5cc309 | ||
|
|
e5be31f9d5 | ||
|
|
17ea85c31f | ||
|
|
572e1422bf | ||
|
|
af263073b5 | ||
|
|
7facf2d620 | ||
|
|
eef3ff434b | ||
|
|
fa94e18a6a | ||
|
|
c680cfd5c5 | ||
|
|
3e8469611d | ||
|
|
39ab475156 | ||
|
|
636de67a17 | ||
|
|
d80c18f652 | ||
|
|
9f68e009f1 | ||
|
|
557bd2bbbf | ||
|
|
d33c53b691 | ||
|
|
ddd223c2ec | ||
|
|
50f5b600b1 | ||
|
|
d94df8390a | ||
|
|
8b33331929 | ||
|
|
86a9dde1eb | ||
|
|
33dec77063 | ||
|
|
fe06e2fa19 | ||
|
|
7b5e3eaafd | ||
|
|
0a30f1ffb9 | ||
|
|
8687604d26 | ||
|
|
0a9fd6c439 | ||
|
|
c95a9395de | ||
|
|
77066d7a9f | ||
|
|
c8e5b7de9a | ||
|
|
3e11a88a7c | ||
|
|
a7e4968836 | ||
|
|
6d9e2d3c03 | ||
|
|
0789e7a353 | ||
|
|
ff97a85552 | ||
|
|
33cfd92cef | ||
|
|
58513ef59f | ||
|
|
6056e3e767 | ||
|
|
1b1ed7c4ab | ||
|
|
5b44e4bddd | ||
|
|
54592969a4 | ||
|
|
38007ab3d5 | ||
|
|
bdd10c7617 | ||
|
|
c0f4bc021a | ||
|
|
34d6af93a6 | ||
|
|
0df481dabb | ||
|
|
55c5b91411 | ||
|
|
be745f4602 | ||
|
|
8bf5ad0f12 | ||
|
|
1b723b2fee | ||
|
|
6095eb98c4 | ||
|
|
95bb070a6b | ||
|
|
75a338304a | ||
|
|
1c28305b83 | ||
|
|
1f1abb80be | ||
|
|
3addc4563a | ||
|
|
e37e7566e2 | ||
|
|
eedf537902 | ||
|
|
5e8bd52433 | ||
|
|
910e8a6cf9 | ||
|
|
edfc2467a3 | ||
|
|
205170efe4 | ||
|
|
fceac407ee | ||
|
|
4c5ebe5949 | ||
|
|
d12ad26e79 | ||
|
|
bd57665b55 | ||
|
|
c3b8d48813 | ||
|
|
c8933e7326 | ||
|
|
c95c64ccff | ||
|
|
c5bcd4c2e4 | ||
|
|
9e402897c5 | ||
|
|
cbf87df551 | ||
|
|
ea01492e1f | ||
|
|
82445d5d3a | ||
|
|
839bc4cc9e | ||
|
|
98c6a569bf | ||
|
|
85ad95c0d0 | ||
|
|
1d0498fdce | ||
|
|
ee390c34e9 | ||
|
|
b853615f7d | ||
|
|
5b2bb30902 | ||
|
|
da4788a6c6 | ||
|
|
3022635a71 | ||
|
|
38d3ae6a6a | ||
|
|
3f0eea44f4 | ||
|
|
5077f60881 | ||
|
|
279f70a6bd | ||
|
|
01f3596fde | ||
|
|
23d3577a06 | ||
|
|
22c75951e0 | ||
|
|
3d3fb5dc61 | ||
|
|
e4a8213a2c | ||
|
|
b2f71ae163 | ||
|
|
53256b0e34 | ||
|
|
4ebd3e046d | ||
|
|
d07d49d2e7 | ||
|
|
111bfb9302 | ||
|
|
143b2a7ff5 | ||
|
|
e7b47d28d9 | ||
|
|
97b44a89d4 | ||
|
|
27e01657b1 | ||
|
|
43638813d7 | ||
|
|
f781b6785c | ||
|
|
9168cd4109 | ||
|
|
4c49c6ffc9 | ||
|
|
4890727692 | ||
|
|
7907a45ca2 | ||
|
|
727bbba815 | ||
|
|
877da36e5a | ||
|
|
ab8effbc32 | ||
|
|
a58d98f0dc | ||
|
|
431cb73e61 | ||
|
|
0ee02f2efd | ||
|
|
8d5b2a9e88 | ||
|
|
1085673010 | ||
|
|
d327a72749 | ||
|
|
74add23c14 | ||
|
|
9b400573c8 | ||
|
|
a8c3ef7d00 | ||
|
|
a484582b70 | ||
|
|
69b956904c | ||
|
|
6e3888f295 | ||
|
|
1bfa0eb9c7 | ||
|
|
08a342c909 | ||
|
|
a28ad2b0c7 | ||
|
|
4c96de9cdf | ||
|
|
3a645abe6f | ||
|
|
27bd6f96e7 | ||
|
|
a98fac2e95 | ||
|
|
f645b65a9e | ||
|
|
6eaf8cc374 | ||
|
|
61c0b691ab | ||
|
|
394cefb2de | ||
|
|
30d6a55e3c | ||
|
|
e32018e8f6 | ||
|
|
6b002a8475 | ||
|
|
97e23c8f50 | ||
|
|
e558ffd807 | ||
|
|
71d158ca45 | ||
|
|
5f8e5e0be9 | ||
|
|
ff91eb1407 | ||
|
|
877a859ef4 | ||
|
|
f8b29cd967 | ||
|
|
3d2554c557 | ||
|
|
b7d7204d40 | ||
|
|
876d26d174 | ||
|
|
3ccb1a63aa | ||
|
|
c8bb9b4f5f | ||
|
|
723be29118 | ||
|
|
2865915cdf | ||
|
|
d3e0c2bb6e | ||
|
|
5e7ae73861 | ||
|
|
61206b2169 | ||
|
|
92e2a8913b | ||
|
|
ad827828d7 | ||
|
|
faf16084a3 | ||
|
|
38d2b55456 | ||
|
|
abcebc54e8 | ||
|
|
40cb963c99 | ||
|
|
9d267a6cc4 | ||
|
|
462f24149b | ||
|
|
4744b62f91 | ||
|
|
08244e7fdc | ||
|
|
f64fb1bee1 | ||
|
|
743c3b2466 | ||
|
|
aefa36fef8 | ||
|
|
d7f6503196 | ||
|
|
3375b8c898 | ||
|
|
52e5919ccd | ||
|
|
1c2e57adb6 | ||
|
|
2e99d6ee0a | ||
|
|
6744815f77 | ||
|
|
0ba44ab2d3 | ||
|
|
a6006450de | ||
|
|
c20e2ba451 | ||
|
|
7a0b387c1c | ||
|
|
fdfe5fbe39 | ||
|
|
35751efad5 | ||
|
|
7005c9e40a | ||
|
|
118cf25ff7 | ||
|
|
01f65220ec | ||
|
|
078c7b78da | ||
|
|
25f00db9cf | ||
|
|
576ee17c4d | ||
|
|
e7f654f56a | ||
|
|
014277b574 | ||
|
|
043a49152f | ||
|
|
40592d197d | ||
|
|
80e3d0eab4 | ||
|
|
40da090fe8 | ||
|
|
795ccd9782 | ||
|
|
57bfd6b968 | ||
|
|
02d1fe308d | ||
|
|
3e117f46d5 | ||
|
|
dbb14e37fa | ||
|
|
8f2b534a68 | ||
|
|
224d2b89b1 | ||
|
|
4c2b5b010b | ||
|
|
b98a1fa100 | ||
|
|
bb51b53e11 | ||
|
|
19bd2431aa | ||
|
|
977ece64e3 | ||
|
|
f8f362fc10 | ||
|
|
fdc265eaea | ||
|
|
941a4fa14f | ||
|
|
e4fdbdaf8d | ||
|
|
c09e050084 | ||
|
|
d12a94401d | ||
|
|
e14d0d7c7f | ||
|
|
ee9c94aceb | ||
|
|
4b4424ae92 | ||
|
|
8a9f11b0f5 | ||
|
|
fd493dba9a | ||
|
|
75bc7c51b5 | ||
|
|
aab166c466 | ||
|
|
94c49f500f | ||
|
|
f353c4edf2 | ||
|
|
acb3e5058d | ||
|
|
6858aecda7 | ||
|
|
4411ca18aa | ||
|
|
dc9dbed683 | ||
|
|
860673bb0e | ||
|
|
ac54a40af5 | ||
|
|
92df52867d | ||
|
|
ab209c6f39 | ||
|
|
69dd23f27b | ||
|
|
c5577317f9 | ||
|
|
0d7cf6f37d | ||
|
|
1d425b3cd6 | ||
|
|
9b62a45100 | ||
|
|
db1f745a50 | ||
|
|
d56e9ef298 | ||
|
|
f278b7c2c5 | ||
|
|
916f3ca9ec | ||
|
|
30c695d988 | ||
|
|
7dcb120edf | ||
|
|
e570e25b24 | ||
|
|
3cbc7f163a | ||
|
|
21a8001f56 | ||
|
|
ebafb0006a | ||
|
|
37e42b2f0b | ||
|
|
1ae5b210b0 | ||
|
|
3f2fb67c04 | ||
|
|
dded30d6a6 | ||
|
|
047a585cc2 | ||
|
|
8cb7aebf86 | ||
|
|
439eedcec7 | ||
|
|
ed94e6508c | ||
|
|
9f33e756d5 | ||
|
|
9fee6568cd | ||
|
|
2a169d50ec | ||
|
|
b2d90c2ac1 | ||
|
|
64cd253618 | ||
|
|
98bc6fbe4c | ||
|
|
70e41eb0e3 | ||
|
|
b5e08df5d3 | ||
|
|
46034b0629 | ||
|
|
109da1056e | ||
|
|
e046565f22 | ||
|
|
ff99472278 | ||
|
|
a5a2c2a93f | ||
|
|
4cf7a531c2 | ||
|
|
b101dc780d | ||
|
|
1c7ff5c597 | ||
|
|
880f220355 | ||
|
|
a1d2acc845 | ||
|
|
60caf896bd | ||
|
|
f9d0ec864d | ||
|
|
6ca1aa532c | ||
|
|
b068d82ccf | ||
|
|
43ca5ac5b9 | ||
|
|
18c3ab2340 | ||
|
|
87eaba184e | ||
|
|
97b5d8a2ee | ||
|
|
7c3a762107 | ||
|
|
0b90af77da | ||
|
|
28dcab8ee8 | ||
|
|
6c468a5291 | ||
|
|
d090d8c2e8 | ||
|
|
aba315866e | ||
|
|
e981aa4520 | ||
|
|
e603622021 | ||
|
|
3e007965b2 | ||
|
|
c0610360a3 | ||
|
|
ff8f6e073f | ||
|
|
aadb5407d2 | ||
|
|
2e4d5eb958 | ||
|
|
33fb13a66e | ||
|
|
4dc69aa1c4 | ||
|
|
b3681a3ceb | ||
|
|
348dcc4275 | ||
|
|
39e69119ac | ||
|
|
8c57926978 | ||
|
|
8b1965054f | ||
|
|
18993069e3 | ||
|
|
7e6c8cc768 | ||
|
|
3ae44d2fcb | ||
|
|
a6eb3936e4 | ||
|
|
bb24d3ca30 | ||
|
|
7e191c0be5 | ||
|
|
d9ea165bbb | ||
|
|
148211a5c7 | ||
|
|
23e07f47f0 | ||
|
|
724156c554 | ||
|
|
b972e4c746 | ||
|
|
f0afa1fff2 | ||
|
|
a36c05f0c5 | ||
|
|
4f3a66b4eb | ||
|
|
6369c7ea44 | ||
|
|
f1b8aff8d4 | ||
|
|
be270aa797 | ||
|
|
f33d355024 | ||
|
|
6f913b026e | ||
|
|
220b4e0954 | ||
|
|
bac4a4fd86 | ||
|
|
d923299230 | ||
|
|
a79ebcec86 | ||
|
|
69bbee7648 | ||
|
|
57d7729d05 | ||
|
|
25a1dcb72c | ||
|
|
cff9b73b80 | ||
|
|
23336de5b5 | ||
|
|
d346ca0466 | ||
|
|
22fadf078e | ||
|
|
74acf85683 | ||
|
|
713de03d1d | ||
|
|
5817f52110 | ||
|
|
dfc03aee38 | ||
|
|
6bc8038068 | ||
|
|
eb7d56c1b9 | ||
|
|
f5b515ef79 | ||
|
|
943431346d | ||
|
|
a597173b71 | ||
|
|
c484cc8fff | ||
|
|
f195a4ccbf | ||
|
|
f55bb6dce6 | ||
|
|
e7f49f8330 | ||
|
|
cec9356fc2 | ||
|
|
6d704b382d | ||
|
|
1c5f67111f | ||
|
|
660df4b370 | ||
|
|
19dc5cc6f2 | ||
|
|
67baf90d04 | ||
|
|
07bf41ea6c | ||
|
|
c2cfc9b8db | ||
|
|
7a7a2bc10b | ||
|
|
e3127ed516 | ||
|
|
93a98db43e | ||
|
|
750b55649a | ||
|
|
ca5854a8bd | ||
|
|
712f728a5e | ||
|
|
eb7766a5e5 | ||
|
|
0cabbfde96 | ||
|
|
c5983276e5 |
15
.gitignore
vendored
@@ -1,7 +1,10 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.cbz
|
Pipfile
|
||||||
*.cbr
|
Pipfile.lock
|
||||||
.idea
|
setup.bat
|
||||||
build
|
kindlecomicconverter/sentry.py
|
||||||
awkcc
|
other/windows/kindlegen.exe
|
||||||
.DS_Store
|
dist/
|
||||||
|
build/
|
||||||
|
KindleComicConverter.egg-info/
|
||||||
|
.idea/
|
||||||
30
.travis.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
372
CHANGELOG.md
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
# CHANGELOG
|
||||||
|
#### 5.5.2:
|
||||||
|
* Fixed KindleGen detection on macOS 10.15
|
||||||
|
* Fixed multiple smaller issues
|
||||||
|
|
||||||
|
#### 5.5.1:
|
||||||
|
* Fixes some stability issues
|
||||||
|
|
||||||
|
#### 5.5.0:
|
||||||
|
* Added support for WebP format
|
||||||
|
* Added profiles for Kindle Paperwhite 4 and Kobo Forma
|
||||||
|
* All archives are now handled by 7z
|
||||||
|
* Removed MCD support
|
||||||
|
* Fixed multiple smaller issues
|
||||||
|
|
||||||
|
#### 5.4.5:
|
||||||
|
* Fixed EPUB output for non-Kindle devices
|
||||||
|
|
||||||
|
#### 5.4.4:
|
||||||
|
* Minor bug fixes
|
||||||
|
|
||||||
|
#### 5.4.3:
|
||||||
|
* Fixed conversion crash on Windows
|
||||||
|
|
||||||
|
#### 5.4.2:
|
||||||
|
* Added Kindle Oasis 2 profile
|
||||||
|
* Allowed metadata editor to edit directories
|
||||||
|
* Fixed image stretching when HQ Panel View option was enabled
|
||||||
|
* Fixed possible problem with directory sort order
|
||||||
|
|
||||||
|
#### 5.4.1:
|
||||||
|
* Minor bug fixes and tweaks
|
||||||
|
* Implemented new binary build pipeline
|
||||||
|
|
||||||
|
#### 5.4:
|
||||||
|
* Reimplemented high quality Panel View option
|
||||||
|
* Improved webtoon splitter
|
||||||
|
* Fixed page splitter
|
||||||
|
|
||||||
|
#### 5.3.1:
|
||||||
|
* Small increase of output quality
|
||||||
|
* Improved error reporting
|
||||||
|
* Internal changes and tweaks
|
||||||
|
|
||||||
|
#### 5.3:
|
||||||
|
* Vastly improved output compatibility for non-Kindle devices
|
||||||
|
* Enabled old pinch zoom for Kindle devices
|
||||||
|
* Re-enabled Panel View support for Kindle Keyboard
|
||||||
|
* Partially re-enabled OS X file association mechanism
|
||||||
|
* Fixed multiple smaller issues
|
||||||
|
|
||||||
|
#### 5.2.1:
|
||||||
|
* Improved directory parsing
|
||||||
|
* Tweaked margin detection algorithm
|
||||||
|
* Improved error reporting
|
||||||
|
|
||||||
|
#### 5.2:
|
||||||
|
* Added new Panel View options
|
||||||
|
* Implemented new margin detection algorithm
|
||||||
|
* Removed HQ Panel View mode
|
||||||
|
* Fixed multiple smaller issues
|
||||||
|
|
||||||
|
#### 5.1.3:
|
||||||
|
* Added Kobo Aura ONE profile
|
||||||
|
* Fixed few small bugs
|
||||||
|
|
||||||
|
#### 5.1.2:
|
||||||
|
* Fixed error reporting
|
||||||
|
|
||||||
|
#### 5.1.1:
|
||||||
|
* Fixed multiple GUI bugs
|
||||||
|
|
||||||
|
#### 5.1:
|
||||||
|
* GUI now can be resized and high DPI support was somewhat improved
|
||||||
|
* Added profile for Kindle Oasis
|
||||||
|
* Implemented new error reporting mechanism
|
||||||
|
* CLI version now support additional cropping options
|
||||||
|
* Fixed permission issues on Windows
|
||||||
|
* Fixed multiple smaller issues
|
||||||
|
|
||||||
|
#### 5.0.1:
|
||||||
|
* Fixed Panel View placement issues
|
||||||
|
* Decreased application startup time
|
||||||
|
* Fixed multiple smaller issues
|
||||||
|
|
||||||
|
#### 5.0:
|
||||||
|
* Major overhaul of internal mechanisms and GUI
|
||||||
|
* Added cover upload feature
|
||||||
|
* Tweaked Webtoon parsing mode
|
||||||
|
* Fixed multiple smaller issues
|
||||||
|
* Migrated build enviroment to PyInstaller
|
||||||
|
|
||||||
|
#### 4.6.5:
|
||||||
|
* Fixed multiple Windows and OS X issues
|
||||||
|
* Allowed Linux release to use older PyQT5 version
|
||||||
|
|
||||||
|
#### 4.6.4:
|
||||||
|
* Fixed multiple Windows specific problems
|
||||||
|
* Improved error handling
|
||||||
|
* Improved color detection algorithm
|
||||||
|
* New, slimmer OS X release
|
||||||
|
|
||||||
|
#### 4.6.3:
|
||||||
|
* Implemented remote bug reporting
|
||||||
|
* Minor bug fixes and GUI tweaks
|
||||||
|
|
||||||
|
#### 4.6.2:
|
||||||
|
* Fixed critical MOBI header bug
|
||||||
|
* Fixed metadata encoding error
|
||||||
|
|
||||||
|
#### 4.6.1:
|
||||||
|
* Fixed KEPUB TOC generator
|
||||||
|
* Added warning about too small input files
|
||||||
|
* ComicRack Summary metadata field is now parsed
|
||||||
|
* Small tweaks of KEPUB output
|
||||||
|
|
||||||
|
#### 4.6:
|
||||||
|
* KEPUB is now default output for all Kobo profiles
|
||||||
|
* EPUB output now produce fully valid EPUB 3.0.1
|
||||||
|
* Added profile for Kindle Paperwhite 3
|
||||||
|
* Dropped official support of all Kindle Fire models and Kindle for Android
|
||||||
|
* Other minor tweaks
|
||||||
|
|
||||||
|
#### 4.5.1:
|
||||||
|
* Added Kobo Glo HD profile
|
||||||
|
* Fixed RAR/CBR parsing anomalies
|
||||||
|
* Minor bug fixes and tweaks
|
||||||
|
|
||||||
|
#### 4.5:
|
||||||
|
* Added simple ComicRack metadata editor
|
||||||
|
* Re-enabled Manga Cover Database support
|
||||||
|
* ComicRack bookmarks are now parsed
|
||||||
|
* Fixed glitches in Kindle Voyage profile
|
||||||
|
* Fixed problems with directory locks on Windows
|
||||||
|
* Fixed sorting anomalies
|
||||||
|
* Improved conversion speed
|
||||||
|
|
||||||
|
#### 4.4.1:
|
||||||
|
* Fixed problems with OSX GUI
|
||||||
|
* Added one missing DLL to Windows installer
|
||||||
|
|
||||||
|
#### 4.4:
|
||||||
|
* Improved speed and quality of conversion
|
||||||
|
* Added RAR5 support
|
||||||
|
* Dropped BMP and TIFF support
|
||||||
|
* Fixed some WebToon mode bugs
|
||||||
|
* Fixed CBR parsing on OSX
|
||||||
|
|
||||||
|
#### 4.3.1:
|
||||||
|
* Fixed Kindle Voyage profile
|
||||||
|
* Fixed some bugs in OS X release
|
||||||
|
* CLI version now support multiple input files at once
|
||||||
|
* Disabled MCB support
|
||||||
|
* Other minor tweaks
|
||||||
|
|
||||||
|
#### 4.3:
|
||||||
|
* Added profiles for Kindle Voyage and Kobo Aura H2O
|
||||||
|
* Added missing features to CLI version
|
||||||
|
* Other minor bug fixes
|
||||||
|
|
||||||
|
#### 4.2.1:
|
||||||
|
* Improved margin color detection
|
||||||
|
* Fixed random crashes of MOBI processing step
|
||||||
|
* Fixed resizing problems in high quality mode
|
||||||
|
* Fixed some MCD support bugs
|
||||||
|
* Default output format for Kindle DX is now CBZ
|
||||||
|
|
||||||
|
#### 4.2:
|
||||||
|
* Added [Manga Cover Database](http://manga.joentjuh.nl/) support
|
||||||
|
* Officially dropped Windows XP support
|
||||||
|
* Fixed _Other_ profile
|
||||||
|
* Fixed problems with page order on stock KOBO CBZ reader
|
||||||
|
* Many other small bug fixes and tweaks
|
||||||
|
|
||||||
|
#### 4.1:
|
||||||
|
* Thanks to code contributed by Kevin Hendricks speed of MOBI creation was greatly increased
|
||||||
|
* Improved performance on Windows
|
||||||
|
* Improved MOBI splitting and changed maximal size of output file
|
||||||
|
* Fixed _No optimization_ mode
|
||||||
|
* Multiple small tweaks nad minor bug fixes
|
||||||
|
|
||||||
|
#### 4.0.2:
|
||||||
|
* Fixed some Windows and OSX specific bugs
|
||||||
|
* Fixed problem with marigns when using HQ mode
|
||||||
|
|
||||||
|
#### 4.0.1:
|
||||||
|
* Fixed file lock problems that plagued some Windows users
|
||||||
|
* Fixed content server failing to start on Windows
|
||||||
|
* Improved performance of WebToon splitter
|
||||||
|
* Tweaked margin color detection
|
||||||
|
|
||||||
|
#### 4.0:
|
||||||
|
* KCC now use Python 3.3 and Qt 5.2
|
||||||
|
* Full UTF-8 awareness
|
||||||
|
* CBZ output now support Manga mode
|
||||||
|
* Improved Panel View support and margin color detection
|
||||||
|
* Added drag&drop support
|
||||||
|
* Output directory can be now selected
|
||||||
|
* Windows release now have auto-updater
|
||||||
|
* Names of chapters on Kindle should be now more user friendly
|
||||||
|
* Fixed OSX file association support
|
||||||
|
* Many extensive internal changes and tweaks
|
||||||
|
|
||||||
|
#### 3.7.2:
|
||||||
|
* Fixed problems with HQ mode
|
||||||
|
|
||||||
|
#### 3.7.1:
|
||||||
|
* Hotfixed Kobo profiles
|
||||||
|
|
||||||
|
#### 3.7:
|
||||||
|
* Added profiles for KOBO devices
|
||||||
|
* Improved Panel View support
|
||||||
|
* Improved WebToon splitter
|
||||||
|
* Improved margin color autodetection
|
||||||
|
* Tweaked EPUB output
|
||||||
|
* Fixed stretching option
|
||||||
|
* GUI tweaks and minor bugfixes
|
||||||
|
|
||||||
|
#### 3.6.2:
|
||||||
|
* Fixed previous PNG output fix
|
||||||
|
* Fixed Panel View anomalies
|
||||||
|
|
||||||
|
#### 3.6.1:
|
||||||
|
* Fixed PNG output
|
||||||
|
|
||||||
|
#### 3.6:
|
||||||
|
* Increased quality of Panel View zoom
|
||||||
|
* Creation of multipart MOBI output is now faster on machines with 4GB+ RAM
|
||||||
|
* Automatic gamma correction now distinguishes color and grayscale images
|
||||||
|
* Added ComicRack metadata parser
|
||||||
|
* Implemented new method to detect border color in non-webtoon comics
|
||||||
|
* Upscaling is now enabled by default for Kindle Fire HD/HDX
|
||||||
|
* Windows nad Linux releases now have tray icon
|
||||||
|
* Fixed Kindle Fire HDX 7" output
|
||||||
|
* Increased target resolution for Kindle DX/DXG CBZ output
|
||||||
|
|
||||||
|
#### 3.5:
|
||||||
|
* Added simple content server - Converted files can be now delivered wireless
|
||||||
|
* Added proper Windows installer
|
||||||
|
* Improved multiprocessing speed
|
||||||
|
* GUI tweaks and minor bug fixes
|
||||||
|
|
||||||
|
#### 3.4:
|
||||||
|
* Improved PNG output
|
||||||
|
* Increased quality of upscaling
|
||||||
|
* Added support of file association - KCC can now open CBZ, CBR, CB7, ZIP, RAR, 7Z and PDF files directly
|
||||||
|
* Paths that contain UTF-8 characters are now supported
|
||||||
|
* Migrated to new version of Pillow library
|
||||||
|
* Merged DX and DXG profiles
|
||||||
|
* Many other minor bug fixes and GUI tweaks
|
||||||
|
|
||||||
|
#### 3.3:
|
||||||
|
* Margins are now automatically omitted in Panel View mode
|
||||||
|
* Margin color fill is now autodetected
|
||||||
|
* Created MOBI files are not longer marked as _Personal_ on newer Kindle models
|
||||||
|
* Layout of panels in Panel View mode is now automatically adjusted to content
|
||||||
|
* Fixed Kindle 2/DX/DXG profiles - no more blank pages
|
||||||
|
* All Kindle Fire profiles now support hiqh quality Panel View
|
||||||
|
* Added support of 7z/CB7 files
|
||||||
|
* Added Kindle Fire HDX profile
|
||||||
|
* Support for Virtual Panel View was removed
|
||||||
|
* Profiles for Kindle Keyboard, Touch and Non-Touch are now merged
|
||||||
|
* Windows release is now bundled with UnRAR and 7za
|
||||||
|
* Small GUI tweaks
|
||||||
|
|
||||||
|
#### 3.2:
|
||||||
|
* Too big EPUB files are now splitted before conversion to MOBI
|
||||||
|
* Added experimental parser of manga webtoons
|
||||||
|
* Improved error handling
|
||||||
|
|
||||||
|
#### 3.2.1:
|
||||||
|
* Hotfixed crash occurring on OS with Russian locale
|
||||||
|
|
||||||
|
#### 3.1:
|
||||||
|
* Added profile: Kindle for Android
|
||||||
|
* Add file/directory dialogs now support multiselect
|
||||||
|
* Many small fixes and tweaks
|
||||||
|
|
||||||
|
#### 3.0:
|
||||||
|
* New QT GUI
|
||||||
|
* Merge with AWKCC
|
||||||
|
* Added ultra quality mode
|
||||||
|
* Added support for custom width/height
|
||||||
|
* Added option to disable color conversion
|
||||||
|
|
||||||
|
#### 2.10:
|
||||||
|
* Multiprocessing support
|
||||||
|
* Kindle Fire support (color EPUB/MOBI)
|
||||||
|
* Panel View support for horizontal content
|
||||||
|
* Fixed panel order for horizontal pages when --rotate is enabled
|
||||||
|
* Disabled cropping and page number cutting for blank pages
|
||||||
|
* Fixed some slugify issues with specific file naming conventions (#50, #51)
|
||||||
|
|
||||||
|
#### 2.9
|
||||||
|
* Added support for generating a plain CBZ (skipping all the EPUB/MOBI generation) (#45)
|
||||||
|
* Prevent output file overwriting the source one: if a duplicate name is detected, append _kcc to the name
|
||||||
|
* Rarfile library updated to 2.6
|
||||||
|
* Added GIF, TIFF and BMP to supported formats (#42)
|
||||||
|
* Filenames slugifications (#28, #31, #9, #8)
|
||||||
|
|
||||||
|
#### 2.8
|
||||||
|
* Updated rarfile library
|
||||||
|
* Panel View support + HQ support (#36) - new option: --nopanelviewhq
|
||||||
|
* Split profiles for K4NT and K4T
|
||||||
|
* Rewrite of Landscape Mode support (huge readability improvement for KPW)
|
||||||
|
* Upscale use now BILINEAR method
|
||||||
|
* Added generic CSS file
|
||||||
|
* Optimized archive extraction for zip/rar files (#40)
|
||||||
|
|
||||||
|
#### 2.7
|
||||||
|
* Lots of GUI improvements (#27, #13)
|
||||||
|
* Added gamma support within --gamma option (defaults to profile-specified gamma) (#26, #27)
|
||||||
|
* Added --nodithering option to prevent dithering optimizations (#27)
|
||||||
|
* EPUB margins support (#30)
|
||||||
|
* Fixed no file added if file has no spaces on Windows (#25)
|
||||||
|
* Gracefully exit if unrar missing (#15)
|
||||||
|
* Do not call kindlegen if source EPUB is bigger than 320MB (#17)
|
||||||
|
* Get filetype from magic number (#14)
|
||||||
|
* PDF conversion works again
|
||||||
|
|
||||||
|
#### 2.6
|
||||||
|
* Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
|
||||||
|
* Added --output option to customize EPUB output dir/file (#22)
|
||||||
|
* Add rendition:layout and rendition:orientation EPUB meta tags (supported by new kindlegen 2.8)
|
||||||
|
* Fixed natural sorting for files (#18)
|
||||||
|
|
||||||
|
#### 2.5
|
||||||
|
* Added --black-borders option to set added borders black when page's ratio is not the device's one (#11).
|
||||||
|
* Fixes EPUB containing zipped itself (#10)
|
||||||
|
|
||||||
|
#### 2.4
|
||||||
|
* Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
|
||||||
|
* Fixed "add folders" from GUI.
|
||||||
|
|
||||||
|
#### 2.3
|
||||||
|
* Fixed win32 EPUB generation, folder handling, filenames with spaces and subfolders
|
||||||
|
|
||||||
|
#### 2.2:
|
||||||
|
* Added (valid!) EPUB 2.0 output
|
||||||
|
* Rename .zip files to .cbz to avoid overwriting
|
||||||
|
|
||||||
|
#### 2.1
|
||||||
|
* Added basic error reporting
|
||||||
|
|
||||||
|
#### 2.0
|
||||||
|
* GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support.
|
||||||
|
|
||||||
|
#### 1.5
|
||||||
|
* Added subfolder support for multiple chapters.
|
||||||
|
|
||||||
|
#### 1.4.1
|
||||||
|
* Fixed a serious bug on resizing when img ratio was bigger than device one
|
||||||
|
|
||||||
|
#### 1.4
|
||||||
|
* Added some options for controlling image optimization
|
||||||
|
* Further optimization (ImageOps, page numbering cut, autocontrast)
|
||||||
|
|
||||||
|
#### 1.3
|
||||||
|
* Fixed an issue in OPF generation for device resolution
|
||||||
|
* Reworked options system (call with -h option to get the inline help)
|
||||||
|
|
||||||
|
#### 1.2
|
||||||
|
* Comic optimizations! Split pages not target-oriented (landscape with portrait target or portrait with landscape target), add palette and other image optimizations from Mangle. WARNING: PIL is required for all image mangling!
|
||||||
|
|
||||||
|
#### 1.1.1
|
||||||
|
* Added support for CBZ/CBR files in Kindle Comic Converter
|
||||||
|
|
||||||
|
#### 1.1
|
||||||
|
* Added support for CBZ/CBR files in comic2ebook.py
|
||||||
|
|
||||||
|
#### 1.0
|
||||||
|
* Initial version
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
ISC LICENSE
|
ISC LICENSE
|
||||||
|
|
||||||
Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
Copyright (c) 2013-2019 Paweł Jastrzębski <pawelj@iosphe.re>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for
|
Permission to use, copy, modify, and/or distribute this software for
|
||||||
any purpose with or without fee is hereby granted, provided that the
|
any purpose with or without fee is hereby granted, provided that the
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
include README.md MANIFEST.in LICENSE.txt
|
exclude kindlecomicconverter/sentry.py
|
||||||
259
README.md
@@ -1,126 +1,183 @@
|
|||||||
# KCC
|
# KCC
|
||||||
|
|
||||||
`KCC` (a.k.a. `KindleComicConverter`) is a Python app to convert comic files or folders to ePub or Panel View MOBI.
|
[](https://github.com/ciromattia/kcc/releases) [](https://pypi.python.org/pypi/KindleComicConverter) [](https://aur.archlinux.org/packages/kcc/)
|
||||||
It was initally developed for Kindle but as of version 2.2 it outputs valid ePub 2.0 so _**despite its name, KCC is
|
|
||||||
actually a comic 2 epub converter that every ereader owner can happily use**_.
|
|
||||||
|
|
||||||
It also optimizes comic images by:
|
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
|
||||||
- Enhancing contrast.
|
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
|
||||||
- Cutting page numbering.
|
actually a comic/manga to EPUB converter that every e-reader owner can happily use**_.
|
||||||
- Cropping white borders.
|
It can also optionally optimize images by applying a number of transformations.
|
||||||
- Resizing larger images to device's native resolution.
|
|
||||||
- Quantizing images to device's palette.
|
### A word of warning
|
||||||
|
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||||
|
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||||
|
_KC2_ in no way is a replacement for **KCC** so you can be quite confident we'll going to carry on developing our little monster ;-)
|
||||||
|
|
||||||
|
### Issues / new features / donations
|
||||||
|
If you have general questions about usage, feedback etc. please [post it here](http://www.mobileread.com/forums/showthread.php?t=207461).
|
||||||
|
If you have some **technical** problems using KCC please [file an issue here](https://github.com/ciromattia/kcc/issues/new).
|
||||||
|
If you can fix an open issue, fork & make a pull request.
|
||||||
|
|
||||||
|
If you find **KCC** valuable you can consider donating to the authors:
|
||||||
|
- Ciro Mattia Gonano:
|
||||||
|
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||||
|
- [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
||||||
|
- Paweł Jastrzębski:
|
||||||
|
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||||
|
- [](https://jastrzeb.ski/donate/)
|
||||||
|
|
||||||
## BINARY RELEASES
|
## BINARY RELEASES
|
||||||
You can find the latest released binary at the following links:
|
You can find the latest released binary at the following links:
|
||||||
- OS X: [https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.7.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.7.zip)
|
- **[Windows](http://kcc.iosphe.re/Windows/) (64-bit only)**
|
||||||
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.7.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.7.zip)
|
- **[macOS](http://kcc.iosphe.re/OSX/) (10.14+)**
|
||||||
- Win32: [http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.7.zip](http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.7.zip) *(thanks to [AcidWeb](https://github.com/AcidWeb))*
|
- **Linux:** Currently unavailable.
|
||||||
- Linux: Just download sourcecode and launch `python kcc.py` *(Provided you have Python and Pillow installed)*
|
|
||||||
|
## PYPI
|
||||||
|
**KCC** is also available on PyPI.
|
||||||
|
```
|
||||||
|
pip install KindleComicConverter
|
||||||
|
```
|
||||||
|
|
||||||
|
## DEPENDENCIES
|
||||||
|
Following software is required to run Linux version of **KCC** and/or bare sources:
|
||||||
|
- Python 3.3+
|
||||||
|
- [PyQt5](https://pypi.python.org/pypi/PyQt5) 5.6.0+
|
||||||
|
- [Pillow](https://pypi.python.org/pypi/Pillow/) 4.0.0+
|
||||||
|
- [psutil](https://pypi.python.org/pypi/psutil) 5.0.0+
|
||||||
|
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.1+, <3.0.0
|
||||||
|
- [raven](https://pypi.python.org/pypi/raven) 6.0.0+
|
||||||
|
|
||||||
|
On Debian based distributions these two commands should install all needed dependencies:
|
||||||
|
```
|
||||||
|
sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full
|
||||||
|
sudo pip3 install --upgrade pillow python-slugify psutil pyqt5 raven
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optional dependencies
|
||||||
|
- [KindleGen](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211) v2.9+ in a directory reachable by your _PATH_ or in _KCC_ directory *(For MOBI generation)*
|
||||||
|
- [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)*
|
||||||
|
|
||||||
## INPUT FORMATS
|
## INPUT FORMATS
|
||||||
`kcc` can understand and convert, at the moment, the following file types:
|
**KCC** can understand and convert, at the moment, the following input types:
|
||||||
- PNG, JPG
|
- Folders containing: PNG, JPG, GIF or WebP files
|
||||||
- Folders
|
- CBZ, ZIP *(With `7z` executable)*
|
||||||
- CBZ, ZIP
|
- CBR, RAR *(With `7z` executable)*
|
||||||
- CBR, RAR *(With `unrar` executable)*
|
- CB7, 7Z *(With `7z` executable)*
|
||||||
- PDF *(Extracting only contained JPG images)*
|
- PDF *(Only extracting JPG images)*
|
||||||
|
|
||||||
## OPTIONAL REQUIREMENTS
|
|
||||||
- `kindlegen` v2.7+ in a directory reachable by your PATH or in KCC directory *(For .mobi generation)*
|
|
||||||
- [unrar](http://www.rarlab.com/download.htm) *(For CBR support)*
|
|
||||||
|
|
||||||
### For compiling/running from source:
|
|
||||||
- Python 2.7+ (Included in MacOS and Linux, follow the [official documentation](http://www.python.org/getit/windows/) to install on Windows)
|
|
||||||
- [Pillow](http://pypi.python.org/pypi/Pillow/) for comic optimizations like split double pages, resize to optimal resolution, improve contrast and palette, etc.
|
|
||||||
Please refer to official documentation for installing into your system.
|
|
||||||
|
|
||||||
## USAGE
|
## USAGE
|
||||||
|
|
||||||
### GUI
|
Should be pretty self-explanatory. All options have detailed information in tooltips.
|
||||||
|
After completed conversion, you should find ready file alongside the original input file (same directory).
|
||||||
|
|
||||||
Should be pretty self-explanatory, just keep in mind that it's still in development ;)
|
Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details.
|
||||||
While working it seems frozen, I'll try to fix the aesthetics later.
|
|
||||||
Conversion being done, you should find an .epub and a .mobi files alongside the original input file (same directory)
|
|
||||||
|
|
||||||
### Standalone `comic2ebook.py` usage:
|
CLI version of **KCC** is intended for power users. It allows using options that might not be compatible and decrease the quality of output.
|
||||||
|
|
||||||
|
### Standalone `kcc-c2e.py` usage:
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: comic2ebook.py [options] comic_file|comic_folder
|
Usage: kcc-c2e [options] comic_file|comic_folder
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--version show program's version number and exit
|
MAIN:
|
||||||
-h, --help show this help message and exit
|
-p PROFILE, --profile=PROFILE
|
||||||
-p PROFILE, --profile=PROFILE
|
Device profile (Available options: K1, K2, K34, K578,
|
||||||
Device profile (Choose one among K1, K2, K3, K4, KDX, KDXG or KHD) [Default=KHD]
|
KDX, KPW, KV, KO, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
|
||||||
-t TITLE, --title=TITLE
|
KoAO, KoF) [Default=KV]
|
||||||
Comic title [Default=filename]
|
-m, --manga-style Manga style (right-to-left reading and splitting)
|
||||||
-m, --manga-style Manga style (Right-to-left reading and splitting) [Default=False]
|
-q, --hq Try to increase the quality of magnification
|
||||||
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
|
-2, --two-panel Display two not four panels in Panel View mode
|
||||||
--nodithering Disable image quantization [Default=False]
|
-w, --webtoon Webtoon processing mode
|
||||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
|
||||||
--upscale Resize images smaller than device's resolution [Default=False]
|
OUTPUT SETTINGS:
|
||||||
--stretch Stretch images to device's resolution [Default=False]
|
-o OUTPUT, --output=OUTPUT
|
||||||
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
|
Output generated file to specified directory or file
|
||||||
--rotate Rotate landscape pages instead of splitting them [Default=False]
|
-t TITLE, --title=TITLE
|
||||||
--nosplitrotate Disable splitting and rotation [Default=False]
|
Comic title [Default=filename or directory name]
|
||||||
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
|
-f FORMAT, --format=FORMAT
|
||||||
-o OUTPUT, --output=OUTPUT
|
Output format (Available options: Auto, MOBI, EPUB,
|
||||||
Output generated EPUB to specified directory or file
|
CBZ, KFX) [Default=Auto]
|
||||||
-v, --verbose Verbose output [Default=False]
|
-b BATCHSPLIT, --batchsplit=BATCHSPLIT
|
||||||
|
Split output into multiple files. 0: Don't split 1:
|
||||||
|
Automatic mode 2: Consider every subdirectory as
|
||||||
|
separate volume [Default=0]
|
||||||
|
|
||||||
|
PROCESSING:
|
||||||
|
-u, --upscale Resize images smaller than device's resolution
|
||||||
|
-s, --stretch Stretch images to device's resolution
|
||||||
|
-r SPLITTER, --splitter=SPLITTER
|
||||||
|
Double page parsing mode. 0: Split 1: Rotate 2: Both
|
||||||
|
[Default=0]
|
||||||
|
-g GAMMA, --gamma=GAMMA
|
||||||
|
Apply gamma correction to linearize the image
|
||||||
|
[Default=Auto]
|
||||||
|
-c CROPPING, --cropping=CROPPING
|
||||||
|
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
|
||||||
|
page numbers [Default=2]
|
||||||
|
--cp=CROPPINGP, --croppingpower=CROPPINGP
|
||||||
|
Set cropping power [Default=1.0]
|
||||||
|
--blackborders Disable autodetection and force black borders
|
||||||
|
--whiteborders Disable autodetection and force white borders
|
||||||
|
--forcecolor Don't convert images to grayscale
|
||||||
|
--forcepng Create PNG files instead JPEG
|
||||||
|
|
||||||
|
CUSTOM PROFILE:
|
||||||
|
--customwidth=CUSTOMWIDTH
|
||||||
|
Replace screen width provided by device profile
|
||||||
|
--customheight=CUSTOMHEIGHT
|
||||||
|
Replace screen height provided by device profile
|
||||||
|
|
||||||
|
OTHER:
|
||||||
|
-h, --help Show this help message and exit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Standalone `kcc-c2p.py` usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: kcc-c2p [options] comic_folder
|
||||||
|
|
||||||
|
Options:
|
||||||
|
MANDATORY:
|
||||||
|
-y HEIGHT, --height=HEIGHT
|
||||||
|
Height of the target device screen
|
||||||
|
-i, --in-place Overwrite source directory
|
||||||
|
-m, --merge Combine every directory into a single image before splitting
|
||||||
|
|
||||||
|
OTHER:
|
||||||
|
-d, --debug Create debug file for every split image
|
||||||
|
-h, --help Show this help message and exit
|
||||||
```
|
```
|
||||||
|
|
||||||
## CREDITS
|
## CREDITS
|
||||||
KCC is made by [Ciro Mattia Gonano](http://github.com/ciromattia) and [Paweł Jastrzębski](http://github.com/AcidWeb)
|
**KCC** is made by [Ciro Mattia Gonano](http://github.com/ciromattia) and [Paweł Jastrzębski](http://github.com/AcidWeb).
|
||||||
|
|
||||||
This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published in [this mobileread forum thread](http://www.mobileread.com/forums/showthread.php?t=192783))
|
This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published [here](http://www.mobileread.com/forums/showthread.php?t=192783)).
|
||||||
|
|
||||||
The app relies and includes the following scripts/binaries:
|
The app relies and includes the following scripts:
|
||||||
|
|
||||||
- `KindleStrip` script © 2010-2012 by **Paul Durrant** and released in public domain
|
- `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License.
|
||||||
([mobileread forum thread](http://www.mobileread.com/forums/showthread.php?t=96903))
|
- `image.py` class from **Alex Yatskov**'s [Mangle](https://github.com/FooSoft/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches.
|
||||||
- `rarfile.py` script © 2005-2011 **Marko Kreen** <markokr@gmail.com>, released with ISC 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.
|
||||||
- the icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC Attribution-NonCommercial-ShareAlike 3.0 Unported](http://creativecommons.org/licenses/by-nc-sa/3.0/) License
|
|
||||||
- `image.py` class from **Alex Yatskov**'s [Mangle](http://foosoft.net/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches
|
|
||||||
- `magic.py` from [python-magic](https://github.com/ahupp/python-magic) library
|
|
||||||
|
|
||||||
## CHANGELOG
|
## SAMPLE FILES CREATED BY KCC
|
||||||
- 1.00: Initial version
|
* [Kindle Oasis 2 / 3](http://kcc.iosphe.re/Samples/Ubunchu!-KO.mobi)
|
||||||
- 1.10: Added support for CBZ/CBR files in comic2ebook.py
|
* [Kindle Paperwhite 3 / 4 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||||
- 1.11: Added support for CBZ/CBR files in KindleComicConverter
|
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
||||||
- 1.20: Comic optimizations! Split pages not target-oriented (landscape with portrait target or portrait
|
* [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K578.mobi)
|
||||||
with landscape target), add palette and other image optimizations from Mangle.
|
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub)
|
||||||
WARNING: PIL is required for all image mangling!
|
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub)
|
||||||
- 1.30: Fixed an issue in OPF generation for device resolution
|
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub)
|
||||||
Reworked options system (call with -h option to get the inline help)
|
* [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub)
|
||||||
- 1.40: Added some options for controlling image optimization
|
* [Kobo Forma](http://kcc.iosphe.re/Samples/Ubunchu-KoF.kepub.epub)
|
||||||
Further optimization (ImageOps, page numbering cut, autocontrast)
|
|
||||||
- 1.41: Fixed a serious bug on resizing when img ratio was bigger than device one
|
## PRIVACY
|
||||||
- 1.50: Added subfolder support for multiple chapters.
|
**KCC** is initiating internet connections in two cases:
|
||||||
- 2.0: GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support.
|
* During startup - Version check.
|
||||||
- 2.1: Added basic error reporting
|
* When error occurs - Automatic reporting on Windows and macOS.
|
||||||
- 2.2: Added (valid!) ePub 2.0 output
|
|
||||||
Rename .zip files to .cbz to avoid overwriting
|
## KNOWN ISSUES
|
||||||
- 2.3: Fixed win32 ePub generation, folder handling, filenames with spaces and subfolders
|
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||||
- 2.4: Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
|
|
||||||
Fixed "add folders" from GUI.
|
|
||||||
- 2.5: Added --black-borders option to set added borders black when page's ratio is not the device's one (#11).
|
|
||||||
Fixes epub containing zipped itself (#10)
|
|
||||||
- 2.6: Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
|
|
||||||
Added --output option to customize ePub output dir/file (#22)
|
|
||||||
Add rendition:layout and rendition:orientation ePub meta tags (supported by new kindlegen 2.8)
|
|
||||||
Fixed natural sorting for files (#18)
|
|
||||||
- 2.7: Lots of GUI improvements (#27, #13)
|
|
||||||
Added gamma support within --gamma option (defaults to profile-specified gamma) (#26, #27)
|
|
||||||
Added --nodithering option to prevent dithering optimizations (#27)
|
|
||||||
Epub margins support (#30)
|
|
||||||
Fixed no file added if file has no spaces on Windows (#25)
|
|
||||||
Gracefully exit if unrar missing (#15)
|
|
||||||
Do not call kindlegen if source epub is bigger than 320MB (#17)
|
|
||||||
Get filetype from magic number (#14)
|
|
||||||
PDF conversion works again
|
|
||||||
|
|
||||||
## COPYRIGHT
|
## COPYRIGHT
|
||||||
|
Copyright (c) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski.
|
||||||
Copyright (c) 2012-2013 Ciro Mattia Gonano with further contributions by Paweł Jastrzębski.
|
**KCC** is released under ISC LICENSE; see LICENSE.txt for further details.
|
||||||
KCC is released under ISC LICENSE; see LICENSE.txt for further details.
|
|
||||||
|
|||||||
14
appveyor.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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*
|
||||||
284
ez_setup.py
@@ -1,284 +0,0 @@
|
|||||||
#!python
|
|
||||||
"""Bootstrap setuptools installation
|
|
||||||
|
|
||||||
If you want to use setuptools in your package's setup.py, just include this
|
|
||||||
file in the same directory with it, and add this to the top of your setup.py::
|
|
||||||
|
|
||||||
from ez_setup import use_setuptools
|
|
||||||
use_setuptools()
|
|
||||||
|
|
||||||
If you want to require a specific version of setuptools, set a download
|
|
||||||
mirror, or use an alternate download directory, you can do so by supplying
|
|
||||||
the appropriate options to ``use_setuptools()``.
|
|
||||||
|
|
||||||
This file can also be run as a script to install or upgrade setuptools.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
DEFAULT_VERSION = "0.6c11"
|
|
||||||
DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
|
|
||||||
|
|
||||||
md5_data = {
|
|
||||||
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
|
|
||||||
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
|
|
||||||
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
|
|
||||||
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
|
|
||||||
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
|
|
||||||
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
|
|
||||||
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
|
|
||||||
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
|
|
||||||
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
|
|
||||||
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
|
|
||||||
'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090',
|
|
||||||
'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4',
|
|
||||||
'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7',
|
|
||||||
'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5',
|
|
||||||
'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de',
|
|
||||||
'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b',
|
|
||||||
'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2',
|
|
||||||
'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086',
|
|
||||||
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
|
|
||||||
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
|
|
||||||
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
|
|
||||||
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
|
|
||||||
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
|
|
||||||
'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
|
|
||||||
'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
|
|
||||||
'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
|
|
||||||
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
|
|
||||||
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
|
|
||||||
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
|
|
||||||
'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
|
|
||||||
'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
|
|
||||||
'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
|
|
||||||
'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
|
|
||||||
'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
|
|
||||||
'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
|
|
||||||
'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
|
|
||||||
'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
|
|
||||||
'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
|
|
||||||
'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
|
|
||||||
'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
|
|
||||||
'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
|
|
||||||
'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
|
|
||||||
}
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
try: from hashlib import md5
|
|
||||||
except ImportError: from md5 import md5
|
|
||||||
|
|
||||||
def _validate_md5(egg_name, data):
|
|
||||||
if egg_name in md5_data:
|
|
||||||
digest = md5(data).hexdigest()
|
|
||||||
if digest != md5_data[egg_name]:
|
|
||||||
print >>sys.stderr, (
|
|
||||||
"md5 validation of %s failed! (Possible download problem?)"
|
|
||||||
% egg_name
|
|
||||||
)
|
|
||||||
sys.exit(2)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def use_setuptools(
|
|
||||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
|
||||||
download_delay=15
|
|
||||||
):
|
|
||||||
"""Automatically find/download setuptools and make it available on sys.path
|
|
||||||
|
|
||||||
`version` should be a valid setuptools version number that is available
|
|
||||||
as an egg for download under the `download_base` URL (which should end with
|
|
||||||
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
|
|
||||||
it is not already available. If `download_delay` is specified, it should
|
|
||||||
be the number of seconds that will be paused before initiating a download,
|
|
||||||
should one be required. If an older version of setuptools is installed,
|
|
||||||
this routine will print a message to ``sys.stderr`` and raise SystemExit in
|
|
||||||
an attempt to abort the calling script.
|
|
||||||
"""
|
|
||||||
was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
|
|
||||||
def do_download():
|
|
||||||
egg = download_setuptools(version, download_base, to_dir, download_delay)
|
|
||||||
sys.path.insert(0, egg)
|
|
||||||
import setuptools; setuptools.bootstrap_install_from = egg
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
except ImportError:
|
|
||||||
return do_download()
|
|
||||||
try:
|
|
||||||
pkg_resources.require("setuptools>="+version); return
|
|
||||||
except pkg_resources.VersionConflict, e:
|
|
||||||
if was_imported:
|
|
||||||
print >>sys.stderr, (
|
|
||||||
"The required version of setuptools (>=%s) is not available, and\n"
|
|
||||||
"can't be installed while this script is running. Please install\n"
|
|
||||||
" a more recent version first, using 'easy_install -U setuptools'."
|
|
||||||
"\n\n(Currently using %r)"
|
|
||||||
) % (version, e.args[0])
|
|
||||||
sys.exit(2)
|
|
||||||
except pkg_resources.DistributionNotFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
del pkg_resources, sys.modules['pkg_resources'] # reload ok
|
|
||||||
return do_download()
|
|
||||||
|
|
||||||
def download_setuptools(
|
|
||||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
|
||||||
delay = 15
|
|
||||||
):
|
|
||||||
"""Download setuptools from a specified location and return its filename
|
|
||||||
|
|
||||||
`version` should be a valid setuptools version number that is available
|
|
||||||
as an egg for download under the `download_base` URL (which should end
|
|
||||||
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
|
||||||
`delay` is the number of seconds to pause before an actual download attempt.
|
|
||||||
"""
|
|
||||||
import urllib2, shutil
|
|
||||||
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
|
|
||||||
url = download_base + egg_name
|
|
||||||
saveto = os.path.join(to_dir, egg_name)
|
|
||||||
src = dst = None
|
|
||||||
if not os.path.exists(saveto): # Avoid repeated downloads
|
|
||||||
try:
|
|
||||||
from distutils import log
|
|
||||||
if delay:
|
|
||||||
log.warn("""
|
|
||||||
---------------------------------------------------------------------------
|
|
||||||
This script requires setuptools version %s to run (even to display
|
|
||||||
help). I will attempt to download it for you (from
|
|
||||||
%s), but
|
|
||||||
you may need to enable firewall access for this script first.
|
|
||||||
I will start the download in %d seconds.
|
|
||||||
|
|
||||||
(Note: if this machine does not have network access, please obtain the file
|
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
and place it in this directory before rerunning this script.)
|
|
||||||
---------------------------------------------------------------------------""",
|
|
||||||
version, download_base, delay, url
|
|
||||||
); from time import sleep; sleep(delay)
|
|
||||||
log.warn("Downloading %s", url)
|
|
||||||
src = urllib2.urlopen(url)
|
|
||||||
# Read/write all in one block, so we don't create a corrupt file
|
|
||||||
# if the download is interrupted.
|
|
||||||
data = _validate_md5(egg_name, src.read())
|
|
||||||
dst = open(saveto,"wb"); dst.write(data)
|
|
||||||
finally:
|
|
||||||
if src: src.close()
|
|
||||||
if dst: dst.close()
|
|
||||||
return os.path.realpath(saveto)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv, version=DEFAULT_VERSION):
|
|
||||||
"""Install or upgrade setuptools and EasyInstall"""
|
|
||||||
try:
|
|
||||||
import setuptools
|
|
||||||
except ImportError:
|
|
||||||
egg = None
|
|
||||||
try:
|
|
||||||
egg = download_setuptools(version, delay=0)
|
|
||||||
sys.path.insert(0,egg)
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
return main(list(argv)+[egg]) # we're done here
|
|
||||||
finally:
|
|
||||||
if egg and os.path.exists(egg):
|
|
||||||
os.unlink(egg)
|
|
||||||
else:
|
|
||||||
if setuptools.__version__ == '0.0.1':
|
|
||||||
print >>sys.stderr, (
|
|
||||||
"You have an obsolete version of setuptools installed. Please\n"
|
|
||||||
"remove it from your system entirely before rerunning this script."
|
|
||||||
)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
req = "setuptools>="+version
|
|
||||||
import pkg_resources
|
|
||||||
try:
|
|
||||||
pkg_resources.require(req)
|
|
||||||
except pkg_resources.VersionConflict:
|
|
||||||
try:
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
except ImportError:
|
|
||||||
from easy_install import main
|
|
||||||
main(list(argv)+[download_setuptools(delay=0)])
|
|
||||||
sys.exit(0) # try to force an exit
|
|
||||||
else:
|
|
||||||
if argv:
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
main(argv)
|
|
||||||
else:
|
|
||||||
print "Setuptools version",version,"or greater has been installed."
|
|
||||||
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
|
|
||||||
|
|
||||||
def update_md5(filenames):
|
|
||||||
"""Update our built-in md5 registry"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
for name in filenames:
|
|
||||||
base = os.path.basename(name)
|
|
||||||
f = open(name,'rb')
|
|
||||||
md5_data[base] = md5(f.read()).hexdigest()
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
data = [" %r: %r,\n" % it for it in md5_data.items()]
|
|
||||||
data.sort()
|
|
||||||
repl = "".join(data)
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
srcfile = inspect.getsourcefile(sys.modules[__name__])
|
|
||||||
f = open(srcfile, 'rb'); src = f.read(); f.close()
|
|
||||||
|
|
||||||
match = re.search("\nmd5_data = {\n([^}]+)}", src)
|
|
||||||
if not match:
|
|
||||||
print >>sys.stderr, "Internal error!"
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
src = src[:match.start(1)] + repl + src[match.end(1):]
|
|
||||||
f = open(srcfile,'w')
|
|
||||||
f.write(src)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
|
|
||||||
update_md5(sys.argv[2:])
|
|
||||||
else:
|
|
||||||
main(sys.argv[1:])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
29
gui/KCC.qrc
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="Icon">
|
||||||
|
<file>../icons/comic2ebook.png</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="Devices">
|
||||||
|
<file>../icons/Kobo.png</file>
|
||||||
|
<file>../icons/Other.png</file>
|
||||||
|
<file>../icons/Kindle.png</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="Formats">
|
||||||
|
<file>../icons/CBZ.png</file>
|
||||||
|
<file>../icons/EPUB.png</file>
|
||||||
|
<file>../icons/MOBI.png</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="Status">
|
||||||
|
<file>../icons/error.png</file>
|
||||||
|
<file>../icons/info.png</file>
|
||||||
|
<file>../icons/warning.png</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="Other">
|
||||||
|
<file>../icons/wiki.png</file>
|
||||||
|
<file>../icons/editor.png</file>
|
||||||
|
<file>../icons/list_background.png</file>
|
||||||
|
<file>../icons/clear.png</file>
|
||||||
|
<file>../icons/convert.png</file>
|
||||||
|
<file>../icons/document_new.png</file>
|
||||||
|
<file>../icons/folder_new.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
532
gui/KCC.ui
Normal file
@@ -0,0 +1,532 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>mainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="mainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>450</width>
|
||||||
|
<height>400</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Kindle Comic Converter</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Icon/icons/comic2ebook.png</normaloff>:/Icon/icons/comic2ebook.png</iconset>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralWidget">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="0" colspan="2">
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignJustify|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="2">
|
||||||
|
<widget class="QListWidget" name="jobList">
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}</string>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::NoSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollMode">
|
||||||
|
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollMode">
|
||||||
|
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" colspan="2">
|
||||||
|
<widget class="QWidget" name="customWidget" native="true">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QLabel" name="hLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom height:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QSpinBox" name="widthBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>2160</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="wLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom width:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3">
|
||||||
|
<widget class="QSpinBox" name="heightBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>3840</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="2">
|
||||||
|
<widget class="QWidget" name="optionWidget" native="true">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QCheckBox" name="mangaBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Manga mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QCheckBox" name="rotateBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Spread splitter</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QCheckBox" name="qualityBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Panel View 4/2/HQ</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QCheckBox" name="webtoonBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Webtoon mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QCheckBox" name="upscaleBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Stretch/Upscale</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QCheckBox" name="gammaBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom gamma</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QCheckBox" name="borderBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>W/B margins</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QCheckBox" name="outputSplit">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Output split</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QCheckBox" name="colorBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Color mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0" colspan="2">
|
||||||
|
<widget class="QWidget" name="gammaWidget" native="true">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="gammaLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Gamma: Auto</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="gammaSlider">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>250</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="QWidget" name="toolWidget" native="true">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="editorButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Editor</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="wikiButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Wiki</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/wiki.png</normaloff>:/Other/icons/wiki.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0" colspan="2">
|
||||||
|
<widget class="QWidget" name="buttonWidget" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPushButton" name="directoryButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Add directory</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/folder_new.png</normaloff>:/Other/icons/folder_new.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3">
|
||||||
|
<widget class="QPushButton" name="fileButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Add file</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/document_new.png</normaloff>:/Other/icons/document_new.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QComboBox" name="deviceBox">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>28</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Target device.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="3">
|
||||||
|
<widget class="QComboBox" name="formatBox">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>28</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Output format.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QPushButton" name="convertButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Convert</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/convert.png</normaloff>:/Other/icons/convert.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QPushButton" name="clearButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Clear list</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
<zorder>directoryButton</zorder>
|
||||||
|
<zorder>clearButton</zorder>
|
||||||
|
<zorder>fileButton</zorder>
|
||||||
|
<zorder>deviceBox</zorder>
|
||||||
|
<zorder>convertButton</zorder>
|
||||||
|
<zorder>formatBox</zorder>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusBar">
|
||||||
|
<property name="sizeGripEnabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>convertButton</tabstop>
|
||||||
|
<tabstop>clearButton</tabstop>
|
||||||
|
<tabstop>directoryButton</tabstop>
|
||||||
|
<tabstop>fileButton</tabstop>
|
||||||
|
<tabstop>deviceBox</tabstop>
|
||||||
|
<tabstop>formatBox</tabstop>
|
||||||
|
<tabstop>mangaBox</tabstop>
|
||||||
|
<tabstop>rotateBox</tabstop>
|
||||||
|
<tabstop>qualityBox</tabstop>
|
||||||
|
<tabstop>webtoonBox</tabstop>
|
||||||
|
<tabstop>upscaleBox</tabstop>
|
||||||
|
<tabstop>gammaBox</tabstop>
|
||||||
|
<tabstop>borderBox</tabstop>
|
||||||
|
<tabstop>outputSplit</tabstop>
|
||||||
|
<tabstop>colorBox</tabstop>
|
||||||
|
<tabstop>editorButton</tabstop>
|
||||||
|
<tabstop>wikiButton</tabstop>
|
||||||
|
<tabstop>jobList</tabstop>
|
||||||
|
<tabstop>gammaSlider</tabstop>
|
||||||
|
<tabstop>widthBox</tabstop>
|
||||||
|
<tabstop>heightBox</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources>
|
||||||
|
<include location="KCC.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
189
gui/MetaEditor.ui
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>editorDialog</class>
|
||||||
|
<widget class="QDialog" name="editorDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>260</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>260</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Metadata editor</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Icon/icons/comic2ebook.png</normaloff>:/Icon/icons/comic2ebook.png</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="editorWidget" native="true">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_1">
|
||||||
|
<property name="text">
|
||||||
|
<string>Series:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="seriesLine"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Volume:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="volumeLine"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Number:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="numberLine"/>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Writer:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLineEdit" name="writerLine"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Penciller:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLineEdit" name="pencillerLine"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Inker:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QLineEdit" name="inkerLine"/>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Colorist:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QLineEdit" name="coloristLine"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="optionWidget" native="true">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="statusLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="okButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/convert.png</normaloff>:/Other/icons/convert.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cancelButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cancel</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="KCC.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
BIN
icons/CBZ.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
icons/EPUB.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
icons/Kindle.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
icons/Kobo.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/MOBI.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
icons/Other.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
icons/Wizard-Small.bmp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/Wizard.bmp
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
icons/WizardOSX.png
Normal file
|
After Width: | Height: | Size: 328 KiB |
BIN
icons/clear.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 345 KiB |
BIN
icons/comic2ebook.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
icons/convert.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
icons/document_new.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
icons/editor.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
icons/error.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
icons/folder_new.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/info.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/list_background.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
icons/list_background.xcf
Normal file
BIN
icons/warning.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
icons/wiki.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
32
kcc-c2e.py
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
if sys.version_info[0] != 3:
|
||||||
|
print('ERROR: This is Python 3 script!')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
from multiprocessing import freeze_support, set_start_method
|
||||||
|
from kindlecomicconverter.startup import startC2E
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
set_start_method('spawn')
|
||||||
|
freeze_support()
|
||||||
|
startC2E()
|
||||||
32
kcc-c2p.py
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
if sys.version_info[0] != 3:
|
||||||
|
print('ERROR: This is Python 3 script!')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
from multiprocessing import freeze_support, set_start_method
|
||||||
|
from kindlecomicconverter.startup import startC2P
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
set_start_method('spawn')
|
||||||
|
freeze_support()
|
||||||
|
startC2P()
|
||||||
123
kcc.iss
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#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;
|
||||||
10
kcc.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"title": "Kindle Comic Converter",
|
||||||
|
"icon": "icons/comic2ebook.icns",
|
||||||
|
"background": "icons/WizardOSX.png",
|
||||||
|
"icon-size": 160,
|
||||||
|
"contents": [
|
||||||
|
{ "x": 180, "y": 300, "type": "file", "path": "dist/Kindle Comic Converter.app" },
|
||||||
|
{ "x": 520, "y": 300, "type": "link", "path": "/Applications" }
|
||||||
|
]
|
||||||
|
}
|
||||||
59
kcc.py
Normal file → Executable file
@@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
#
|
#
|
||||||
# Permission to use, copy, modify, and/or distribute this software for
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
# any purpose with or without fee is hereby granted, provided that the
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
@@ -15,25 +17,40 @@
|
|||||||
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
# 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.
|
||||||
#
|
|
||||||
__version__ = '2.7'
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
from Tkinter import *
|
import sys
|
||||||
from kcc import gui
|
if sys.version_info[0] != 3:
|
||||||
from sys import platform
|
print('ERROR: This is Python 3 script!')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# OS specific workarounds
|
||||||
import os
|
import os
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
os.environ['PATH'] = os.path.dirname(os.path.abspath(sys.executable)) + \
|
||||||
|
'/../Resources:/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/' \
|
||||||
|
'MacOS:/usr/local/bin:/usr/bin:/bin'
|
||||||
|
os.chdir(os.path.dirname(os.path.abspath(sys.executable)) + '/../Resources')
|
||||||
|
else:
|
||||||
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
elif sys.platform.startswith('win'):
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||||
|
else:
|
||||||
|
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/windows/;' + os.environ['PATH']
|
||||||
|
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 kindlecomicconverter.startup import start
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
set_start_method('spawn')
|
||||||
|
freeze_support()
|
||||||
|
start()
|
||||||
|
|
||||||
root = Tk()
|
|
||||||
root.resizable(width=False, height=False)
|
|
||||||
root.config(padx=5, pady=5, takefocus=True)
|
|
||||||
root.title("Kindle Comic Converter v" + __version__)
|
|
||||||
root.wm_attributes("-topmost", 1)
|
|
||||||
if platform == 'darwin':
|
|
||||||
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
|
|
||||||
elif platform == 'win32':
|
|
||||||
root.iconbitmap(default='comic2ebook.ico')
|
|
||||||
gui.MainWindow(master=root)
|
|
||||||
root.mainloop()
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
__version__ = '2.0'
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
import os
|
|
||||||
import zipfile
|
|
||||||
import rarfile
|
|
||||||
|
|
||||||
|
|
||||||
class CBxArchive:
|
|
||||||
def __init__(self, origFileName):
|
|
||||||
self.origFileName = origFileName
|
|
||||||
if zipfile.is_zipfile(origFileName):
|
|
||||||
self.compressor = 'zip'
|
|
||||||
elif rarfile.is_rarfile(origFileName):
|
|
||||||
self.compressor = 'rar'
|
|
||||||
else:
|
|
||||||
self.compressor = None
|
|
||||||
|
|
||||||
def isCbxFile(self):
|
|
||||||
return self.compressor is not None
|
|
||||||
|
|
||||||
def extractCBZ(self, targetdir):
|
|
||||||
cbzFile = zipfile.ZipFile(self.origFileName)
|
|
||||||
for f in cbzFile.namelist():
|
|
||||||
if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
|
|
||||||
pass # skip MacOS special files
|
|
||||||
elif f.endswith('/'):
|
|
||||||
try:
|
|
||||||
os.makedirs(os.path.join(targetdir, f))
|
|
||||||
except:
|
|
||||||
pass # the dir exists so we are going to extract the images only.
|
|
||||||
else:
|
|
||||||
cbzFile.extract(f, targetdir)
|
|
||||||
|
|
||||||
def extractCBR(self, targetdir):
|
|
||||||
cbrFile = rarfile.RarFile(self.origFileName)
|
|
||||||
for f in cbrFile.namelist():
|
|
||||||
if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
|
|
||||||
pass # skip MacOS special files
|
|
||||||
elif f.endswith('/'):
|
|
||||||
try:
|
|
||||||
os.makedirs(os.path.join(targetdir, f))
|
|
||||||
except:
|
|
||||||
pass # the dir exists so we are going to extract the images only.
|
|
||||||
else:
|
|
||||||
cbrFile.extract(f, targetdir)
|
|
||||||
|
|
||||||
def extract(self, targetdir):
|
|
||||||
if self.compressor == 'rar':
|
|
||||||
self.extractCBR(targetdir)
|
|
||||||
elif self.compressor == 'zip':
|
|
||||||
self.extractCBZ(targetdir)
|
|
||||||
adir = os.listdir(targetdir)
|
|
||||||
if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])):
|
|
||||||
import shutil
|
|
||||||
for f in os.listdir(os.path.join(targetdir, adir[0])):
|
|
||||||
shutil.move(os.path.join(targetdir, adir[0], f), targetdir)
|
|
||||||
os.rmdir(os.path.join(targetdir, adir[0]))
|
|
||||||
return targetdir
|
|
||||||
@@ -1,495 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
__version__ = '2.7'
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import re
|
|
||||||
from shutil import move
|
|
||||||
from shutil import copyfile
|
|
||||||
from shutil import copytree
|
|
||||||
from shutil import rmtree
|
|
||||||
from shutil import make_archive
|
|
||||||
from optparse import OptionParser
|
|
||||||
import image
|
|
||||||
import cbxarchive
|
|
||||||
import pdfjpgextract
|
|
||||||
|
|
||||||
|
|
||||||
def buildHTML(path, imgfile):
|
|
||||||
filename = getImageFileName(imgfile)
|
|
||||||
if filename is not None:
|
|
||||||
htmlpath = ''
|
|
||||||
postfix = ''
|
|
||||||
backref = 1
|
|
||||||
head = path
|
|
||||||
while True:
|
|
||||||
head, tail = os.path.split(head)
|
|
||||||
if tail == 'Images':
|
|
||||||
htmlpath = os.path.join(head, 'Text', postfix)
|
|
||||||
break
|
|
||||||
postfix = tail + "/" + postfix
|
|
||||||
backref += 1
|
|
||||||
if not os.path.exists(htmlpath):
|
|
||||||
os.makedirs(htmlpath)
|
|
||||||
htmlfile = os.path.join(htmlpath, filename[0] + '.html')
|
|
||||||
f = open(htmlfile, "w")
|
|
||||||
f.writelines(["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" ",
|
|
||||||
"\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n",
|
|
||||||
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n",
|
|
||||||
"<head>\n",
|
|
||||||
"<title>", filename[0], "</title>\n",
|
|
||||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n",
|
|
||||||
"<link href=\"", "../" * (backref - 1),
|
|
||||||
"stylesheet.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
|
||||||
"<link href=\"", "../" * (backref - 1),
|
|
||||||
"page_styles.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
|
||||||
"</head>\n",
|
|
||||||
"<body class=\"kcc\">\n",
|
|
||||||
"<div class=\"kcc1\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
|
||||||
imgfile, "\" class=\"kcc2\"/></div>\n",
|
|
||||||
#"<div id=\"", filename[0], "-1\">\n",
|
|
||||||
#"<a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"", filename[0],
|
|
||||||
#"-1-magTargetParent\", \"ordinal\":1}'></a>\n",
|
|
||||||
#"</div>\n",
|
|
||||||
#"<div id=\"", filename[0], "-1-magTargetParent\" class=\"target-mag-parent\">\n",
|
|
||||||
#"<div class=\"target-mag-lb\">\n",
|
|
||||||
#"</div>\n",
|
|
||||||
#"<div id=\"", filename[0], "-1-magTarget\" class=\"target-mag\">\n",
|
|
||||||
#"<img src=\"../" * backref, "Images/", postfix, imgfile, "\" alt=\"", imgfile, "\"/>\n",
|
|
||||||
#"</div></div>\n",
|
|
||||||
"</body>\n",
|
|
||||||
"</html>"
|
|
||||||
])
|
|
||||||
f.close()
|
|
||||||
return path, imgfile
|
|
||||||
|
|
||||||
|
|
||||||
def buildBlankHTML(path):
|
|
||||||
f = open(os.path.join(path, 'blank.html'), "w")
|
|
||||||
f.writelines(["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" ",
|
|
||||||
"\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n",
|
|
||||||
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n",
|
|
||||||
"<head>\n",
|
|
||||||
"<title></title>\n",
|
|
||||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n",
|
|
||||||
"</head>\n",
|
|
||||||
"<body>\n",
|
|
||||||
"</body>\n",
|
|
||||||
"</html>"])
|
|
||||||
f.close()
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def buildNCX(dstdir, title, chapters):
|
|
||||||
ncxfile = os.path.join(dstdir, 'OEBPS', 'toc.ncx')
|
|
||||||
f = open(ncxfile, "w")
|
|
||||||
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
|
|
||||||
"<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\" ",
|
|
||||||
"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n",
|
|
||||||
"<ncx version=\"2005-1\" xml:lang=\"en-US\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n",
|
|
||||||
"<head>\n",
|
|
||||||
"<meta name=\"dtb:uid\" content=\"015ffaec-9340-42f8-b163-a0c5ab7d0611\"/>\n",
|
|
||||||
"<meta name=\"dtb:depth\" content=\"2\"/>\n",
|
|
||||||
"<meta name=\"dtb:totalPageCount\" content=\"0\"/>\n",
|
|
||||||
"<meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n",
|
|
||||||
"</head>\n",
|
|
||||||
"<docTitle><text>", title, "</text></docTitle>\n",
|
|
||||||
"<navMap>"
|
|
||||||
])
|
|
||||||
for chapter in chapters:
|
|
||||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
|
||||||
title = os.path.basename(folder)
|
|
||||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
|
||||||
f.write("<navPoint id=\"" + folder.replace('/', '_').replace('\\', '_') + "\"><navLabel><text>" + title
|
|
||||||
+ "</text></navLabel><content src=\"" + filename[0] + ".html\"/></navPoint>\n")
|
|
||||||
f.write("</navMap>\n</ncx>")
|
|
||||||
f.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False):
|
|
||||||
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
|
|
||||||
# read the first file resolution
|
|
||||||
profilelabel, deviceres, palette, gamma = image.ProfileData.Profiles[profile]
|
|
||||||
imgres = str(deviceres[0]) + "x" + str(deviceres[1])
|
|
||||||
if righttoleft:
|
|
||||||
writingmode = "horizontal-rl"
|
|
||||||
facing = "right"
|
|
||||||
facing1 = "right"
|
|
||||||
facing2 = "left"
|
|
||||||
else:
|
|
||||||
writingmode = "horizontal-lr"
|
|
||||||
facing = "left"
|
|
||||||
facing1 = "left"
|
|
||||||
facing2 = "right"
|
|
||||||
from uuid import uuid4
|
|
||||||
uuid = str(uuid4())
|
|
||||||
uuid = uuid.encode('utf-8')
|
|
||||||
f = open(opffile, "w")
|
|
||||||
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
|
|
||||||
"<package version=\"2.0\" unique-identifier=\"BookID\" xmlns=\"http://www.idpf.org/2007/opf\">\n",
|
|
||||||
"<metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\" ",
|
|
||||||
"xmlns:opf=\"http://www.idpf.org/2007/opf\">\n",
|
|
||||||
"<dc:title>", title, "</dc:title>\n",
|
|
||||||
"<dc:language>en-US</dc:language>\n",
|
|
||||||
"<dc:identifier id=\"BookID\" opf:scheme=\"UUID\">", uuid, "</dc:identifier>\n",
|
|
||||||
"<meta name=\"RegionMagnification\" content=\"true\"/>\n",
|
|
||||||
"<meta name=\"cover\" content=\"cover\"/>\n",
|
|
||||||
"<meta name=\"book-type\" content=\"comic\"/>\n",
|
|
||||||
"<meta name=\"zero-gutter\" content=\"true\"/>\n",
|
|
||||||
"<meta name=\"zero-margin\" content=\"true\"/>\n",
|
|
||||||
"<meta name=\"fixed-layout\" content=\"true\"/>\n",
|
|
||||||
"<meta name=\"orientation-lock\" content=\"none\"/>\n",
|
|
||||||
"<meta name=\"original-resolution\" content=\"", imgres, "\"/>\n",
|
|
||||||
"<meta name=\"primary-writing-mode\" content=\"", writingmode, "\"/>\n",
|
|
||||||
"<meta name=\"rendition:layout\" content=\"pre-paginated\"/>\n",
|
|
||||||
"<meta name=\"rendition:orientation\" content=\"auto\"/>\n",
|
|
||||||
"</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ",
|
|
||||||
"media-type=\"application/x-dtbncx+xml\"/>\n"
|
|
||||||
])
|
|
||||||
# set cover
|
|
||||||
if cover is not None:
|
|
||||||
filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\'))
|
|
||||||
if '.png' == filename[1]:
|
|
||||||
mt = 'image/png'
|
|
||||||
else:
|
|
||||||
mt = 'image/jpeg'
|
|
||||||
f.write("<item id=\"cover\" href=\"" + filename[0] + filename[1] + "\" media-type=\"" + mt + "\"/>\n")
|
|
||||||
reflist = []
|
|
||||||
for path in filelist:
|
|
||||||
folder = path[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
|
||||||
filename = getImageFileName(path[1])
|
|
||||||
uniqueid = os.path.join(folder, filename[0]).replace('/', '_').replace('\\', '_')
|
|
||||||
reflist.append(uniqueid)
|
|
||||||
f.write("<item id=\"page_" + uniqueid + "\" href=\""
|
|
||||||
+ folder.replace('Images', 'Text') + "/" + filename[0]
|
|
||||||
+ ".html\" media-type=\"application/xhtml+xml\"/>\n")
|
|
||||||
if '.png' == filename[1]:
|
|
||||||
mt = 'image/png'
|
|
||||||
else:
|
|
||||||
mt = 'image/jpeg'
|
|
||||||
f.write("<item id=\"img_" + uniqueid + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\""
|
|
||||||
+ mt + "\"/>\n")
|
|
||||||
if (options.profile == 'K4' or options.profile == 'KHD') and splitCount > 0:
|
|
||||||
splitCountUsed = 1
|
|
||||||
while splitCountUsed <= splitCount:
|
|
||||||
f.write("<item id=\"blank-page" + str(splitCountUsed) +
|
|
||||||
"\" href=\"Text/blank.html\" media-type=\"application/xhtml+xml\"/>\n")
|
|
||||||
splitCountUsed += 1
|
|
||||||
f.write("</manifest>\n<spine toc=\"ncx\">\n")
|
|
||||||
splitCountUsed = 1
|
|
||||||
for entry in reflist:
|
|
||||||
if entry.endswith("-1"):
|
|
||||||
if ((righttoleft and facing == 'left') or (not righttoleft and facing == 'right')) and \
|
|
||||||
(options.profile == 'K4' or options.profile == 'KHD'):
|
|
||||||
f.write("<itemref idref=\"blank-page" + str(splitCountUsed) + "\" properties=\"layout-blank\"/>\n")
|
|
||||||
splitCountUsed += 1
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing1 + "\"/>\n")
|
|
||||||
elif entry.endswith("-2"):
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing2 + "\"/>\n")
|
|
||||||
if righttoleft:
|
|
||||||
facing = "right"
|
|
||||||
else:
|
|
||||||
facing = "left"
|
|
||||||
else:
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing + "\"/>\n")
|
|
||||||
if facing == 'right':
|
|
||||||
facing = 'left'
|
|
||||||
else:
|
|
||||||
facing = 'right'
|
|
||||||
f.write("</spine>\n<guide>\n</guide>\n</package>\n")
|
|
||||||
f.close()
|
|
||||||
# finish with standard ePub folders
|
|
||||||
os.mkdir(os.path.join(dstdir, 'META-INF'))
|
|
||||||
f = open(os.path.join(dstdir, 'mimetype'), 'w')
|
|
||||||
f.write('application/epub+zip')
|
|
||||||
f.close()
|
|
||||||
f = open(os.path.join(dstdir, 'META-INF', 'container.xml'), 'w')
|
|
||||||
f.writelines(["<?xml version=\"1.0\"?>\n",
|
|
||||||
"<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n",
|
|
||||||
"<rootfiles>\n",
|
|
||||||
"<rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n",
|
|
||||||
"</rootfiles>\n",
|
|
||||||
"</container>"])
|
|
||||||
f.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def getImageFileName(imgfile):
|
|
||||||
filename = os.path.splitext(imgfile)
|
|
||||||
if filename[0].startswith('.') or\
|
|
||||||
(filename[1].lower() != '.png' and
|
|
||||||
filename[1].lower() != '.jpg' and
|
|
||||||
filename[1].lower() != '.jpeg'):
|
|
||||||
return None
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
|
||||||
def isInFilelist(filename, filelist):
|
|
||||||
filename = os.path.splitext(filename)
|
|
||||||
seen = False
|
|
||||||
for item in filelist:
|
|
||||||
if filename[0] == item[0]:
|
|
||||||
seen = True
|
|
||||||
return seen
|
|
||||||
|
|
||||||
|
|
||||||
def applyImgOptimization(img, isSplit=False, toRight=False):
|
|
||||||
img.optimizeImage(options.gamma)
|
|
||||||
img.cropWhiteSpace(10.0)
|
|
||||||
if options.cutpagenumbers:
|
|
||||||
img.cutPageNumber()
|
|
||||||
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight)
|
|
||||||
if not options.notquantize:
|
|
||||||
img.quantizeImage()
|
|
||||||
|
|
||||||
|
|
||||||
def dirImgProcess(path):
|
|
||||||
global options, splitCount
|
|
||||||
if options.righttoleft:
|
|
||||||
facing = "right"
|
|
||||||
else:
|
|
||||||
facing = "left"
|
|
||||||
|
|
||||||
for (dirpath, dirnames, filenames) in os.walk(path):
|
|
||||||
for afile in filenames:
|
|
||||||
if getImageFileName(afile) is not None:
|
|
||||||
if options.verbose:
|
|
||||||
print "Optimizing " + afile + " for " + options.profile
|
|
||||||
else:
|
|
||||||
print ".",
|
|
||||||
img = image.ComicPage(os.path.join(dirpath, afile), options.profile)
|
|
||||||
if options.nosplitrotate:
|
|
||||||
split = None
|
|
||||||
else:
|
|
||||||
split = img.splitPage(dirpath, options.righttoleft, options.rotate)
|
|
||||||
if split is not None:
|
|
||||||
if options.verbose:
|
|
||||||
print "Splitted " + afile
|
|
||||||
if options.righttoleft:
|
|
||||||
toRight1 = False
|
|
||||||
toRight2 = True
|
|
||||||
else:
|
|
||||||
toRight1 = True
|
|
||||||
toRight2 = False
|
|
||||||
if options.righttoleft:
|
|
||||||
if facing == "left":
|
|
||||||
splitCount += 1
|
|
||||||
facing = "right"
|
|
||||||
else:
|
|
||||||
if facing == "right":
|
|
||||||
splitCount += 1
|
|
||||||
facing = "left"
|
|
||||||
img0 = image.ComicPage(split[0], options.profile)
|
|
||||||
applyImgOptimization(img0, True, toRight1)
|
|
||||||
img0.saveToDir(dirpath, options.notquantize)
|
|
||||||
img1 = image.ComicPage(split[1], options.profile)
|
|
||||||
applyImgOptimization(img1, True, toRight2)
|
|
||||||
img1.saveToDir(dirpath, options.notquantize)
|
|
||||||
else:
|
|
||||||
if facing == "right":
|
|
||||||
facing = "left"
|
|
||||||
else:
|
|
||||||
facing = "right"
|
|
||||||
applyImgOptimization(img)
|
|
||||||
img.saveToDir(dirpath, options.notquantize)
|
|
||||||
|
|
||||||
|
|
||||||
def genEpubStruct(path):
|
|
||||||
global options
|
|
||||||
filelist = []
|
|
||||||
chapterlist = []
|
|
||||||
cover = None
|
|
||||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
|
||||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'page_styles.css'), 'w')
|
|
||||||
f.writelines(["@page {\n",
|
|
||||||
" margin-bottom: 0;\n",
|
|
||||||
" margin-top: 0\n",
|
|
||||||
"}\n"])
|
|
||||||
f.close()
|
|
||||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'stylesheet.css'), 'w')
|
|
||||||
f.writelines([".kcc {\n",
|
|
||||||
" display: block;\n",
|
|
||||||
" margin-bottom: 0;\n",
|
|
||||||
" margin-left: 0;\n",
|
|
||||||
" margin-right: 0;\n",
|
|
||||||
" margin-top: 0;\n",
|
|
||||||
" padding-bottom: 0;\n",
|
|
||||||
" padding-left: 0;\n",
|
|
||||||
" padding-right: 0;\n",
|
|
||||||
" padding-top: 0;\n",
|
|
||||||
" text-align: left\n",
|
|
||||||
"}\n",
|
|
||||||
".kcc1 {\n",
|
|
||||||
" display: block;\n",
|
|
||||||
" text-align: center\n",
|
|
||||||
"}\n",
|
|
||||||
".kcc2 {\n",
|
|
||||||
" height: auto;\n",
|
|
||||||
" width: auto\n",
|
|
||||||
"}\n"])
|
|
||||||
f.close()
|
|
||||||
for (dirpath, dirnames, filenames) in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
|
||||||
chapter = False
|
|
||||||
for afile in filenames:
|
|
||||||
filename = getImageFileName(afile)
|
|
||||||
if filename is not None:
|
|
||||||
# put credits at the end
|
|
||||||
if "credit" in afile.lower():
|
|
||||||
os.rename(os.path.join(dirpath, afile), os.path.join(dirpath, 'ZZZ999_' + afile))
|
|
||||||
afile = 'ZZZ999_' + afile
|
|
||||||
if "+" in afile.lower() or "#" in afile.lower():
|
|
||||||
newfilename = afile.replace('+', '_').replace('#', '_')
|
|
||||||
os.rename(os.path.join(dirpath, afile), os.path.join(dirpath, newfilename))
|
|
||||||
afile = newfilename
|
|
||||||
filelist.append(buildHTML(dirpath, afile))
|
|
||||||
if not chapter:
|
|
||||||
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
|
|
||||||
chapter = True
|
|
||||||
if cover is None:
|
|
||||||
cover = os.path.join(filelist[-1][0], 'cover' + getImageFileName(filelist[-1][1])[1])
|
|
||||||
copyfile(os.path.join(filelist[-1][0], filelist[-1][1]), cover)
|
|
||||||
buildNCX(path, options.title, chapterlist)
|
|
||||||
# ensure we're sorting files alphabetically
|
|
||||||
convert = lambda text: int(text) if text.isdigit() else text
|
|
||||||
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
|
|
||||||
filelist.sort(key=lambda name: (alphanum_key(name[0].lower()), alphanum_key(name[1].lower())))
|
|
||||||
buildOPF(options.profile, path, options.title, filelist, cover, options.righttoleft)
|
|
||||||
if (options.profile == 'K4' or options.profile == 'KHD') and splitCount > 0:
|
|
||||||
filelist.append(buildBlankHTML(os.path.join(path, 'OEBPS', 'Text')))
|
|
||||||
|
|
||||||
|
|
||||||
def getWorkFolder(afile):
|
|
||||||
workdir = tempfile.mkdtemp()
|
|
||||||
if os.path.isdir(afile):
|
|
||||||
try:
|
|
||||||
import shutil
|
|
||||||
os.rmdir(workdir) # needed for copytree() fails if dst already exists
|
|
||||||
copytree(afile, workdir)
|
|
||||||
path = workdir
|
|
||||||
except OSError:
|
|
||||||
raise
|
|
||||||
elif afile.lower().endswith('.pdf'):
|
|
||||||
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
|
||||||
path = pdf.extract()
|
|
||||||
else:
|
|
||||||
cbx = cbxarchive.CBxArchive(afile)
|
|
||||||
if cbx.isCbxFile():
|
|
||||||
try:
|
|
||||||
path = cbx.extract(workdir)
|
|
||||||
except OSError:
|
|
||||||
print 'Unrar not found, please download from ' + \
|
|
||||||
'http://www.rarlab.com/download.htm and put into your PATH.'
|
|
||||||
sys.exit(21)
|
|
||||||
else:
|
|
||||||
raise TypeError
|
|
||||||
move(path, path + "_temp")
|
|
||||||
move(path + "_temp", os.path.join(path, 'OEBPS', 'Images'))
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def Copyright():
|
|
||||||
print ('comic2ebook v%(__version__)s. '
|
|
||||||
'Written 2012 by Ciro Mattia Gonano.' % globals())
|
|
||||||
|
|
||||||
|
|
||||||
def Usage():
|
|
||||||
print "Generates HTML, NCX and OPF for a Comic ebook from a bunch of images."
|
|
||||||
print "Optimized for creating MOBI files to be read on Kindle Paperwhite."
|
|
||||||
parser.print_help()
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
|
||||||
global parser, options, epub_path, splitCount
|
|
||||||
usage = "Usage: %prog [options] comic_file|comic_folder"
|
|
||||||
parser = OptionParser(usage=usage, version=__version__)
|
|
||||||
parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
|
|
||||||
help="Device profile (Choose one among K1, K2, K3, K4, KDX, KDXG or KHD) [Default=KHD]")
|
|
||||||
parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
|
|
||||||
help="Comic title [Default=filename]")
|
|
||||||
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
|
||||||
help="Manga style (Right-to-left reading and splitting) [Default=False]")
|
|
||||||
parser.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
|
|
||||||
help="Do not apply image preprocessing (Page splitting and optimizations) [Default=True]")
|
|
||||||
parser.add_option("--nodithering", action="store_true", dest="notquantize", default=False,
|
|
||||||
help="Disable image quantization [Default=False]")
|
|
||||||
parser.add_option("--gamma", type="float", dest="gamma", default="0.0",
|
|
||||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
|
||||||
parser.add_option("--upscale", action="store_true", dest="upscale", default=False,
|
|
||||||
help="Resize images smaller than device's resolution [Default=False]")
|
|
||||||
parser.add_option("--stretch", action="store_true", dest="stretch", default=False,
|
|
||||||
help="Stretch images to device's resolution [Default=False]")
|
|
||||||
parser.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
|
|
||||||
help="Use black borders instead of white ones when not stretching and ratio "
|
|
||||||
+ "is not like the device's one [Default=False]")
|
|
||||||
parser.add_option("--rotate", action="store_true", dest="rotate", default=False,
|
|
||||||
help="Rotate landscape pages instead of splitting them [Default=False]")
|
|
||||||
parser.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
|
|
||||||
help="Disable splitting and rotation [Default=False]")
|
|
||||||
parser.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
|
|
||||||
help="Do not try to cut page numbering on images [Default=True]")
|
|
||||||
parser.add_option("-o", "--output", action="store", dest="output", default=None,
|
|
||||||
help="Output generated EPUB to specified directory or file")
|
|
||||||
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
|
||||||
help="Verbose output [Default=False]")
|
|
||||||
options, args = parser.parse_args(argv)
|
|
||||||
if len(args) != 1:
|
|
||||||
parser.print_help()
|
|
||||||
return
|
|
||||||
path = getWorkFolder(args[0])
|
|
||||||
if options.title == 'defaulttitle':
|
|
||||||
options.title = os.path.splitext(os.path.basename(args[0]))[0]
|
|
||||||
splitCount = 0
|
|
||||||
if options.imgproc:
|
|
||||||
print "Processing images..."
|
|
||||||
dirImgProcess(path + "/OEBPS/Images/")
|
|
||||||
print "\nCreating ePub structure..."
|
|
||||||
genEpubStruct(path)
|
|
||||||
# actually zip the ePub
|
|
||||||
if options.output is not None:
|
|
||||||
if options.output.endswith('.epub'):
|
|
||||||
epubpath = os.path.abspath(options.output)
|
|
||||||
elif os.path.isdir(args[0]):
|
|
||||||
epubpath = os.path.abspath(options.output) + "/" + os.path.basename(args[0]) + '.epub'
|
|
||||||
else:
|
|
||||||
epubpath = os.path.abspath(options.output) + "/" \
|
|
||||||
+ os.path.basename(os.path.splitext(args[0])[0]) + '.epub'
|
|
||||||
elif os.path.isdir(args[0]):
|
|
||||||
epubpath = args[0] + '.epub'
|
|
||||||
else:
|
|
||||||
epubpath = os.path.splitext(args[0])[0] + '.epub'
|
|
||||||
make_archive(path + '_comic', 'zip', path)
|
|
||||||
move(path + '_comic.zip', epubpath)
|
|
||||||
rmtree(path)
|
|
||||||
return epubpath
|
|
||||||
|
|
||||||
|
|
||||||
def getEpubPath():
|
|
||||||
global epub_path
|
|
||||||
return epub_path
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
Copyright()
|
|
||||||
main(sys.argv[1:])
|
|
||||||
sys.exit(0)
|
|
||||||
260
kcc/gui.py
@@ -1,260 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Ciro Mattia Gonano <ciromattia@gmail.com>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
from Tkinter import *
|
|
||||||
import tkFileDialog
|
|
||||||
import tkMessageBox
|
|
||||||
import ttk
|
|
||||||
import comic2ebook
|
|
||||||
import kindlestrip
|
|
||||||
from image import ProfileData
|
|
||||||
from subprocess import call
|
|
||||||
from subprocess import check_call
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import stat
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow:
|
|
||||||
|
|
||||||
def clear_files(self):
|
|
||||||
self.filelist = []
|
|
||||||
self.refresh_list()
|
|
||||||
|
|
||||||
def change_gamma(self):
|
|
||||||
if self.aEntry['state'] == DISABLED:
|
|
||||||
self.aEntry['state'] = NORMAL
|
|
||||||
else:
|
|
||||||
self.aEntry['state'] = DISABLED
|
|
||||||
|
|
||||||
def open_files(self):
|
|
||||||
filetypes = [('All files', '.*'), ('Comic files', ('*.cbr', '*.cbz', '*.zip', '*.rar', '*.pdf'))]
|
|
||||||
f = tkFileDialog.askopenfilenames(title="Choose files", filetypes=filetypes)
|
|
||||||
if not isinstance(f, tuple):
|
|
||||||
f = self.master.tk.splitlist(f)
|
|
||||||
self.filelist.extend(f)
|
|
||||||
self.refresh_list()
|
|
||||||
|
|
||||||
def open_folder(self):
|
|
||||||
f = tkFileDialog.askdirectory(title="Choose folder:")
|
|
||||||
self.filelist.extend([f])
|
|
||||||
self.refresh_list()
|
|
||||||
|
|
||||||
def refresh_list(self):
|
|
||||||
self.filelocation.config(state=NORMAL)
|
|
||||||
self.filelocation.delete(0, END)
|
|
||||||
for afile in self.filelist:
|
|
||||||
self.filelocation.insert(END, afile)
|
|
||||||
self.filelocation.config(state=DISABLED)
|
|
||||||
try:
|
|
||||||
if len(self.filelist) > 0:
|
|
||||||
self.submit['state'] = NORMAL
|
|
||||||
else:
|
|
||||||
self.submit['state'] = DISABLED
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
self.filelocation = Listbox(self.master)
|
|
||||||
self.filelocation.grid(row=0, columnspan=4, sticky=W + E + N + S)
|
|
||||||
self.refresh_list()
|
|
||||||
|
|
||||||
self.clear_file = Button(self.master, text="Clear files", command=self.clear_files)
|
|
||||||
self.clear_file.grid(row=4, column=0, sticky=W + E + N + S)
|
|
||||||
self.open_file = Button(self.master, text="Add files", command=self.open_files)
|
|
||||||
self.open_file.grid(row=4, column=1, sticky=W + E + N + S)
|
|
||||||
self.open_folder = Button(self.master, text="Add folder", command=self.open_folder)
|
|
||||||
self.open_folder.grid(row=4, column=2, sticky=W + E + N + S)
|
|
||||||
|
|
||||||
self.profile = StringVar()
|
|
||||||
profiles = sorted(ProfileData.ProfileLabels.iterkeys())
|
|
||||||
self.profile.set(profiles[-1])
|
|
||||||
w = apply(OptionMenu, (self.master, self.profile) + tuple(profiles))
|
|
||||||
w.grid(row=4, column=3, sticky=W + E + N + S)
|
|
||||||
|
|
||||||
self.options = {
|
|
||||||
'Aepub_only': IntVar(None, 0),
|
|
||||||
'Bmangastyle': IntVar(None, 0),
|
|
||||||
'Cimage_preprocess': IntVar(None, 0),
|
|
||||||
'Dnotquantize': IntVar(None, 0),
|
|
||||||
'Eimage_gamma': DoubleVar(None, 0.0),
|
|
||||||
'Fimage_upscale': IntVar(None, 0),
|
|
||||||
'Gimage_stretch': IntVar(None, 0),
|
|
||||||
'Hblack_borders': IntVar(None, 0),
|
|
||||||
'Irotate': IntVar(None, 0),
|
|
||||||
'Jnosplitrotate': IntVar(None, 0),
|
|
||||||
'Kcut_page_numbers': IntVar(None, 0)
|
|
||||||
}
|
|
||||||
self.optionlabels = {
|
|
||||||
'Aepub_only': "Generate EPUB only",
|
|
||||||
'Cimage_preprocess': "Disable image optimizations",
|
|
||||||
'Dnotquantize': "Disable image quantization",
|
|
||||||
'Jnosplitrotate': "Disable splitting and rotation",
|
|
||||||
'Irotate': "Rotate images instead splitting them",
|
|
||||||
'Kcut_page_numbers': "Disable page numbers cutting",
|
|
||||||
'Bmangastyle': "Manga mode",
|
|
||||||
'Eimage_gamma': "Custom gamma correction",
|
|
||||||
'Fimage_upscale': "Allow image upscaling",
|
|
||||||
'Gimage_stretch': "Stretch images",
|
|
||||||
'Hblack_borders': "Use black borders"
|
|
||||||
}
|
|
||||||
self.optionsButtons = {}
|
|
||||||
for key in sorted(self.options):
|
|
||||||
if isinstance(self.options[key], IntVar) or isinstance(self.options[key], BooleanVar):
|
|
||||||
self.optionsButtons[key] = Checkbutton(self.master, text=self.optionlabels[key],
|
|
||||||
variable=self.options[key])
|
|
||||||
self.optionsButtons[key].grid(columnspan=4, sticky=W + N + S)
|
|
||||||
elif isinstance(self.options[key], DoubleVar):
|
|
||||||
self.optionsButtons[key] = Checkbutton(self.master, text=self.optionlabels[key],
|
|
||||||
command=self.change_gamma)
|
|
||||||
self.optionsButtons[key].grid(columnspan=4, sticky=W + N + S)
|
|
||||||
self.aEntry = Entry(self.master, textvariable=self.options[key])
|
|
||||||
self.aEntry['state'] = DISABLED
|
|
||||||
self.aEntry.grid(column=3, row=(self.master.grid_size()[1] - 1), sticky=W + N + S)
|
|
||||||
|
|
||||||
self.submit = Button(self.master, text="CONVERT", command=self.start_conversion, fg="red", state=DISABLED)
|
|
||||||
self.submit.grid(columnspan=4, sticky=W + E + N + S)
|
|
||||||
aLabel = Label(self.master, text="File progress:", anchor=W, justify=LEFT)
|
|
||||||
aLabel.grid(column=0, sticky=E)
|
|
||||||
self.progress_file = ttk.Progressbar(orient=HORIZONTAL, length=200, mode='determinate', maximum=4)
|
|
||||||
self.progress_file.grid(column=1, columnspan=3, row=(self.master.grid_size()[1] - 1), sticky=W + E + N + S)
|
|
||||||
aLabel = Label(self.master, text="Overall progress:", anchor=W, justify=LEFT)
|
|
||||||
aLabel.grid(column=0, sticky=E)
|
|
||||||
self.progress_overall = ttk.Progressbar(orient=HORIZONTAL, length=200, mode='determinate')
|
|
||||||
self.progress_overall.grid(column=1, columnspan=3, row=(self.master.grid_size()[1] - 1), sticky=W + E + N + S)
|
|
||||||
|
|
||||||
retcode = call("kindlegen", shell=True)
|
|
||||||
if retcode == 1:
|
|
||||||
self.optionsButtons['Aepub_only'].select()
|
|
||||||
self.optionsButtons['Aepub_only']['state'] = DISABLED
|
|
||||||
|
|
||||||
def start_conversion(self):
|
|
||||||
self.submit['state'] = DISABLED
|
|
||||||
self.master.update()
|
|
||||||
self.convert()
|
|
||||||
self.submit['state'] = NORMAL
|
|
||||||
self.master.update()
|
|
||||||
|
|
||||||
def convert(self):
|
|
||||||
if len(self.filelist) < 1:
|
|
||||||
tkMessageBox.showwarning('No files selected!', "Please choose files to convert.")
|
|
||||||
return
|
|
||||||
profilekey = ProfileData.ProfileLabels[self.profile.get()]
|
|
||||||
argv = ["-p", profilekey]
|
|
||||||
if self.options['Eimage_gamma'].get() != 0.0:
|
|
||||||
argv.append("--gamma")
|
|
||||||
argv.append(self.options['Eimage_gamma'].get())
|
|
||||||
if self.options['Cimage_preprocess'].get() == 1:
|
|
||||||
argv.append("--noprocessing")
|
|
||||||
if self.options['Dnotquantize'].get() == 1:
|
|
||||||
argv.append("--nodithering")
|
|
||||||
if self.options['Jnosplitrotate'].get() == 1:
|
|
||||||
argv.append("--nosplitrotate")
|
|
||||||
if self.options['Irotate'].get() == 1:
|
|
||||||
argv.append("--rotate")
|
|
||||||
if self.options['Kcut_page_numbers'].get() == 1:
|
|
||||||
argv.append("--nocutpagenumbers")
|
|
||||||
if self.options['Bmangastyle'].get() == 1:
|
|
||||||
argv.append("-m")
|
|
||||||
if self.options['Fimage_upscale'].get() == 1:
|
|
||||||
argv.append("--upscale")
|
|
||||||
if self.options['Gimage_stretch'].get() == 1:
|
|
||||||
argv.append("--stretch")
|
|
||||||
if self.options['Hblack_borders'].get() == 1:
|
|
||||||
argv.append("--blackborders")
|
|
||||||
errors = False
|
|
||||||
left_files = len(self.filelist)
|
|
||||||
filenum = 0
|
|
||||||
self.progress_overall['value'] = 0
|
|
||||||
self.progress_overall['maximum'] = left_files
|
|
||||||
for entry in self.filelist:
|
|
||||||
self.progress_overall['value'] = filenum
|
|
||||||
self.progress_file['value'] = 1
|
|
||||||
self.master.update()
|
|
||||||
filenum += 1
|
|
||||||
subargv = list(argv)
|
|
||||||
try:
|
|
||||||
subargv.append(entry)
|
|
||||||
epub_path = comic2ebook.main(subargv)
|
|
||||||
self.progress_file['value'] = 2
|
|
||||||
self.master.update()
|
|
||||||
except Exception as err:
|
|
||||||
type_, value_, traceback_ = sys.exc_info()
|
|
||||||
tkMessageBox.showerror('KCC Error', "Error on file %s:\n%s\nTraceback:\n%s" %
|
|
||||||
(subargv[-1], str(err), traceback.format_tb(traceback_)))
|
|
||||||
errors = True
|
|
||||||
continue
|
|
||||||
if self.options['Aepub_only'].get() == 0:
|
|
||||||
try:
|
|
||||||
if os.path.getsize(epub_path) > 335544320:
|
|
||||||
# do not call kindlegen if source is bigger than 320MB
|
|
||||||
tkMessageBox.showwarning('KindleGen Warning',
|
|
||||||
"ePub file %s is bigger than 320MB, not suitable for kindlegen" %
|
|
||||||
epub_path)
|
|
||||||
continue
|
|
||||||
retcode = call("kindlegen \"" + epub_path + "\"", shell=True)
|
|
||||||
if retcode < 0:
|
|
||||||
print >>sys.stderr, "Child was terminated by signal", -retcode
|
|
||||||
else:
|
|
||||||
print >>sys.stderr, "Child returned", retcode
|
|
||||||
self.progress_file['value'] = 3
|
|
||||||
self.master.update()
|
|
||||||
except OSError as e:
|
|
||||||
tkMessageBox.showerror('KindleGen Error', "Error on file %s:\n%s" % (epub_path, e))
|
|
||||||
errors = True
|
|
||||||
continue
|
|
||||||
mobifile = epub_path.replace('.epub', '.mobi')
|
|
||||||
try:
|
|
||||||
shutil.move(mobifile, mobifile + '_tostrip')
|
|
||||||
kindlestrip.main((mobifile + '_tostrip', mobifile))
|
|
||||||
os.remove(mobifile + '_tostrip')
|
|
||||||
self.progress_file['value'] = 4
|
|
||||||
self.master.update()
|
|
||||||
except Exception, err:
|
|
||||||
tkMessageBox.showerror('KindleStrip Error', "Error on file %s:\n%s" % (mobifile, str(err)))
|
|
||||||
errors = True
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
self.progress_file['value'] = 4
|
|
||||||
self.master.update()
|
|
||||||
if errors:
|
|
||||||
tkMessageBox.showwarning("Done", "Conversion completed with errors.")
|
|
||||||
else:
|
|
||||||
tkMessageBox.showinfo("Done", "Conversion successful!")
|
|
||||||
# reset progressbars
|
|
||||||
self.progress_overall['value'] = 0
|
|
||||||
self.progress_file['value'] = 0
|
|
||||||
self.master.update()
|
|
||||||
|
|
||||||
def remove_readonly(self, fn, path):
|
|
||||||
if fn is os.rmdir:
|
|
||||||
os.chmod(path, stat.S_IWRITE)
|
|
||||||
os.rmdir(path)
|
|
||||||
elif fn is os.remove:
|
|
||||||
os.chmod(path, stat.S_IWRITE)
|
|
||||||
os.remove(path)
|
|
||||||
|
|
||||||
def __init__(self, master):
|
|
||||||
self.filelist = []
|
|
||||||
self.master = master
|
|
||||||
self.initialize()
|
|
||||||
369
kcc/image.py
@@ -1,369 +0,0 @@
|
|||||||
# Copyright (C) 2010 Alex Yatskov
|
|
||||||
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
|
|
||||||
# Copyright (C) 2012-2013 Ciro Mattia Gonano <ciromattia@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
import os
|
|
||||||
from PIL import Image, ImageOps, ImageDraw, ImageStat
|
|
||||||
|
|
||||||
|
|
||||||
class ImageFlags:
|
|
||||||
Orient = 1 << 0
|
|
||||||
Resize = 1 << 1
|
|
||||||
Frame = 1 << 2
|
|
||||||
Quantize = 1 << 3
|
|
||||||
Stretch = 1 << 4
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileData:
|
|
||||||
Palette4 = [
|
|
||||||
0x00, 0x00, 0x00,
|
|
||||||
0x55, 0x55, 0x55,
|
|
||||||
0xaa, 0xaa, 0xaa,
|
|
||||||
0xff, 0xff, 0xff
|
|
||||||
]
|
|
||||||
|
|
||||||
Palette15 = [
|
|
||||||
0x00, 0x00, 0x00,
|
|
||||||
0x11, 0x11, 0x11,
|
|
||||||
0x22, 0x22, 0x22,
|
|
||||||
0x33, 0x33, 0x33,
|
|
||||||
0x44, 0x44, 0x44,
|
|
||||||
0x55, 0x55, 0x55,
|
|
||||||
0x66, 0x66, 0x66,
|
|
||||||
0x77, 0x77, 0x77,
|
|
||||||
0x88, 0x88, 0x88,
|
|
||||||
0x99, 0x99, 0x99,
|
|
||||||
0xaa, 0xaa, 0xaa,
|
|
||||||
0xbb, 0xbb, 0xbb,
|
|
||||||
0xcc, 0xcc, 0xcc,
|
|
||||||
0xdd, 0xdd, 0xdd,
|
|
||||||
0xff, 0xff, 0xff,
|
|
||||||
]
|
|
||||||
|
|
||||||
Palette16 = [
|
|
||||||
0x00, 0x00, 0x00,
|
|
||||||
0x11, 0x11, 0x11,
|
|
||||||
0x22, 0x22, 0x22,
|
|
||||||
0x33, 0x33, 0x33,
|
|
||||||
0x44, 0x44, 0x44,
|
|
||||||
0x55, 0x55, 0x55,
|
|
||||||
0x66, 0x66, 0x66,
|
|
||||||
0x77, 0x77, 0x77,
|
|
||||||
0x88, 0x88, 0x88,
|
|
||||||
0x99, 0x99, 0x99,
|
|
||||||
0xaa, 0xaa, 0xaa,
|
|
||||||
0xbb, 0xbb, 0xbb,
|
|
||||||
0xcc, 0xcc, 0xcc,
|
|
||||||
0xdd, 0xdd, 0xdd,
|
|
||||||
0xee, 0xee, 0xee,
|
|
||||||
0xff, 0xff, 0xff,
|
|
||||||
]
|
|
||||||
|
|
||||||
Profiles = {
|
|
||||||
'K1': ("Kindle", (600, 800), Palette4, 1.8),
|
|
||||||
'K2': ("Kindle 2", (600, 800), Palette15, 1.8),
|
|
||||||
'K3': ("Kindle 3/Keyboard", (600, 800), Palette16, 1.8),
|
|
||||||
'K4': ("Kindle 4/NT/Touch", (600, 800), Palette16, 1.8),
|
|
||||||
'KHD': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8),
|
|
||||||
'KDX': ("Kindle DX", (824, 1200), Palette15, 1.8),
|
|
||||||
'KDXG': ("Kindle DXG", (824, 1200), Palette16, 1.8)
|
|
||||||
}
|
|
||||||
|
|
||||||
ProfileLabels = {
|
|
||||||
"Kindle": 'K1',
|
|
||||||
"Kindle 2": 'K2',
|
|
||||||
"Kindle 3/Keyboard": 'K3',
|
|
||||||
"Kindle 4/NT/Touch": 'K4',
|
|
||||||
"Kindle Paperwhite": 'KHD',
|
|
||||||
"Kindle DX": 'KDX',
|
|
||||||
"Kindle DXG": 'KDXG'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ComicPage:
|
|
||||||
def __init__(self, source, device):
|
|
||||||
try:
|
|
||||||
self.profile = device
|
|
||||||
self.profile_label, self.size, self.palette, self.gamma = ProfileData.Profiles[device]
|
|
||||||
except KeyError:
|
|
||||||
raise RuntimeError('Unexpected output device %s' % device)
|
|
||||||
try:
|
|
||||||
self.origFileName = source
|
|
||||||
self.image = Image.open(source)
|
|
||||||
except IOError:
|
|
||||||
raise RuntimeError('Cannot read image file %s' % source)
|
|
||||||
self.image = self.image.convert('RGB')
|
|
||||||
|
|
||||||
def saveToDir(self, targetdir, notquantize):
|
|
||||||
filename = os.path.basename(self.origFileName)
|
|
||||||
try:
|
|
||||||
self.image = self.image.convert('L') # convert to grayscale
|
|
||||||
os.remove(os.path.join(targetdir, filename))
|
|
||||||
if notquantize:
|
|
||||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".jpg"), "JPEG")
|
|
||||||
else:
|
|
||||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".png"), "PNG")
|
|
||||||
except IOError as e:
|
|
||||||
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
|
|
||||||
|
|
||||||
def optimizeImage(self, gamma):
|
|
||||||
if gamma < 0.1:
|
|
||||||
gamma = self.gamma
|
|
||||||
if gamma == 1.0:
|
|
||||||
self.image = ImageOps.autocontrast(self.image)
|
|
||||||
else:
|
|
||||||
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: 255 * (a / 255.) ** gamma))
|
|
||||||
|
|
||||||
def quantizeImage(self):
|
|
||||||
colors = len(self.palette) / 3
|
|
||||||
if colors < 256:
|
|
||||||
self.palette += self.palette[:3] * (256 - colors)
|
|
||||||
palImg = Image.new('P', (1, 1))
|
|
||||||
palImg.putpalette(self.palette)
|
|
||||||
self.image = self.image.quantize(palette=palImg)
|
|
||||||
|
|
||||||
def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False):
|
|
||||||
method = Image.ANTIALIAS
|
|
||||||
if black_borders:
|
|
||||||
fill = 'black'
|
|
||||||
else:
|
|
||||||
fill = 'white'
|
|
||||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
|
||||||
if not upscale:
|
|
||||||
if isSplit and (self.profile == 'K4' or self.profile == 'KHD'):
|
|
||||||
borderw = (self.size[0] - self.image.size[0])
|
|
||||||
borderh = (self.size[1] - self.image.size[1]) / 2
|
|
||||||
self.image = ImageOps.expand(self.image, border=(0, borderh), fill=fill)
|
|
||||||
tempImg = Image.new(self.image.mode, (self.image.size[0] + borderw, self.image.size[1]), fill)
|
|
||||||
if toRight:
|
|
||||||
tempImg.paste(self.image, (borderw, 0))
|
|
||||||
else:
|
|
||||||
tempImg.paste(self.image, (0, 0))
|
|
||||||
self.image = tempImg
|
|
||||||
else:
|
|
||||||
borderw = (self.size[0] - self.image.size[0]) / 2
|
|
||||||
borderh = (self.size[1] - self.image.size[1]) / 2
|
|
||||||
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill)
|
|
||||||
return self.image
|
|
||||||
else:
|
|
||||||
method = Image.NEAREST
|
|
||||||
|
|
||||||
if stretch: # if stretching call directly resize() without other considerations.
|
|
||||||
self.image = self.image.resize(self.size, method)
|
|
||||||
return self.image
|
|
||||||
|
|
||||||
ratioDev = float(self.size[0]) / float(self.size[1])
|
|
||||||
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
|
|
||||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
|
||||||
if isSplit and (self.profile == 'K4' or self.profile == 'KHD'):
|
|
||||||
diff = 2
|
|
||||||
self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill)
|
|
||||||
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
|
||||||
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
|
||||||
self.image = ImageOps.expand(self.image, border=(0, diff / 2), fill=fill)
|
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
|
|
||||||
return self.image
|
|
||||||
|
|
||||||
def splitPage(self, targetdir, righttoleft=False, rotate=False):
|
|
||||||
width, height = self.image.size
|
|
||||||
dstwidth, dstheight = self.size
|
|
||||||
#print "Image is %d x %d" % (width,height)
|
|
||||||
# only split if origin is not oriented the same as target
|
|
||||||
if (width > height) != (dstwidth > dstheight):
|
|
||||||
if rotate:
|
|
||||||
self.image = self.image.rotate(90)
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
if width > height:
|
|
||||||
# source is landscape, so split by the width
|
|
||||||
leftbox = (0, 0, width / 2, height)
|
|
||||||
rightbox = (width / 2, 0, width, height)
|
|
||||||
else:
|
|
||||||
# source is portrait and target is landscape, so split by the height
|
|
||||||
leftbox = (0, 0, width, height / 2)
|
|
||||||
rightbox = (0, height / 2, width, height)
|
|
||||||
filename = os.path.splitext(os.path.basename(self.origFileName))
|
|
||||||
fileone = targetdir + '/' + filename[0] + '-1' + filename[1]
|
|
||||||
filetwo = targetdir + '/' + filename[0] + '-2' + filename[1]
|
|
||||||
try:
|
|
||||||
if righttoleft:
|
|
||||||
pageone = self.image.crop(rightbox)
|
|
||||||
pagetwo = self.image.crop(leftbox)
|
|
||||||
else:
|
|
||||||
pageone = self.image.crop(leftbox)
|
|
||||||
pagetwo = self.image.crop(rightbox)
|
|
||||||
pageone.save(fileone)
|
|
||||||
pagetwo.save(filetwo)
|
|
||||||
os.remove(self.origFileName)
|
|
||||||
except IOError as e:
|
|
||||||
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
|
|
||||||
return fileone, filetwo
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def frameImage(self):
|
|
||||||
foreground = tuple(self.palette[:3])
|
|
||||||
background = tuple(self.palette[-3:])
|
|
||||||
widthDev, heightDev = self.size
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
pastePt = (
|
|
||||||
max(0, (widthDev - widthImg) / 2),
|
|
||||||
max(0, (heightDev - heightImg) / 2)
|
|
||||||
)
|
|
||||||
corner1 = (
|
|
||||||
pastePt[0] - 1,
|
|
||||||
pastePt[1] - 1
|
|
||||||
)
|
|
||||||
corner2 = (
|
|
||||||
pastePt[0] + widthImg + 1,
|
|
||||||
pastePt[1] + heightImg + 1
|
|
||||||
)
|
|
||||||
imageBg = Image.new(self.image.mode, self.size, background)
|
|
||||||
imageBg.paste(self.image, pastePt)
|
|
||||||
draw = ImageDraw.Draw(imageBg)
|
|
||||||
draw.rectangle([corner1, corner2], outline=foreground)
|
|
||||||
self.image = imageBg
|
|
||||||
|
|
||||||
def cutPageNumber(self):
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
delta = 2
|
|
||||||
diff = delta
|
|
||||||
fixedThreshold = 5
|
|
||||||
if ImageStat.Stat(self.image).var[0] < 2 * fixedThreshold:
|
|
||||||
return self.image
|
|
||||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\
|
|
||||||
and diff < heightImg:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
pageNumberCut1 = diff
|
|
||||||
if diff < delta:
|
|
||||||
diff = delta
|
|
||||||
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0]
|
|
||||||
diff += delta
|
|
||||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] - oldStat > 0\
|
|
||||||
and diff < heightImg / 4:
|
|
||||||
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0]
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
pageNumberCut2 = diff
|
|
||||||
diff += delta
|
|
||||||
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg - pageNumberCut2))).var[0]
|
|
||||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg - pageNumberCut2))).var[0]\
|
|
||||||
< fixedThreshold + oldStat and diff < heightImg / 4:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
pageNumberCut3 = diff
|
|
||||||
delta = 5
|
|
||||||
diff = delta
|
|
||||||
while ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut2, diff, heightImg))).var[0] < fixedThreshold\
|
|
||||||
and diff < widthImg:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
pageNumberX1 = diff
|
|
||||||
diff = delta
|
|
||||||
while ImageStat.Stat(self.image.crop((widthImg - diff, heightImg - pageNumberCut2,
|
|
||||||
widthImg, heightImg))).var[0] < fixedThreshold and diff < widthImg:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
pageNumberX2 = widthImg - diff
|
|
||||||
if pageNumberCut3 - pageNumberCut1 > 2 * delta\
|
|
||||||
and float(pageNumberX2 - pageNumberX1) / float(pageNumberCut2 - pageNumberCut1) <= 9.0\
|
|
||||||
and ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut3, widthImg, heightImg))).var[0]\
|
|
||||||
/ ImageStat.Stat(self.image).var[0] < 0.1\
|
|
||||||
and pageNumberCut3 < heightImg / 4 - delta:
|
|
||||||
diff = pageNumberCut3
|
|
||||||
else:
|
|
||||||
diff = pageNumberCut1
|
|
||||||
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
|
|
||||||
return self.image
|
|
||||||
|
|
||||||
def cropWhiteSpace(self, threshold):
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
delta = 10
|
|
||||||
diff = delta
|
|
||||||
# top
|
|
||||||
while ImageStat.Stat(self.image.crop((0, 0, widthImg, diff))).var[0] < threshold and diff < heightImg:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
# print "Top crop: %s"%diff
|
|
||||||
self.image = self.image.crop((0, diff, widthImg, heightImg))
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
diff = delta
|
|
||||||
# left
|
|
||||||
while ImageStat.Stat(self.image.crop((0, 0, diff, heightImg))).var[0] < threshold and diff < widthImg:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
# print "Left crop: %s"%diff
|
|
||||||
self.image = self.image.crop((diff, 0, widthImg, heightImg))
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
diff = delta
|
|
||||||
# down
|
|
||||||
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < threshold\
|
|
||||||
and diff < heightImg:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
# print "Down crop: %s"%diff
|
|
||||||
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
diff = delta
|
|
||||||
# right
|
|
||||||
while ImageStat.Stat(self.image.crop((widthImg - diff, 0, widthImg, heightImg))).var[0] < threshold\
|
|
||||||
and diff < widthImg:
|
|
||||||
diff += delta
|
|
||||||
diff -= delta
|
|
||||||
# print "Right crop: %s"%diff
|
|
||||||
self.image = self.image.crop((0, 0, widthImg - diff, heightImg))
|
|
||||||
# print "New size: %sx%s"%(self.image.size[0],self.image.size[1])
|
|
||||||
return self.image
|
|
||||||
|
|
||||||
def addProgressbar(self, file_number, files_totalnumber, size, howoften):
|
|
||||||
if file_number // howoften != float(file_number) / howoften:
|
|
||||||
return self.image
|
|
||||||
white = (255, 255, 255)
|
|
||||||
black = (0, 0, 0)
|
|
||||||
widthDev, heightDev = size
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
pastePt = (
|
|
||||||
max(0, (widthDev - widthImg) / 2),
|
|
||||||
max(0, (heightDev - heightImg) / 2)
|
|
||||||
)
|
|
||||||
imageBg = Image.new('RGB', size, white)
|
|
||||||
imageBg.paste(self.image, pastePt)
|
|
||||||
self.image = imageBg
|
|
||||||
widthImg, heightImg = self.image.size
|
|
||||||
draw = ImageDraw.Draw(self.image)
|
|
||||||
#Black rectangle
|
|
||||||
draw.rectangle([(0, heightImg - 3), (widthImg, heightImg)], outline=black, fill=black)
|
|
||||||
#White rectangle
|
|
||||||
draw.rectangle([(widthImg * file_number / files_totalnumber, heightImg - 3), (widthImg - 1, heightImg)],
|
|
||||||
outline=black, fill=white)
|
|
||||||
#Making notches
|
|
||||||
for i in range(1, 10):
|
|
||||||
if i <= (10 * file_number / files_totalnumber):
|
|
||||||
notch_colour = white # White
|
|
||||||
else:
|
|
||||||
notch_colour = black # Black
|
|
||||||
draw.line([(widthImg * float(i) / 10, heightImg - 3), (widthImg * float(i) / 10, heightImg)],
|
|
||||||
fill=notch_colour)
|
|
||||||
#The 50%
|
|
||||||
if i == 5:
|
|
||||||
draw.rectangle([(widthImg / 2 - 1, heightImg - 5), (widthImg / 2 + 1, heightImg)],
|
|
||||||
outline=black, fill=notch_colour)
|
|
||||||
return self.image
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
|
||||||
#
|
|
||||||
# This is a python script. You need a Python interpreter to run it.
|
|
||||||
# For example, ActiveState Python, which exists for windows.
|
|
||||||
#
|
|
||||||
# This script strips the penultimate record from a Mobipocket file.
|
|
||||||
# This is useful because the current KindleGen add a compressed copy
|
|
||||||
# of the source files used in this record, making the ebook produced
|
|
||||||
# about twice as big as it needs to be.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
|
||||||
#
|
|
||||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
# distribute this software, either in source code form or as a compiled
|
|
||||||
# binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
# means.
|
|
||||||
#
|
|
||||||
# In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
# of this software dedicate any and all copyright interest in the
|
|
||||||
# software to the public domain. We make this dedication for the benefit
|
|
||||||
# of the public at large and to the detriment of our heirs and
|
|
||||||
# successors. We intend this dedication to be an overt act of
|
|
||||||
# relinquishment in perpetuity of all present and future rights to this
|
|
||||||
# software under copyright law.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
# OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# For more information, please refer to <http://unlicense.org/>
|
|
||||||
#
|
|
||||||
# Written by Paul Durrant, 2010-2011, paul@durrant.co.uk, pdurrant on mobileread.com
|
|
||||||
# With enhancements by Kevin Hendricks, KevinH on mobileread.com
|
|
||||||
#
|
|
||||||
# Changelog
|
|
||||||
# 1.00 - Initial version
|
|
||||||
# 1.10 - Added an option to output the stripped data
|
|
||||||
# 1.20 - Added check for source files section (thanks Piquan)
|
|
||||||
# 1.30 - Added prelim Support for K8 style mobis
|
|
||||||
# 1.31 - removed the SRCS section but kept a 0 size entry for it
|
|
||||||
# 1.32 - removes the SRCS section and its entry, now updates metadata 121 if needed
|
|
||||||
# 1.33 - now uses and modifies mobiheader SRCS and CNT
|
|
||||||
# 1.34 - added credit for Kevin Hendricks
|
|
||||||
# 1.35 - fixed bug when more than one compilation (SRCS/CMET) records
|
|
||||||
|
|
||||||
__version__ = '1.35'
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import struct
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
class Unbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
def write(self, data):
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
|
|
||||||
class StripException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SectionStripper:
|
|
||||||
def loadSection(self, section):
|
|
||||||
if (section + 1 == self.num_sections):
|
|
||||||
endoff = len(self.data_file)
|
|
||||||
else:
|
|
||||||
endoff = self.sections[section + 1][0]
|
|
||||||
off = self.sections[section][0]
|
|
||||||
return self.data_file[off:endoff]
|
|
||||||
|
|
||||||
def patch(self, off, new):
|
|
||||||
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
|
||||||
|
|
||||||
def strip(self, off, len):
|
|
||||||
self.data_file = self.data_file[:off] + self.data_file[off+len:]
|
|
||||||
|
|
||||||
def patchSection(self, section, new, in_off = 0):
|
|
||||||
if (section + 1 == self.num_sections):
|
|
||||||
endoff = len(self.data_file)
|
|
||||||
else:
|
|
||||||
endoff = self.sections[section + 1][0]
|
|
||||||
off = self.sections[section][0]
|
|
||||||
assert off + in_off + len(new) <= endoff
|
|
||||||
self.patch(off + in_off, new)
|
|
||||||
|
|
||||||
def updateEXTH121(self, srcs_secnum, srcs_cnt, mobiheader):
|
|
||||||
mobi_length, = struct.unpack('>L',mobiheader[0x14:0x18])
|
|
||||||
exth_flag, = struct.unpack('>L', mobiheader[0x80:0x84])
|
|
||||||
exth = 'NONE'
|
|
||||||
try:
|
|
||||||
if exth_flag & 0x40:
|
|
||||||
exth = mobiheader[16 + mobi_length:]
|
|
||||||
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
|
|
||||||
nitems, = struct.unpack('>I', exth[8:12])
|
|
||||||
pos = 12
|
|
||||||
for i in xrange(nitems):
|
|
||||||
type, size = struct.unpack('>II', exth[pos: pos + 8])
|
|
||||||
# print type, size
|
|
||||||
if type == 121:
|
|
||||||
boundaryptr, =struct.unpack('>L',exth[pos+8: pos + size])
|
|
||||||
if srcs_secnum <= boundaryptr:
|
|
||||||
boundaryptr -= srcs_cnt
|
|
||||||
prefix = mobiheader[0:16 + mobi_length + pos + 8]
|
|
||||||
suffix = mobiheader[16 + mobi_length + pos + 8 + 4:]
|
|
||||||
nval = struct.pack('>L',boundaryptr)
|
|
||||||
mobiheader = prefix + nval + suffix
|
|
||||||
pos += size
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return mobiheader
|
|
||||||
|
|
||||||
def __init__(self, datain):
|
|
||||||
if datain[0x3C:0x3C+8] != 'BOOKMOBI':
|
|
||||||
raise StripException("invalid file format")
|
|
||||||
self.num_sections, = struct.unpack('>H', datain[76:78])
|
|
||||||
|
|
||||||
# get mobiheader and check SRCS section number and count
|
|
||||||
offset0, = struct.unpack_from('>L', datain, 78)
|
|
||||||
offset1, = struct.unpack_from('>L', datain, 86)
|
|
||||||
mobiheader = datain[offset0:offset1]
|
|
||||||
srcs_secnum, srcs_cnt = struct.unpack_from('>2L', mobiheader, 0xe0)
|
|
||||||
if srcs_secnum == 0xffffffff or srcs_cnt == 0:
|
|
||||||
raise StripException("File doesn't contain the sources section.")
|
|
||||||
|
|
||||||
print "Found SRCS section number %d, and count %d" % (srcs_secnum, srcs_cnt)
|
|
||||||
# find its offset and length
|
|
||||||
next = srcs_secnum + srcs_cnt
|
|
||||||
srcs_offset, flgval = struct.unpack_from('>2L', datain, 78+(srcs_secnum*8))
|
|
||||||
next_offset, flgval = struct.unpack_from('>2L', datain, 78+(next*8))
|
|
||||||
srcs_length = next_offset - srcs_offset
|
|
||||||
if datain[srcs_offset:srcs_offset+4] != 'SRCS':
|
|
||||||
raise StripException("SRCS section num does not point to SRCS.")
|
|
||||||
print " beginning at offset %0x and ending at offset %0x" % (srcs_offset, srcs_length)
|
|
||||||
|
|
||||||
# it appears bytes 68-71 always contain (2*num_sections) + 1
|
|
||||||
# this is not documented anyplace at all but it appears to be some sort of next
|
|
||||||
# available unique_id used to identify specific sections in the palm db
|
|
||||||
self.data_file = datain[:68] + struct.pack('>L',((self.num_sections-srcs_cnt)*2+1))
|
|
||||||
self.data_file += datain[72:76]
|
|
||||||
|
|
||||||
# write out the number of sections reduced by srtcs_cnt
|
|
||||||
self.data_file = self.data_file + struct.pack('>H',self.num_sections-srcs_cnt)
|
|
||||||
|
|
||||||
# we are going to remove srcs_cnt SRCS sections so the offset of every entry in the table
|
|
||||||
# up to the srcs secnum must begin 8 bytes earlier per section removed (each table entry is 8 )
|
|
||||||
delta = -8 * srcs_cnt
|
|
||||||
for i in xrange(srcs_secnum):
|
|
||||||
offset, flgval = struct.unpack_from('>2L', datain, 78+(i*8))
|
|
||||||
offset += delta
|
|
||||||
self.data_file += struct.pack('>L',offset) + struct.pack('>L',flgval)
|
|
||||||
|
|
||||||
# for every record after the srcs_cnt SRCS records we must start it
|
|
||||||
# earlier by 8*srcs_cnt + the length of the srcs sections themselves)
|
|
||||||
delta = delta - srcs_length
|
|
||||||
for i in xrange(srcs_secnum+srcs_cnt,self.num_sections):
|
|
||||||
offset, flgval = struct.unpack_from('>2L', datain, 78+(i*8))
|
|
||||||
offset += delta
|
|
||||||
flgval = 2 * (i - srcs_cnt)
|
|
||||||
self.data_file += struct.pack('>L',offset) + struct.pack('>L',flgval)
|
|
||||||
|
|
||||||
# now pad it out to begin right at the first offset
|
|
||||||
# typically this is 2 bytes of nulls
|
|
||||||
first_offset, flgval = struct.unpack_from('>2L', self.data_file, 78)
|
|
||||||
self.data_file += '\0' * (first_offset - len(self.data_file))
|
|
||||||
|
|
||||||
# now finally add on every thing up to the original src_offset
|
|
||||||
self.data_file += datain[offset0: srcs_offset]
|
|
||||||
|
|
||||||
# and everything afterwards
|
|
||||||
self.data_file += datain[srcs_offset+srcs_length:]
|
|
||||||
|
|
||||||
#store away the SRCS section in case the user wants it output
|
|
||||||
self.stripped_data_header = datain[srcs_offset:srcs_offset+16]
|
|
||||||
self.stripped_data = datain[srcs_offset+16:srcs_offset+srcs_length]
|
|
||||||
|
|
||||||
# update the number of sections count
|
|
||||||
self.num_section = self.num_sections - srcs_cnt
|
|
||||||
|
|
||||||
# update the srcs_secnum and srcs_cnt in the mobiheader
|
|
||||||
offset0, flgval0 = struct.unpack_from('>2L', self.data_file, 78)
|
|
||||||
offset1, flgval1 = struct.unpack_from('>2L', self.data_file, 86)
|
|
||||||
mobiheader = self.data_file[offset0:offset1]
|
|
||||||
mobiheader = mobiheader[:0xe0]+ struct.pack('>L', 0xffffffff) + struct.pack('>L', 0) + mobiheader[0xe8:]
|
|
||||||
|
|
||||||
# if K8 mobi, handle metadata 121 in old mobiheader
|
|
||||||
mobiheader = self.updateEXTH121(srcs_secnum, srcs_cnt, mobiheader)
|
|
||||||
self.data_file = self.data_file[0:offset0] + mobiheader + self.data_file[offset1:]
|
|
||||||
print "done"
|
|
||||||
|
|
||||||
def getResult(self):
|
|
||||||
return self.data_file
|
|
||||||
|
|
||||||
def getStrippedData(self):
|
|
||||||
return self.stripped_data
|
|
||||||
|
|
||||||
def getHeader(self):
|
|
||||||
return self.stripped_data_header
|
|
||||||
|
|
||||||
def main(argv=None):
|
|
||||||
infile = argv[0]
|
|
||||||
outfile = argv[1]
|
|
||||||
data_file = file(infile, 'rb').read()
|
|
||||||
try:
|
|
||||||
strippedFile = SectionStripper(data_file)
|
|
||||||
file(outfile, 'wb').write(strippedFile.getResult())
|
|
||||||
print "Header Bytes: " + binascii.b2a_hex(strippedFile.getHeader())
|
|
||||||
if len(argv)==3:
|
|
||||||
file(argv[2], 'wb').write(strippedFile.getStrippedData())
|
|
||||||
except StripException, e:
|
|
||||||
print "Error: %s" % e
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
|
||||||
print ('KindleStrip v%(__version__)s. '
|
|
||||||
'Written 2010-2012 by Paul Durrant and Kevin Hendricks.' % globals())
|
|
||||||
if len(sys.argv)<3 or len(sys.argv)>4:
|
|
||||||
print "Strips the Sources record from Mobipocket ebooks"
|
|
||||||
print "For ebooks generated using KindleGen 1.1 and later that add the source"
|
|
||||||
print "Usage:"
|
|
||||||
print " %s <infile> <outfile> <strippeddatafile>" % sys.argv[0]
|
|
||||||
print "<strippeddatafile> is optional."
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
main(sys.argv[1:])
|
|
||||||
sys.exit(0)
|
|
||||||
1792
kcc/rarfile.py
1147
kindlecomicconverter/KCC_gui.py
Normal file
11315
kindlecomicconverter/KCC_rc.py
Normal file
273
kindlecomicconverter/KCC_ui.py
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file 'gui\KCC.ui'
|
||||||
|
#
|
||||||
|
# Created by: PyQt5 UI code generator 5.8.1
|
||||||
|
#
|
||||||
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
class Ui_mainWindow(object):
|
||||||
|
def setupUi(self, mainWindow):
|
||||||
|
mainWindow.setObjectName("mainWindow")
|
||||||
|
mainWindow.resize(450, 400)
|
||||||
|
icon = QtGui.QIcon()
|
||||||
|
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
mainWindow.setWindowIcon(icon)
|
||||||
|
self.centralWidget = QtWidgets.QWidget(mainWindow)
|
||||||
|
self.centralWidget.setObjectName("centralWidget")
|
||||||
|
self.gridLayout = QtWidgets.QGridLayout(self.centralWidget)
|
||||||
|
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
|
||||||
|
self.gridLayout.setObjectName("gridLayout")
|
||||||
|
self.progressBar = QtWidgets.QProgressBar(self.centralWidget)
|
||||||
|
self.progressBar.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setBold(True)
|
||||||
|
font.setWeight(75)
|
||||||
|
self.progressBar.setFont(font)
|
||||||
|
self.progressBar.setVisible(False)
|
||||||
|
self.progressBar.setAlignment(QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter)
|
||||||
|
self.progressBar.setObjectName("progressBar")
|
||||||
|
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
|
||||||
|
self.jobList = QtWidgets.QListWidget(self.centralWidget)
|
||||||
|
self.jobList.setStyleSheet("QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}")
|
||||||
|
self.jobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||||
|
self.jobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
|
self.jobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
|
self.jobList.setObjectName("jobList")
|
||||||
|
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
|
||||||
|
self.customWidget = QtWidgets.QWidget(self.centralWidget)
|
||||||
|
self.customWidget.setVisible(False)
|
||||||
|
self.customWidget.setObjectName("customWidget")
|
||||||
|
self.gridLayout_3 = QtWidgets.QGridLayout(self.customWidget)
|
||||||
|
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||||
|
self.hLabel = QtWidgets.QLabel(self.customWidget)
|
||||||
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
||||||
|
sizePolicy.setHorizontalStretch(0)
|
||||||
|
sizePolicy.setVerticalStretch(0)
|
||||||
|
sizePolicy.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
|
||||||
|
self.hLabel.setSizePolicy(sizePolicy)
|
||||||
|
self.hLabel.setObjectName("hLabel")
|
||||||
|
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
|
||||||
|
self.widthBox = QtWidgets.QSpinBox(self.customWidget)
|
||||||
|
self.widthBox.setMaximum(2160)
|
||||||
|
self.widthBox.setObjectName("widthBox")
|
||||||
|
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
||||||
|
self.wLabel = QtWidgets.QLabel(self.customWidget)
|
||||||
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
||||||
|
sizePolicy.setHorizontalStretch(0)
|
||||||
|
sizePolicy.setVerticalStretch(0)
|
||||||
|
sizePolicy.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
|
||||||
|
self.wLabel.setSizePolicy(sizePolicy)
|
||||||
|
self.wLabel.setObjectName("wLabel")
|
||||||
|
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
|
||||||
|
self.heightBox = QtWidgets.QSpinBox(self.customWidget)
|
||||||
|
self.heightBox.setMaximum(3840)
|
||||||
|
self.heightBox.setObjectName("heightBox")
|
||||||
|
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
||||||
|
self.gridLayout.addWidget(self.customWidget, 6, 0, 1, 2)
|
||||||
|
self.optionWidget = QtWidgets.QWidget(self.centralWidget)
|
||||||
|
self.optionWidget.setObjectName("optionWidget")
|
||||||
|
self.gridLayout_2 = QtWidgets.QGridLayout(self.optionWidget)
|
||||||
|
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||||
|
self.mangaBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||||
|
self.mangaBox.setObjectName("mangaBox")
|
||||||
|
self.gridLayout_2.addWidget(self.mangaBox, 0, 0, 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.qualityBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||||
|
self.qualityBox.setTristate(True)
|
||||||
|
self.qualityBox.setObjectName("qualityBox")
|
||||||
|
self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1)
|
||||||
|
self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||||
|
self.webtoonBox.setObjectName("webtoonBox")
|
||||||
|
self.gridLayout_2.addWidget(self.webtoonBox, 1, 0, 1, 1)
|
||||||
|
self.upscaleBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||||
|
self.upscaleBox.setTristate(True)
|
||||||
|
self.upscaleBox.setObjectName("upscaleBox")
|
||||||
|
self.gridLayout_2.addWidget(self.upscaleBox, 1, 1, 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.outputSplit = QtWidgets.QCheckBox(self.optionWidget)
|
||||||
|
self.outputSplit.setObjectName("outputSplit")
|
||||||
|
self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
|
||||||
|
self.colorBox = QtWidgets.QCheckBox(self.optionWidget)
|
||||||
|
self.colorBox.setObjectName("colorBox")
|
||||||
|
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
|
||||||
|
self.gridLayout.addWidget(self.optionWidget, 4, 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, 5, 0, 1, 2)
|
||||||
|
self.toolWidget = QtWidgets.QWidget(self.centralWidget)
|
||||||
|
self.toolWidget.setObjectName("toolWidget")
|
||||||
|
self.horizontalLayout = QtWidgets.QHBoxLayout(self.toolWidget)
|
||||||
|
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||||
|
self.editorButton = QtWidgets.QPushButton(self.toolWidget)
|
||||||
|
self.editorButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
icon1 = QtGui.QIcon()
|
||||||
|
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.editorButton.setIcon(icon1)
|
||||||
|
self.editorButton.setObjectName("editorButton")
|
||||||
|
self.horizontalLayout.addWidget(self.editorButton)
|
||||||
|
self.wikiButton = QtWidgets.QPushButton(self.toolWidget)
|
||||||
|
self.wikiButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
icon2 = QtGui.QIcon()
|
||||||
|
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/wiki.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.wikiButton.setIcon(icon2)
|
||||||
|
self.wikiButton.setObjectName("wikiButton")
|
||||||
|
self.horizontalLayout.addWidget(self.wikiButton)
|
||||||
|
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
|
||||||
|
self.buttonWidget = QtWidgets.QWidget(self.centralWidget)
|
||||||
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||||
|
sizePolicy.setHorizontalStretch(0)
|
||||||
|
sizePolicy.setVerticalStretch(0)
|
||||||
|
sizePolicy.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
|
||||||
|
self.buttonWidget.setSizePolicy(sizePolicy)
|
||||||
|
self.buttonWidget.setObjectName("buttonWidget")
|
||||||
|
self.gridLayout_4 = QtWidgets.QGridLayout(self.buttonWidget)
|
||||||
|
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout_4.setObjectName("gridLayout_4")
|
||||||
|
self.directoryButton = QtWidgets.QPushButton(self.buttonWidget)
|
||||||
|
self.directoryButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
icon3 = QtGui.QIcon()
|
||||||
|
icon3.addPixmap(QtGui.QPixmap(":/Other/icons/folder_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.directoryButton.setIcon(icon3)
|
||||||
|
self.directoryButton.setObjectName("directoryButton")
|
||||||
|
self.gridLayout_4.addWidget(self.directoryButton, 0, 0, 1, 1)
|
||||||
|
self.fileButton = QtWidgets.QPushButton(self.buttonWidget)
|
||||||
|
self.fileButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
icon4 = QtGui.QIcon()
|
||||||
|
icon4.addPixmap(QtGui.QPixmap(":/Other/icons/document_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.fileButton.setIcon(icon4)
|
||||||
|
self.fileButton.setObjectName("fileButton")
|
||||||
|
self.gridLayout_4.addWidget(self.fileButton, 0, 3, 1, 1)
|
||||||
|
self.deviceBox = QtWidgets.QComboBox(self.buttonWidget)
|
||||||
|
self.deviceBox.setMinimumSize(QtCore.QSize(0, 28))
|
||||||
|
self.deviceBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
|
||||||
|
self.deviceBox.setObjectName("deviceBox")
|
||||||
|
self.gridLayout_4.addWidget(self.deviceBox, 1, 0, 1, 1)
|
||||||
|
self.formatBox = QtWidgets.QComboBox(self.buttonWidget)
|
||||||
|
self.formatBox.setMinimumSize(QtCore.QSize(0, 28))
|
||||||
|
self.formatBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
|
||||||
|
self.formatBox.setObjectName("formatBox")
|
||||||
|
self.gridLayout_4.addWidget(self.formatBox, 1, 3, 1, 1)
|
||||||
|
self.convertButton = QtWidgets.QPushButton(self.buttonWidget)
|
||||||
|
self.convertButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setBold(True)
|
||||||
|
font.setWeight(75)
|
||||||
|
self.convertButton.setFont(font)
|
||||||
|
icon5 = QtGui.QIcon()
|
||||||
|
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.convertButton.setIcon(icon5)
|
||||||
|
self.convertButton.setObjectName("convertButton")
|
||||||
|
self.gridLayout_4.addWidget(self.convertButton, 1, 2, 1, 1)
|
||||||
|
self.clearButton = QtWidgets.QPushButton(self.buttonWidget)
|
||||||
|
self.clearButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
icon6 = QtGui.QIcon()
|
||||||
|
icon6.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.clearButton.setIcon(icon6)
|
||||||
|
self.clearButton.setObjectName("clearButton")
|
||||||
|
self.gridLayout_4.addWidget(self.clearButton, 0, 2, 1, 1)
|
||||||
|
self.directoryButton.raise_()
|
||||||
|
self.clearButton.raise_()
|
||||||
|
self.fileButton.raise_()
|
||||||
|
self.deviceBox.raise_()
|
||||||
|
self.convertButton.raise_()
|
||||||
|
self.formatBox.raise_()
|
||||||
|
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
|
||||||
|
mainWindow.setCentralWidget(self.centralWidget)
|
||||||
|
self.statusBar = QtWidgets.QStatusBar(mainWindow)
|
||||||
|
self.statusBar.setSizeGripEnabled(False)
|
||||||
|
self.statusBar.setObjectName("statusBar")
|
||||||
|
mainWindow.setStatusBar(self.statusBar)
|
||||||
|
|
||||||
|
self.retranslateUi(mainWindow)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(mainWindow)
|
||||||
|
mainWindow.setTabOrder(self.convertButton, self.clearButton)
|
||||||
|
mainWindow.setTabOrder(self.clearButton, self.directoryButton)
|
||||||
|
mainWindow.setTabOrder(self.directoryButton, self.fileButton)
|
||||||
|
mainWindow.setTabOrder(self.fileButton, self.deviceBox)
|
||||||
|
mainWindow.setTabOrder(self.deviceBox, self.formatBox)
|
||||||
|
mainWindow.setTabOrder(self.formatBox, self.mangaBox)
|
||||||
|
mainWindow.setTabOrder(self.mangaBox, self.rotateBox)
|
||||||
|
mainWindow.setTabOrder(self.rotateBox, self.qualityBox)
|
||||||
|
mainWindow.setTabOrder(self.qualityBox, self.webtoonBox)
|
||||||
|
mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox)
|
||||||
|
mainWindow.setTabOrder(self.upscaleBox, self.gammaBox)
|
||||||
|
mainWindow.setTabOrder(self.gammaBox, self.borderBox)
|
||||||
|
mainWindow.setTabOrder(self.borderBox, self.outputSplit)
|
||||||
|
mainWindow.setTabOrder(self.outputSplit, self.colorBox)
|
||||||
|
mainWindow.setTabOrder(self.colorBox, self.editorButton)
|
||||||
|
mainWindow.setTabOrder(self.editorButton, self.wikiButton)
|
||||||
|
mainWindow.setTabOrder(self.wikiButton, self.jobList)
|
||||||
|
mainWindow.setTabOrder(self.jobList, self.gammaSlider)
|
||||||
|
mainWindow.setTabOrder(self.gammaSlider, self.widthBox)
|
||||||
|
mainWindow.setTabOrder(self.widthBox, self.heightBox)
|
||||||
|
|
||||||
|
def retranslateUi(self, mainWindow):
|
||||||
|
_translate = QtCore.QCoreApplication.translate
|
||||||
|
mainWindow.setWindowTitle(_translate("mainWindow", "Kindle Comic Converter"))
|
||||||
|
self.hLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||||
|
self.hLabel.setText(_translate("mainWindow", "Custom height:"))
|
||||||
|
self.widthBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||||
|
self.wLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||||
|
self.wLabel.setText(_translate("mainWindow", "Custom width:"))
|
||||||
|
self.heightBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>"))
|
||||||
|
self.mangaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>"))
|
||||||
|
self.mangaBox.setText(_translate("mainWindow", "Manga mode"))
|
||||||
|
self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>"))
|
||||||
|
self.rotateBox.setText(_translate("mainWindow", "Spread splitter"))
|
||||||
|
self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>"))
|
||||||
|
self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2/HQ"))
|
||||||
|
self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>"))
|
||||||
|
self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode"))
|
||||||
|
self.upscaleBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>"))
|
||||||
|
self.upscaleBox.setText(_translate("mainWindow", "Stretch/Upscale"))
|
||||||
|
self.gammaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable automatic gamma correction.</p></body></html>"))
|
||||||
|
self.gammaBox.setText(_translate("mainWindow", "Custom gamma"))
|
||||||
|
self.borderBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>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.borderBox.setText(_translate("mainWindow", "W/B margins"))
|
||||||
|
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.outputSplit.setText(_translate("mainWindow", "Output split"))
|
||||||
|
self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>"))
|
||||||
|
self.colorBox.setText(_translate("mainWindow", "Color mode"))
|
||||||
|
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))
|
||||||
|
self.editorButton.setText(_translate("mainWindow", "Editor"))
|
||||||
|
self.editorButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to edit directory.</p></body></html>"))
|
||||||
|
self.wikiButton.setText(_translate("mainWindow", "Wiki"))
|
||||||
|
self.directoryButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>"))
|
||||||
|
self.directoryButton.setText(_translate("mainWindow", "Add directory"))
|
||||||
|
self.fileButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>"))
|
||||||
|
self.fileButton.setText(_translate("mainWindow", "Add file"))
|
||||||
|
self.deviceBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>"))
|
||||||
|
self.formatBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>"))
|
||||||
|
self.convertButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>"))
|
||||||
|
self.convertButton.setText(_translate("mainWindow", "Convert"))
|
||||||
|
self.clearButton.setText(_translate("mainWindow", "Clear list"))
|
||||||
|
|
||||||
|
from . import KCC_rc
|
||||||
116
kindlecomicconverter/KCC_ui_editor.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file 'gui\MetaEditor.ui'
|
||||||
|
#
|
||||||
|
# Created by: PyQt5 UI code generator 5.6
|
||||||
|
#
|
||||||
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
class Ui_editorDialog(object):
|
||||||
|
def setupUi(self, editorDialog):
|
||||||
|
editorDialog.setObjectName("editorDialog")
|
||||||
|
editorDialog.resize(400, 260)
|
||||||
|
editorDialog.setMinimumSize(QtCore.QSize(400, 260))
|
||||||
|
icon = QtGui.QIcon()
|
||||||
|
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
editorDialog.setWindowIcon(icon)
|
||||||
|
self.verticalLayout = QtWidgets.QVBoxLayout(editorDialog)
|
||||||
|
self.verticalLayout.setContentsMargins(-1, -1, -1, 5)
|
||||||
|
self.verticalLayout.setObjectName("verticalLayout")
|
||||||
|
self.editorWidget = QtWidgets.QWidget(editorDialog)
|
||||||
|
self.editorWidget.setObjectName("editorWidget")
|
||||||
|
self.gridLayout = QtWidgets.QGridLayout(self.editorWidget)
|
||||||
|
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.gridLayout.setObjectName("gridLayout")
|
||||||
|
self.label_1 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_1.setObjectName("label_1")
|
||||||
|
self.gridLayout.addWidget(self.label_1, 0, 0, 1, 1)
|
||||||
|
self.seriesLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.seriesLine.setObjectName("seriesLine")
|
||||||
|
self.gridLayout.addWidget(self.seriesLine, 0, 1, 1, 1)
|
||||||
|
self.label_2 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_2.setObjectName("label_2")
|
||||||
|
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
|
||||||
|
self.volumeLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.volumeLine.setObjectName("volumeLine")
|
||||||
|
self.gridLayout.addWidget(self.volumeLine, 1, 1, 1, 1)
|
||||||
|
self.label_3 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_3.setObjectName("label_3")
|
||||||
|
self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
|
||||||
|
self.numberLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.numberLine.setObjectName("numberLine")
|
||||||
|
self.gridLayout.addWidget(self.numberLine, 2, 1, 1, 1)
|
||||||
|
self.label_4 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_4.setObjectName("label_4")
|
||||||
|
self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
|
||||||
|
self.writerLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.writerLine.setObjectName("writerLine")
|
||||||
|
self.gridLayout.addWidget(self.writerLine, 3, 1, 1, 1)
|
||||||
|
self.label_5 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_5.setObjectName("label_5")
|
||||||
|
self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1)
|
||||||
|
self.pencillerLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.pencillerLine.setObjectName("pencillerLine")
|
||||||
|
self.gridLayout.addWidget(self.pencillerLine, 4, 1, 1, 1)
|
||||||
|
self.label_6 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_6.setObjectName("label_6")
|
||||||
|
self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1)
|
||||||
|
self.inkerLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.inkerLine.setObjectName("inkerLine")
|
||||||
|
self.gridLayout.addWidget(self.inkerLine, 5, 1, 1, 1)
|
||||||
|
self.label_7 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_7.setObjectName("label_7")
|
||||||
|
self.gridLayout.addWidget(self.label_7, 6, 0, 1, 1)
|
||||||
|
self.coloristLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.coloristLine.setObjectName("coloristLine")
|
||||||
|
self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1)
|
||||||
|
self.verticalLayout.addWidget(self.editorWidget)
|
||||||
|
self.optionWidget = QtWidgets.QWidget(editorDialog)
|
||||||
|
self.optionWidget.setObjectName("optionWidget")
|
||||||
|
self.horizontalLayout = QtWidgets.QHBoxLayout(self.optionWidget)
|
||||||
|
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||||
|
self.statusLabel = QtWidgets.QLabel(self.optionWidget)
|
||||||
|
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||||
|
sizePolicy.setHorizontalStretch(0)
|
||||||
|
sizePolicy.setVerticalStretch(0)
|
||||||
|
sizePolicy.setHeightForWidth(self.statusLabel.sizePolicy().hasHeightForWidth())
|
||||||
|
self.statusLabel.setSizePolicy(sizePolicy)
|
||||||
|
self.statusLabel.setText("")
|
||||||
|
self.statusLabel.setObjectName("statusLabel")
|
||||||
|
self.horizontalLayout.addWidget(self.statusLabel)
|
||||||
|
self.okButton = QtWidgets.QPushButton(self.optionWidget)
|
||||||
|
self.okButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
icon1 = QtGui.QIcon()
|
||||||
|
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.okButton.setIcon(icon1)
|
||||||
|
self.okButton.setObjectName("okButton")
|
||||||
|
self.horizontalLayout.addWidget(self.okButton)
|
||||||
|
self.cancelButton = QtWidgets.QPushButton(self.optionWidget)
|
||||||
|
self.cancelButton.setMinimumSize(QtCore.QSize(0, 30))
|
||||||
|
icon2 = QtGui.QIcon()
|
||||||
|
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||||
|
self.cancelButton.setIcon(icon2)
|
||||||
|
self.cancelButton.setObjectName("cancelButton")
|
||||||
|
self.horizontalLayout.addWidget(self.cancelButton)
|
||||||
|
self.verticalLayout.addWidget(self.optionWidget)
|
||||||
|
|
||||||
|
self.retranslateUi(editorDialog)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(editorDialog)
|
||||||
|
|
||||||
|
def retranslateUi(self, editorDialog):
|
||||||
|
_translate = QtCore.QCoreApplication.translate
|
||||||
|
editorDialog.setWindowTitle(_translate("editorDialog", "Metadata editor"))
|
||||||
|
self.label_1.setText(_translate("editorDialog", "Series:"))
|
||||||
|
self.label_2.setText(_translate("editorDialog", "Volume:"))
|
||||||
|
self.label_3.setText(_translate("editorDialog", "Number:"))
|
||||||
|
self.label_4.setText(_translate("editorDialog", "Writer:"))
|
||||||
|
self.label_5.setText(_translate("editorDialog", "Penciller:"))
|
||||||
|
self.label_6.setText(_translate("editorDialog", "Inker:"))
|
||||||
|
self.label_7.setText(_translate("editorDialog", "Colorist:"))
|
||||||
|
self.okButton.setText(_translate("editorDialog", "Save"))
|
||||||
|
self.cancelButton.setText(_translate("editorDialog", "Cancel"))
|
||||||
|
|
||||||
|
from . import KCC_rc
|
||||||
4
kindlecomicconverter/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
__version__ = '5.5.2'
|
||||||
|
__license__ = 'ISC'
|
||||||
|
__copyright__ = '2012-2019, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
1234
kindlecomicconverter/comic2ebook.py
Executable file
281
kindlecomicconverter/comic2panel.py
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from shutil import rmtree, copytree, move
|
||||||
|
from optparse import OptionParser, OptionGroup
|
||||||
|
from multiprocessing import Pool
|
||||||
|
from PIL import Image, ImageChops, ImageOps, ImageDraw
|
||||||
|
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||||
|
try:
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
except ImportError:
|
||||||
|
QtCore = None
|
||||||
|
|
||||||
|
|
||||||
|
def mergeDirectoryTick(output):
|
||||||
|
if output:
|
||||||
|
mergeWorkerOutput.append(output)
|
||||||
|
mergeWorkerPool.terminate()
|
||||||
|
if GUI:
|
||||||
|
GUI.progressBarTick.emit('tick')
|
||||||
|
if not GUI.conversionAlive:
|
||||||
|
mergeWorkerPool.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def mergeDirectory(work):
|
||||||
|
try:
|
||||||
|
directory = work[0]
|
||||||
|
images = []
|
||||||
|
imagesValid = []
|
||||||
|
sizes = []
|
||||||
|
targetHeight = 0
|
||||||
|
for root, _, files in walkLevel(directory, 0):
|
||||||
|
for name in files:
|
||||||
|
if getImageFileName(name) is not None:
|
||||||
|
i = Image.open(os.path.join(root, name))
|
||||||
|
images.append([os.path.join(root, name), i.size[0], i.size[1]])
|
||||||
|
sizes.append(i.size[0])
|
||||||
|
if len(images) > 0:
|
||||||
|
targetWidth = max(set(sizes), key=sizes.count)
|
||||||
|
for i in images:
|
||||||
|
if i[1] <= targetWidth:
|
||||||
|
targetHeight += i[2]
|
||||||
|
imagesValid.append(i[0])
|
||||||
|
# Silently drop directories that contain too many images
|
||||||
|
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
|
||||||
|
if targetHeight > 131072:
|
||||||
|
return None
|
||||||
|
result = Image.new('RGB', (targetWidth, targetHeight))
|
||||||
|
y = 0
|
||||||
|
for i in imagesValid:
|
||||||
|
img = Image.open(i).convert('RGB')
|
||||||
|
if img.size[0] < targetWidth:
|
||||||
|
img = ImageOps.fit(img, (targetWidth, img.size[1]), method=Image.BICUBIC, centering=(0.5, 0.5))
|
||||||
|
result.paste(img, (0, y))
|
||||||
|
y += img.size[1]
|
||||||
|
os.remove(i)
|
||||||
|
savePath = os.path.split(imagesValid[0])
|
||||||
|
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
|
||||||
|
except Exception:
|
||||||
|
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
|
||||||
|
|
||||||
|
|
||||||
|
def detectSolid(img):
|
||||||
|
return not ImageChops.invert(img).getbbox() or not img.getbbox()
|
||||||
|
|
||||||
|
|
||||||
|
def splitImageTick(output):
|
||||||
|
if output:
|
||||||
|
splitWorkerOutput.append(output)
|
||||||
|
splitWorkerPool.terminate()
|
||||||
|
if GUI:
|
||||||
|
GUI.progressBarTick.emit('tick')
|
||||||
|
if not GUI.conversionAlive:
|
||||||
|
splitWorkerPool.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
def splitImage(work):
|
||||||
|
try:
|
||||||
|
path = work[0]
|
||||||
|
name = work[1]
|
||||||
|
opt = work[2]
|
||||||
|
filePath = os.path.join(path, name)
|
||||||
|
imgOrg = Image.open(filePath).convert('RGB')
|
||||||
|
imgProcess = Image.open(filePath).convert('1')
|
||||||
|
widthImg, heightImg = imgOrg.size
|
||||||
|
if heightImg > opt.height:
|
||||||
|
if opt.debug:
|
||||||
|
drawImg = Image.open(filePath).convert(mode='RGBA')
|
||||||
|
draw = ImageDraw.Draw(drawImg)
|
||||||
|
|
||||||
|
# Find panels
|
||||||
|
yWork = 0
|
||||||
|
panelDetected = False
|
||||||
|
panels = []
|
||||||
|
while yWork < heightImg:
|
||||||
|
tmpImg = imgProcess.crop([0, yWork, widthImg, yWork + 4])
|
||||||
|
solid = detectSolid(tmpImg)
|
||||||
|
if not solid and not panelDetected:
|
||||||
|
panelDetected = True
|
||||||
|
panelY1 = yWork - 2
|
||||||
|
if solid and panelDetected:
|
||||||
|
panelDetected = False
|
||||||
|
panelY2 = yWork + 6
|
||||||
|
panels.append((panelY1, panelY2, panelY2 - panelY1))
|
||||||
|
yWork += 5
|
||||||
|
|
||||||
|
# Split too big panels
|
||||||
|
panelsProcessed = []
|
||||||
|
for panel in panels:
|
||||||
|
if panel[2] <= opt.height * 1.5:
|
||||||
|
panelsProcessed.append(panel)
|
||||||
|
elif panel[2] < opt.height * 2:
|
||||||
|
diff = panel[2] - opt.height
|
||||||
|
panelsProcessed.append((panel[0], panel[1] - diff, opt.height))
|
||||||
|
panelsProcessed.append((panel[1] - opt.height, panel[1], opt.height))
|
||||||
|
else:
|
||||||
|
parts = round(panel[2] / opt.height)
|
||||||
|
diff = panel[2] // parts
|
||||||
|
for x in range(0, parts):
|
||||||
|
panelsProcessed.append((panel[0] + (x * diff), panel[1] - ((parts - x - 1) * diff), diff))
|
||||||
|
|
||||||
|
if opt.debug:
|
||||||
|
for panel in panelsProcessed:
|
||||||
|
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.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
|
||||||
|
|
||||||
|
# Create virtual pages
|
||||||
|
pages = []
|
||||||
|
currentPage = []
|
||||||
|
pageLeft = opt.height
|
||||||
|
panelNumber = 0
|
||||||
|
for panel in panelsProcessed:
|
||||||
|
if pageLeft - panel[2] > 0:
|
||||||
|
pageLeft -= panel[2]
|
||||||
|
currentPage.append(panelNumber)
|
||||||
|
panelNumber += 1
|
||||||
|
else:
|
||||||
|
if len(currentPage) > 0:
|
||||||
|
pages.append(currentPage)
|
||||||
|
pageLeft = opt.height - panel[2]
|
||||||
|
currentPage = [panelNumber]
|
||||||
|
panelNumber += 1
|
||||||
|
if len(currentPage) > 0:
|
||||||
|
pages.append(currentPage)
|
||||||
|
|
||||||
|
# Create pages
|
||||||
|
pageNumber = 1
|
||||||
|
for page in pages:
|
||||||
|
pageHeight = 0
|
||||||
|
targetHeight = 0
|
||||||
|
for panel in page:
|
||||||
|
pageHeight += panelsProcessed[panel][2]
|
||||||
|
if pageHeight > 15:
|
||||||
|
newPage = Image.new('RGB', (widthImg, pageHeight))
|
||||||
|
for panel in page:
|
||||||
|
panelImg = imgOrg.crop([0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]])
|
||||||
|
newPage.paste(panelImg, (0, targetHeight))
|
||||||
|
targetHeight += panelsProcessed[panel][2]
|
||||||
|
newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
|
||||||
|
pageNumber += 1
|
||||||
|
os.remove(filePath)
|
||||||
|
except Exception:
|
||||||
|
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None, qtgui=None):
|
||||||
|
global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
|
||||||
|
parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False)
|
||||||
|
mainOptions = OptionGroup(parser, "MANDATORY")
|
||||||
|
otherOptions = OptionGroup(parser, "OTHER")
|
||||||
|
mainOptions.add_option("-y", "--height", type="int", dest="height", default=0,
|
||||||
|
help="Height of the target device screen")
|
||||||
|
mainOptions.add_option("-i", "--in-place", action="store_true", dest="inPlace", default=False,
|
||||||
|
help="Overwrite source directory")
|
||||||
|
mainOptions.add_option("-m", "--merge", action="store_true", dest="merge", default=False,
|
||||||
|
help="Combine every directory into a single image before splitting")
|
||||||
|
otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False,
|
||||||
|
help="Create debug file for every split image")
|
||||||
|
otherOptions.add_option("-h", "--help", action="help",
|
||||||
|
help="Show this help message and exit")
|
||||||
|
parser.add_option_group(mainOptions)
|
||||||
|
parser.add_option_group(otherOptions)
|
||||||
|
options, args = parser.parse_args(argv)
|
||||||
|
if qtgui:
|
||||||
|
GUI = qtgui
|
||||||
|
else:
|
||||||
|
GUI = None
|
||||||
|
if len(args) != 1:
|
||||||
|
parser.print_help()
|
||||||
|
return 1
|
||||||
|
if options.height > 0:
|
||||||
|
options.sourceDir = args[0]
|
||||||
|
options.targetDir = args[0] + "-Splitted"
|
||||||
|
if os.path.isdir(options.sourceDir):
|
||||||
|
rmtree(options.targetDir, True)
|
||||||
|
copytree(options.sourceDir, options.targetDir)
|
||||||
|
work = []
|
||||||
|
pagenumber = 1
|
||||||
|
splitWorkerOutput = []
|
||||||
|
splitWorkerPool = Pool(maxtasksperchild=10)
|
||||||
|
if options.merge:
|
||||||
|
print("Merging images...")
|
||||||
|
directoryNumer = 1
|
||||||
|
mergeWork = []
|
||||||
|
mergeWorkerOutput = []
|
||||||
|
mergeWorkerPool = Pool(maxtasksperchild=10)
|
||||||
|
mergeWork.append([options.targetDir])
|
||||||
|
for root, dirs, files in os.walk(options.targetDir, False):
|
||||||
|
dirs, files = walkSort(dirs, files)
|
||||||
|
for directory in dirs:
|
||||||
|
directoryNumer += 1
|
||||||
|
mergeWork.append([os.path.join(root, directory)])
|
||||||
|
if GUI:
|
||||||
|
GUI.progressBarTick.emit('Combining images')
|
||||||
|
GUI.progressBarTick.emit(str(directoryNumer))
|
||||||
|
for i in mergeWork:
|
||||||
|
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
|
||||||
|
mergeWorkerPool.close()
|
||||||
|
mergeWorkerPool.join()
|
||||||
|
if GUI and not GUI.conversionAlive:
|
||||||
|
rmtree(options.targetDir, True)
|
||||||
|
raise UserWarning("Conversion interrupted.")
|
||||||
|
if len(mergeWorkerOutput) > 0:
|
||||||
|
rmtree(options.targetDir, True)
|
||||||
|
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
||||||
|
mergeWorkerOutput[0][1])
|
||||||
|
print("Splitting images...")
|
||||||
|
for root, _, files in os.walk(options.targetDir, False):
|
||||||
|
for name in files:
|
||||||
|
if getImageFileName(name) is not None:
|
||||||
|
pagenumber += 1
|
||||||
|
work.append([root, name, options])
|
||||||
|
else:
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
|
if GUI:
|
||||||
|
GUI.progressBarTick.emit('Splitting images')
|
||||||
|
GUI.progressBarTick.emit(str(pagenumber))
|
||||||
|
GUI.progressBarTick.emit('tick')
|
||||||
|
if len(work) > 0:
|
||||||
|
for i in work:
|
||||||
|
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
|
||||||
|
splitWorkerPool.close()
|
||||||
|
splitWorkerPool.join()
|
||||||
|
if GUI and not GUI.conversionAlive:
|
||||||
|
rmtree(options.targetDir, True)
|
||||||
|
raise UserWarning("Conversion interrupted.")
|
||||||
|
if len(splitWorkerOutput) > 0:
|
||||||
|
rmtree(options.targetDir, True)
|
||||||
|
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
||||||
|
splitWorkerOutput[0][1])
|
||||||
|
if options.inPlace:
|
||||||
|
rmtree(options.sourceDir)
|
||||||
|
move(options.targetDir, options.sourceDir)
|
||||||
|
else:
|
||||||
|
rmtree(options.targetDir, True)
|
||||||
|
raise UserWarning("Source directory is empty.")
|
||||||
|
else:
|
||||||
|
raise UserWarning("Provided path is not a directory.")
|
||||||
|
else:
|
||||||
|
raise UserWarning("Target height is not set.")
|
||||||
81
kindlecomicconverter/comicarchive.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
from psutil import Popen
|
||||||
|
from shutil import move
|
||||||
|
from subprocess import STDOUT, PIPE
|
||||||
|
from xml.dom.minidom import parseString
|
||||||
|
from xml.parsers.expat import ExpatError
|
||||||
|
|
||||||
|
|
||||||
|
class ComicArchive:
|
||||||
|
def __init__(self, filepath):
|
||||||
|
self.filepath = filepath
|
||||||
|
self.type = None
|
||||||
|
if not os.path.isfile(self.filepath):
|
||||||
|
raise OSError('File not found.')
|
||||||
|
process = Popen('7z l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True)
|
||||||
|
for line in process.stdout:
|
||||||
|
if b'Type =' in line:
|
||||||
|
self.type = line.rstrip().decode().split(' = ')[1].upper()
|
||||||
|
break
|
||||||
|
process.communicate()
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise OSError('Archive is corrupted or encrypted.')
|
||||||
|
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
|
||||||
|
raise OSError('Unsupported archive format.')
|
||||||
|
|
||||||
|
def extract(self, targetdir):
|
||||||
|
if not os.path.isdir(targetdir):
|
||||||
|
raise OSError('Target directory don\'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)
|
||||||
|
process.communicate()
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise OSError('Failed to extract archive.')
|
||||||
|
tdir = os.listdir(targetdir)
|
||||||
|
if 'ComicInfo.xml' in tdir:
|
||||||
|
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])):
|
||||||
|
move(os.path.join(targetdir, tdir[0], f), targetdir)
|
||||||
|
os.rmdir(os.path.join(targetdir, tdir[0]))
|
||||||
|
return targetdir
|
||||||
|
|
||||||
|
def addFile(self, sourcefile):
|
||||||
|
if self.type in ['RAR', 'RAR5']:
|
||||||
|
raise NotImplementedError
|
||||||
|
process = Popen('7z a -y "' + self.filepath + '" "' + sourcefile + '"',
|
||||||
|
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||||
|
process.communicate()
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise OSError('Failed to add the file.')
|
||||||
|
|
||||||
|
def extractMetadata(self):
|
||||||
|
process = Popen('7z x -y -so "' + self.filepath + '" ComicInfo.xml',
|
||||||
|
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||||
|
xml = process.communicate()
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise OSError('Failed to extract archive.')
|
||||||
|
try:
|
||||||
|
return parseString(xml[0])
|
||||||
|
except ExpatError:
|
||||||
|
return None
|
||||||
182
kindlecomicconverter/dualmetafix.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks
|
||||||
|
# Changes for KCC Copyright (C) 2014-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import mmap
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
class DualMetaFixException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# palm database offset constants
|
||||||
|
number_of_pdb_records = 76
|
||||||
|
first_pdb_record = 78
|
||||||
|
|
||||||
|
# important rec0 offsets
|
||||||
|
mobi_header_base = 16
|
||||||
|
mobi_header_length = 20
|
||||||
|
mobi_version = 36
|
||||||
|
title_offset = 84
|
||||||
|
|
||||||
|
|
||||||
|
def getint(data, ofs, sz='L'):
|
||||||
|
i, = struct.unpack_from('>' + sz, data, ofs)
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def writeint(data, ofs, n, slen='L'):
|
||||||
|
if slen == 'L':
|
||||||
|
return data[:ofs] + struct.pack('>L', n) + data[ofs + 4:]
|
||||||
|
else:
|
||||||
|
return data[:ofs] + struct.pack('>H', n) + data[ofs + 2:]
|
||||||
|
|
||||||
|
|
||||||
|
def getsecaddr(datain, secno):
|
||||||
|
nsec = getint(datain, number_of_pdb_records, 'H')
|
||||||
|
if (secno < 0) | (secno >= nsec):
|
||||||
|
emsg = 'requested section number %d out of range (nsec=%d)' % (secno, nsec)
|
||||||
|
raise DualMetaFixException(emsg)
|
||||||
|
secstart = getint(datain, first_pdb_record + secno * 8)
|
||||||
|
if secno == nsec - 1:
|
||||||
|
secend = len(datain)
|
||||||
|
else:
|
||||||
|
secend = getint(datain, first_pdb_record + (secno + 1) * 8)
|
||||||
|
return secstart, secend
|
||||||
|
|
||||||
|
|
||||||
|
def readsection(datain, secno):
|
||||||
|
secstart, secend = getsecaddr(datain, secno)
|
||||||
|
return datain[secstart:secend]
|
||||||
|
|
||||||
|
|
||||||
|
# overwrite section - must be exact same length as original
|
||||||
|
def replacesection(datain, secno, secdata):
|
||||||
|
secstart, secend = getsecaddr(datain, secno)
|
||||||
|
seclen = secend - secstart
|
||||||
|
if len(secdata) != seclen:
|
||||||
|
raise DualMetaFixException('section length change in replacesection')
|
||||||
|
datain[secstart:secstart + seclen] = secdata
|
||||||
|
|
||||||
|
|
||||||
|
def get_exth_params(rec0):
|
||||||
|
ebase = mobi_header_base + getint(rec0, mobi_header_length)
|
||||||
|
if rec0[ebase:ebase + 4] != b'EXTH':
|
||||||
|
raise DualMetaFixException('EXTH tag not found where expected')
|
||||||
|
elen = getint(rec0, ebase + 4)
|
||||||
|
enum = getint(rec0, ebase + 8)
|
||||||
|
rlen = len(rec0)
|
||||||
|
return ebase, elen, enum, rlen
|
||||||
|
|
||||||
|
|
||||||
|
def add_exth(rec0, exth_num, exth_bytes):
|
||||||
|
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||||
|
newrecsize = 8 + len(exth_bytes)
|
||||||
|
newrec0 = rec0[0:ebase + 4] + struct.pack('>L', elen + newrecsize) + struct.pack('>L', enum + 1) + \
|
||||||
|
struct.pack('>L', exth_num) + struct.pack('>L', newrecsize) + exth_bytes + rec0[ebase + 12:]
|
||||||
|
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset) + newrecsize)
|
||||||
|
# keep constant record length by removing newrecsize null bytes from end
|
||||||
|
sectail = newrec0[-newrecsize:]
|
||||||
|
if sectail != b'\0' * newrecsize:
|
||||||
|
raise DualMetaFixException('add_exth: trimmed non-null bytes at end of section')
|
||||||
|
newrec0 = newrec0[0:rlen]
|
||||||
|
return newrec0
|
||||||
|
|
||||||
|
|
||||||
|
def read_exth(rec0, exth_num):
|
||||||
|
exth_values = []
|
||||||
|
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||||
|
ebase += 12
|
||||||
|
while enum > 0:
|
||||||
|
exth_id = getint(rec0, ebase)
|
||||||
|
if exth_id == exth_num:
|
||||||
|
# We might have multiple exths, so build a list.
|
||||||
|
exth_values.append(rec0[ebase + 8:ebase + getint(rec0, ebase + 4)])
|
||||||
|
enum -= 1
|
||||||
|
ebase = ebase + getint(rec0, ebase + 4)
|
||||||
|
return exth_values
|
||||||
|
|
||||||
|
|
||||||
|
def del_exth(rec0, exth_num):
|
||||||
|
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||||
|
ebase_idx = ebase + 12
|
||||||
|
enum_idx = 0
|
||||||
|
while enum_idx < enum:
|
||||||
|
exth_id = getint(rec0, ebase_idx)
|
||||||
|
exth_size = getint(rec0, ebase_idx + 4)
|
||||||
|
if exth_id == exth_num:
|
||||||
|
newrec0 = rec0
|
||||||
|
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset) - exth_size)
|
||||||
|
newrec0 = newrec0[:ebase_idx] + newrec0[ebase_idx + exth_size:]
|
||||||
|
newrec0 = newrec0[0:ebase + 4] + struct.pack('>L', elen - exth_size) + \
|
||||||
|
struct.pack('>L', enum - 1) + newrec0[ebase + 12:]
|
||||||
|
newrec0 += b'\0' * exth_size
|
||||||
|
if rlen != len(newrec0):
|
||||||
|
raise DualMetaFixException('del_exth: incorrect section size change')
|
||||||
|
return newrec0
|
||||||
|
enum_idx += 1
|
||||||
|
ebase_idx = ebase_idx + exth_size
|
||||||
|
return rec0
|
||||||
|
|
||||||
|
|
||||||
|
class DualMobiMetaFix:
|
||||||
|
def __init__(self, infile, outfile, asin):
|
||||||
|
shutil.copyfile(infile, outfile)
|
||||||
|
f = open(outfile, "r+b")
|
||||||
|
self.datain = mmap.mmap(f.fileno(), 0)
|
||||||
|
self.datain_rec0 = readsection(self.datain, 0)
|
||||||
|
|
||||||
|
# in the first mobi header
|
||||||
|
# add 501 to "EBOK", add 113 as asin
|
||||||
|
rec0 = self.datain_rec0
|
||||||
|
rec0 = del_exth(rec0, 501)
|
||||||
|
rec0 = del_exth(rec0, 113)
|
||||||
|
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||||
|
rec0 = add_exth(rec0, 113, asin)
|
||||||
|
replacesection(self.datain, 0, rec0)
|
||||||
|
|
||||||
|
ver = getint(self.datain_rec0, mobi_version)
|
||||||
|
self.combo = (ver != 8)
|
||||||
|
if not self.combo:
|
||||||
|
return
|
||||||
|
|
||||||
|
exth121 = read_exth(self.datain_rec0, 121)
|
||||||
|
if len(exth121) == 0:
|
||||||
|
self.combo = False
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# only pay attention to first exth121
|
||||||
|
# (there should only be one)
|
||||||
|
datain_kf8, = struct.unpack_from('>L', exth121[0], 0)
|
||||||
|
if datain_kf8 == 0xffffffff:
|
||||||
|
self.combo = False
|
||||||
|
return
|
||||||
|
self.datain_kfrec0 = readsection(self.datain, datain_kf8)
|
||||||
|
|
||||||
|
# in the second header
|
||||||
|
# add 501 to "EBOK", add 113 as asin
|
||||||
|
rec0 = self.datain_kfrec0
|
||||||
|
rec0 = del_exth(rec0, 501)
|
||||||
|
rec0 = del_exth(rec0, 113)
|
||||||
|
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||||
|
rec0 = add_exth(rec0, 113, asin)
|
||||||
|
replacesection(self.datain, datain_kf8, rec0)
|
||||||
|
|
||||||
|
self.datain.flush()
|
||||||
|
self.datain.close()
|
||||||
369
kindlecomicconverter/image.py
Executable file
@@ -0,0 +1,369 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010 Alex Yatskov
|
||||||
|
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
|
||||||
|
# Copyright (c) 2016 Alberto Planas <aplanas@gmail.com>
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
||||||
|
from .shared import md5Checksum
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileData:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
Palette4 = [
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
0x55, 0x55, 0x55,
|
||||||
|
0xaa, 0xaa, 0xaa,
|
||||||
|
0xff, 0xff, 0xff
|
||||||
|
]
|
||||||
|
|
||||||
|
Palette15 = [
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
0x11, 0x11, 0x11,
|
||||||
|
0x22, 0x22, 0x22,
|
||||||
|
0x33, 0x33, 0x33,
|
||||||
|
0x44, 0x44, 0x44,
|
||||||
|
0x55, 0x55, 0x55,
|
||||||
|
0x66, 0x66, 0x66,
|
||||||
|
0x77, 0x77, 0x77,
|
||||||
|
0x88, 0x88, 0x88,
|
||||||
|
0x99, 0x99, 0x99,
|
||||||
|
0xaa, 0xaa, 0xaa,
|
||||||
|
0xbb, 0xbb, 0xbb,
|
||||||
|
0xcc, 0xcc, 0xcc,
|
||||||
|
0xdd, 0xdd, 0xdd,
|
||||||
|
0xff, 0xff, 0xff,
|
||||||
|
]
|
||||||
|
|
||||||
|
Palette16 = [
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
0x11, 0x11, 0x11,
|
||||||
|
0x22, 0x22, 0x22,
|
||||||
|
0x33, 0x33, 0x33,
|
||||||
|
0x44, 0x44, 0x44,
|
||||||
|
0x55, 0x55, 0x55,
|
||||||
|
0x66, 0x66, 0x66,
|
||||||
|
0x77, 0x77, 0x77,
|
||||||
|
0x88, 0x88, 0x88,
|
||||||
|
0x99, 0x99, 0x99,
|
||||||
|
0xaa, 0xaa, 0xaa,
|
||||||
|
0xbb, 0xbb, 0xbb,
|
||||||
|
0xcc, 0xcc, 0xcc,
|
||||||
|
0xdd, 0xdd, 0xdd,
|
||||||
|
0xee, 0xee, 0xee,
|
||||||
|
0xff, 0xff, 0xff,
|
||||||
|
]
|
||||||
|
|
||||||
|
PalleteNull = [
|
||||||
|
]
|
||||||
|
|
||||||
|
Profiles = {
|
||||||
|
'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
|
||||||
|
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
||||||
|
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
||||||
|
'K578': ("Kindle", (600, 800), Palette16, 1.8),
|
||||||
|
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||||
|
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
|
||||||
|
'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
|
||||||
|
'KO': ("Kindle Oasis 2/3", (1264, 1680), 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),
|
||||||
|
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
||||||
|
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ComicPageParser:
|
||||||
|
def __init__(self, source, options):
|
||||||
|
Image.MAX_IMAGE_PIXELS = int(2048 * 2048 * 2048 // 4 // 3)
|
||||||
|
self.opt = options
|
||||||
|
self.source = source
|
||||||
|
self.size = self.opt.profileData[1]
|
||||||
|
self.payload = []
|
||||||
|
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
|
||||||
|
self.color = self.colorCheck()
|
||||||
|
self.fill = self.fillCheck()
|
||||||
|
self.splitCheck()
|
||||||
|
|
||||||
|
def getImageHistogram(self, image):
|
||||||
|
histogram = image.histogram()
|
||||||
|
if histogram[0] == 0:
|
||||||
|
return -1
|
||||||
|
elif histogram[255] == 0:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def splitCheck(self):
|
||||||
|
width, height = self.image.size
|
||||||
|
dstwidth, dstheight = self.size
|
||||||
|
if (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
|
||||||
|
and not self.opt.webtoon and self.opt.splitter == 1:
|
||||||
|
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True), self.color, self.fill])
|
||||||
|
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
||||||
|
if self.opt.splitter != 1:
|
||||||
|
if width > height:
|
||||||
|
leftbox = (0, 0, int(width / 2), height)
|
||||||
|
rightbox = (int(width / 2), 0, width, height)
|
||||||
|
else:
|
||||||
|
leftbox = (0, 0, width, int(height / 2))
|
||||||
|
rightbox = (0, int(height / 2), width, height)
|
||||||
|
if self.opt.righttoleft:
|
||||||
|
pageone = self.image.crop(rightbox)
|
||||||
|
pagetwo = self.image.crop(leftbox)
|
||||||
|
else:
|
||||||
|
pageone = self.image.crop(leftbox)
|
||||||
|
pagetwo = self.image.crop(rightbox)
|
||||||
|
self.payload.append(['S1', self.source, pageone, self.color, self.fill])
|
||||||
|
self.payload.append(['S2', self.source, pagetwo, self.color, self.fill])
|
||||||
|
if self.opt.splitter > 0:
|
||||||
|
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True),
|
||||||
|
self.color, self.fill])
|
||||||
|
else:
|
||||||
|
self.payload.append(['N', self.source, self.image, self.color, self.fill])
|
||||||
|
|
||||||
|
def colorCheck(self):
|
||||||
|
if self.opt.webtoon:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
img = self.image.copy()
|
||||||
|
bands = img.getbands()
|
||||||
|
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
|
||||||
|
thumb = img.resize((40, 40))
|
||||||
|
SSE, bias = 0, [0, 0, 0]
|
||||||
|
bias = ImageStat.Stat(thumb).mean[:3]
|
||||||
|
bias = [b - sum(bias) / 3 for b in bias]
|
||||||
|
for pixel in thumb.getdata():
|
||||||
|
mu = sum(pixel) / 3
|
||||||
|
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
|
||||||
|
MSE = float(SSE) / (40 * 40)
|
||||||
|
if MSE > 22:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def fillCheck(self):
|
||||||
|
if self.opt.bordersColor:
|
||||||
|
return self.opt.bordersColor
|
||||||
|
else:
|
||||||
|
bw = self.image.convert('L').point(lambda x: 0 if x < 128 else 255, '1')
|
||||||
|
imageBoxA = bw.getbbox()
|
||||||
|
imageBoxB = ImageChops.invert(bw).getbbox()
|
||||||
|
if imageBoxA is None or imageBoxB is None:
|
||||||
|
surfaceB, surfaceW = 0, 0
|
||||||
|
diff = 0
|
||||||
|
else:
|
||||||
|
surfaceB = (imageBoxA[2] - imageBoxA[0]) * (imageBoxA[3] - imageBoxA[1])
|
||||||
|
surfaceW = (imageBoxB[2] - imageBoxB[0]) * (imageBoxB[3] - imageBoxB[1])
|
||||||
|
diff = ((max(surfaceB, surfaceW) - min(surfaceB, surfaceW)) / min(surfaceB, surfaceW)) * 100
|
||||||
|
if diff > 0.5:
|
||||||
|
if surfaceW < surfaceB:
|
||||||
|
return 'white'
|
||||||
|
elif surfaceW > surfaceB:
|
||||||
|
return 'black'
|
||||||
|
else:
|
||||||
|
fill = 0
|
||||||
|
startY = 0
|
||||||
|
while startY < bw.size[1]:
|
||||||
|
if startY + 5 > bw.size[1]:
|
||||||
|
startY = bw.size[1] - 5
|
||||||
|
fill += self.getImageHistogram(bw.crop((0, startY, bw.size[0], startY + 5)))
|
||||||
|
startY += 5
|
||||||
|
startX = 0
|
||||||
|
while startX < bw.size[0]:
|
||||||
|
if startX + 5 > bw.size[0]:
|
||||||
|
startX = bw.size[0] - 5
|
||||||
|
fill += self.getImageHistogram(bw.crop((startX, 0, startX + 5, bw.size[1])))
|
||||||
|
startX += 5
|
||||||
|
if fill > 0:
|
||||||
|
return 'black'
|
||||||
|
else:
|
||||||
|
return 'white'
|
||||||
|
|
||||||
|
|
||||||
|
class ComicPage:
|
||||||
|
def __init__(self, options, mode, path, image, color, fill):
|
||||||
|
self.opt = options
|
||||||
|
_, self.size, self.palette, self.gamma = self.opt.profileData
|
||||||
|
if self.opt.hq:
|
||||||
|
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
||||||
|
self.image = image
|
||||||
|
self.color = color
|
||||||
|
self.fill = fill
|
||||||
|
self.rotated = False
|
||||||
|
self.orgPath = os.path.join(path[0], path[1])
|
||||||
|
if 'N' in mode:
|
||||||
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
|
||||||
|
elif 'R' in mode:
|
||||||
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
|
||||||
|
self.rotated = True
|
||||||
|
elif 'S1' in mode:
|
||||||
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-B'
|
||||||
|
elif 'S2' in mode:
|
||||||
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-C'
|
||||||
|
|
||||||
|
def saveToDir(self):
|
||||||
|
try:
|
||||||
|
flags = []
|
||||||
|
if not self.opt.forcecolor and not self.opt.forcepng:
|
||||||
|
self.image = self.image.convert('L')
|
||||||
|
if self.rotated:
|
||||||
|
flags.append('Rotated')
|
||||||
|
if self.fill != 'white':
|
||||||
|
flags.append('BlackBackground')
|
||||||
|
if self.opt.forcepng:
|
||||||
|
self.targetPath += '.png'
|
||||||
|
self.image.save(self.targetPath, 'PNG', optimize=1)
|
||||||
|
else:
|
||||||
|
self.targetPath += '.jpg'
|
||||||
|
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
|
||||||
|
return [md5Checksum(self.targetPath), flags, self.orgPath]
|
||||||
|
except IOError as err:
|
||||||
|
raise RuntimeError('Cannot save image. ' + str(err))
|
||||||
|
|
||||||
|
def autocontrastImage(self):
|
||||||
|
gamma = self.opt.gamma
|
||||||
|
if gamma < 0.1:
|
||||||
|
gamma = self.gamma
|
||||||
|
if self.gamma != 1.0 and self.color:
|
||||||
|
gamma = 1.0
|
||||||
|
if gamma == 1.0:
|
||||||
|
self.image = ImageOps.autocontrast(self.image)
|
||||||
|
else:
|
||||||
|
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: 255 * (a / 255.) ** gamma))
|
||||||
|
|
||||||
|
def quantizeImage(self):
|
||||||
|
colors = len(self.palette) // 3
|
||||||
|
if colors < 256:
|
||||||
|
self.palette += self.palette[:3] * (256 - colors)
|
||||||
|
palImg = Image.new('P', (1, 1))
|
||||||
|
palImg.putpalette(self.palette)
|
||||||
|
self.image = self.image.convert('L')
|
||||||
|
self.image = self.image.convert('RGB')
|
||||||
|
# Quantize is deprecated but new function call it internally anyway...
|
||||||
|
self.image = self.image.quantize(palette=palImg)
|
||||||
|
|
||||||
|
def resizeImage(self):
|
||||||
|
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
||||||
|
method = Image.BICUBIC
|
||||||
|
else:
|
||||||
|
method = Image.LANCZOS
|
||||||
|
if self.opt.stretch or (self.opt.kfx and ('-KCC-B' in self.targetPath or '-KCC-C' in self.targetPath)):
|
||||||
|
self.image = self.image.resize(self.size, method)
|
||||||
|
elif self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not self.opt.upscale:
|
||||||
|
if self.opt.format == 'CBZ' or self.opt.kfx:
|
||||||
|
borderw = int((self.size[0] - self.image.size[0]) / 2)
|
||||||
|
borderh = int((self.size[1] - self.image.size[1]) / 2)
|
||||||
|
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
|
||||||
|
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
|
||||||
|
self.image = ImageOps.fit(self.image, self.size, method=Image.BICUBIC, centering=(0.5, 0.5))
|
||||||
|
else:
|
||||||
|
if self.opt.format == 'CBZ' or self.opt.kfx:
|
||||||
|
ratioDev = float(self.size[0]) / float(self.size[1])
|
||||||
|
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
|
||||||
|
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
||||||
|
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
|
||||||
|
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
||||||
|
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
||||||
|
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
|
||||||
|
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
|
||||||
|
else:
|
||||||
|
hpercent = self.size[1] / float(self.image.size[1])
|
||||||
|
wsize = int((float(self.image.size[0]) * float(hpercent)))
|
||||||
|
self.image = self.image.resize((wsize, self.size[1]), method)
|
||||||
|
if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]:
|
||||||
|
self.image.thumbnail(self.size, Image.LANCZOS)
|
||||||
|
|
||||||
|
def getBoundingBox(self, tmptmg):
|
||||||
|
min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
|
||||||
|
max_margin = [int(0.1 * i + 0.5) for i in tmptmg.size]
|
||||||
|
bbox = tmptmg.getbbox()
|
||||||
|
bbox = (
|
||||||
|
max(0, min(max_margin[0], bbox[0] - min_margin[0])),
|
||||||
|
max(0, min(max_margin[1], bbox[1] - min_margin[1])),
|
||||||
|
min(tmptmg.size[0],
|
||||||
|
max(tmptmg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
|
||||||
|
min(tmptmg.size[1],
|
||||||
|
max(tmptmg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
|
||||||
|
)
|
||||||
|
return bbox
|
||||||
|
|
||||||
|
def cropPageNumber(self, power):
|
||||||
|
if self.fill != 'white':
|
||||||
|
tmptmg = self.image.convert(mode='L')
|
||||||
|
else:
|
||||||
|
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
||||||
|
tmptmg = tmptmg.point(lambda x: x and 255)
|
||||||
|
tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
|
||||||
|
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
|
||||||
|
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
||||||
|
self.image = self.image.crop(tmptmg.getbbox()) if tmptmg.getbbox() else self.image
|
||||||
|
|
||||||
|
def cropMargin(self, power):
|
||||||
|
if self.fill != 'white':
|
||||||
|
tmptmg = self.image.convert(mode='L')
|
||||||
|
else:
|
||||||
|
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
|
||||||
|
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
|
||||||
|
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
|
||||||
|
self.image = self.image.crop(self.getBoundingBox(tmptmg)) if tmptmg.getbbox() else self.image
|
||||||
|
|
||||||
|
|
||||||
|
class Cover:
|
||||||
|
def __init__(self, source, target, opt, tomeid):
|
||||||
|
self.options = opt
|
||||||
|
self.source = source
|
||||||
|
self.target = target
|
||||||
|
if tomeid == 0:
|
||||||
|
self.tomeid = 1
|
||||||
|
else:
|
||||||
|
self.tomeid = tomeid
|
||||||
|
self.image = Image.open(source)
|
||||||
|
self.process()
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
self.image = self.image.convert('RGB')
|
||||||
|
self.image = ImageOps.autocontrast(self.image)
|
||||||
|
if not self.options.forcecolor:
|
||||||
|
self.image = self.image.convert('L')
|
||||||
|
self.image.thumbnail(self.options.profileData[1], Image.LANCZOS)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
try:
|
||||||
|
self.image.save(self.target, "JPEG", optimize=1, quality=85)
|
||||||
|
except IOError:
|
||||||
|
raise RuntimeError('Failed to save cover.')
|
||||||
|
|
||||||
|
def saveToKindle(self, kindle, asin):
|
||||||
|
self.image = self.image.resize((300, 470), Image.ANTIALIAS)
|
||||||
|
try:
|
||||||
|
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
|
||||||
|
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=85)
|
||||||
|
except IOError:
|
||||||
|
raise RuntimeError('Failed to upload cover.')
|
||||||
44
kindlecomicconverter/kindle.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
|
||||||
|
class Kindle:
|
||||||
|
def __init__(self):
|
||||||
|
self.path = self.findDevice()
|
||||||
|
if self.path:
|
||||||
|
self.coverSupport = self.checkThumbnails()
|
||||||
|
else:
|
||||||
|
self.coverSupport = False
|
||||||
|
|
||||||
|
def findDevice(self):
|
||||||
|
for drive in reversed(psutil.disk_partitions(False)):
|
||||||
|
if (drive[2] == 'FAT32' and drive[3] == 'rw,removable') or \
|
||||||
|
(drive[2] == 'vfat' and 'rw' in drive[3]) or \
|
||||||
|
(drive[2] == 'msdos' and 'rw' in drive[3]):
|
||||||
|
if os.path.isdir(os.path.join(drive[1], 'system')) and \
|
||||||
|
os.path.isdir(os.path.join(drive[1], 'documents')):
|
||||||
|
return drive[1]
|
||||||
|
return False
|
||||||
|
|
||||||
|
def checkThumbnails(self):
|
||||||
|
if os.path.isdir(os.path.join(self.path, 'system', 'thumbnails')):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
120
kindlecomicconverter/metadata.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import os
|
||||||
|
from xml.dom.minidom import parse, Document
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from shutil import rmtree
|
||||||
|
from . import comicarchive
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataParser:
|
||||||
|
def __init__(self, source):
|
||||||
|
self.source = source
|
||||||
|
self.data = {'Series': '',
|
||||||
|
'Volume': '',
|
||||||
|
'Number': '',
|
||||||
|
'Writers': [],
|
||||||
|
'Pencillers': [],
|
||||||
|
'Inkers': [],
|
||||||
|
'Colorists': [],
|
||||||
|
'Summary': '',
|
||||||
|
'Bookmarks': []}
|
||||||
|
self.rawdata = None
|
||||||
|
self.format = None
|
||||||
|
if self.source.endswith('.xml') and os.path.exists(self.source):
|
||||||
|
self.rawdata = parse(self.source)
|
||||||
|
elif not self.source.endswith('.xml'):
|
||||||
|
try:
|
||||||
|
cbx = comicarchive.ComicArchive(self.source)
|
||||||
|
self.rawdata = cbx.extractMetadata()
|
||||||
|
self.format = cbx.type
|
||||||
|
except OSError as e:
|
||||||
|
raise UserWarning(e.strerror)
|
||||||
|
if self.rawdata:
|
||||||
|
self.parseXML()
|
||||||
|
|
||||||
|
def parseXML(self):
|
||||||
|
if len(self.rawdata.getElementsByTagName('Series')) != 0:
|
||||||
|
self.data['Series'] = self.rawdata.getElementsByTagName('Series')[0].firstChild.nodeValue
|
||||||
|
if len(self.rawdata.getElementsByTagName('Volume')) != 0:
|
||||||
|
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
|
||||||
|
if len(self.rawdata.getElementsByTagName('Number')) != 0:
|
||||||
|
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
|
||||||
|
if len(self.rawdata.getElementsByTagName('Summary')) != 0:
|
||||||
|
self.data['Summary'] = self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue
|
||||||
|
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
|
||||||
|
if len(self.rawdata.getElementsByTagName(field)) != 0:
|
||||||
|
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
|
||||||
|
self.data[field + 's'].append(person)
|
||||||
|
self.data[field + 's'] = list(set(self.data[field + 's']))
|
||||||
|
self.data[field + 's'].sort()
|
||||||
|
if len(self.rawdata.getElementsByTagName('Page')) != 0:
|
||||||
|
for page in self.rawdata.getElementsByTagName('Page'):
|
||||||
|
if 'Bookmark' in page.attributes and 'Image' in page.attributes:
|
||||||
|
self.data['Bookmarks'].append((int(page.attributes['Image'].value),
|
||||||
|
page.attributes['Bookmark'].value))
|
||||||
|
|
||||||
|
def saveXML(self):
|
||||||
|
if self.rawdata:
|
||||||
|
root = self.rawdata.getElementsByTagName('ComicInfo')[0]
|
||||||
|
for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
|
||||||
|
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
|
||||||
|
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
|
||||||
|
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]):
|
||||||
|
if self.rawdata.getElementsByTagName(row[0]):
|
||||||
|
node = self.rawdata.getElementsByTagName(row[0])[0]
|
||||||
|
if row[1]:
|
||||||
|
node.firstChild.replaceWholeText(row[1])
|
||||||
|
else:
|
||||||
|
root.removeChild(node)
|
||||||
|
elif row[1]:
|
||||||
|
main = self.rawdata.createElement(row[0])
|
||||||
|
root.appendChild(main)
|
||||||
|
text = self.rawdata.createTextNode(row[1])
|
||||||
|
main.appendChild(text)
|
||||||
|
else:
|
||||||
|
doc = Document()
|
||||||
|
root = doc.createElement('ComicInfo')
|
||||||
|
root.setAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema')
|
||||||
|
root.setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
|
||||||
|
doc.appendChild(root)
|
||||||
|
for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
|
||||||
|
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
|
||||||
|
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
|
||||||
|
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]):
|
||||||
|
if row[1]:
|
||||||
|
main = doc.createElement(row[0])
|
||||||
|
root.appendChild(main)
|
||||||
|
text = doc.createTextNode(row[1])
|
||||||
|
main.appendChild(text)
|
||||||
|
self.rawdata = doc
|
||||||
|
if self.source.endswith('.xml'):
|
||||||
|
with open(self.source, 'w', encoding='utf-8') as f:
|
||||||
|
self.rawdata.writexml(f, encoding='utf-8')
|
||||||
|
else:
|
||||||
|
workdir = mkdtemp('', 'KCC-')
|
||||||
|
tmpXML = os.path.join(workdir, 'ComicInfo.xml')
|
||||||
|
with open(tmpXML, 'w', encoding='utf-8') as f:
|
||||||
|
self.rawdata.writexml(f, encoding='utf-8')
|
||||||
|
try:
|
||||||
|
cbx = comicarchive.ComicArchive(self.source)
|
||||||
|
cbx.addFile(tmpXML)
|
||||||
|
except OSError as e:
|
||||||
|
raise UserWarning(e.strerror)
|
||||||
|
rmtree(workdir)
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
# -*- 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
|
# Based upon the code snippet by Ned Batchelder
|
||||||
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
|
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
|
||||||
@@ -18,56 +21,49 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from random import choice
|
||||||
|
from string import ascii_uppercase, digits
|
||||||
|
|
||||||
|
|
||||||
class PdfJpgExtract:
|
class PdfJpgExtract:
|
||||||
def __init__(self, origFileName):
|
def __init__(self, fname):
|
||||||
self.origFileName = origFileName
|
self.fname = fname
|
||||||
self.filename = os.path.splitext(origFileName)
|
self.filename = os.path.splitext(fname)
|
||||||
self.path = self.filename[0]
|
self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for _ in range(3))
|
||||||
|
|
||||||
def getPath(self):
|
def getPath(self):
|
||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
def extract(self):
|
def extract(self):
|
||||||
pdf = file(self.origFileName, "rb").read()
|
pdf = open(self.fname, "rb").read()
|
||||||
|
startmark = b"\xff\xd8"
|
||||||
startmark = "\xff\xd8"
|
|
||||||
startfix = 0
|
startfix = 0
|
||||||
endmark = "\xff\xd9"
|
endmark = b"\xff\xd9"
|
||||||
endfix = 2
|
endfix = 2
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
njpg = 0
|
njpg = 0
|
||||||
os.makedirs(self.path)
|
os.makedirs(self.path)
|
||||||
while True:
|
while True:
|
||||||
istream = pdf.find("stream", i)
|
istream = pdf.find(b"stream", i)
|
||||||
if istream < 0:
|
if istream < 0:
|
||||||
break
|
break
|
||||||
istart = pdf.find(startmark, istream, istream + 20)
|
istart = pdf.find(startmark, istream, istream + 20)
|
||||||
if istart < 0:
|
if istart < 0:
|
||||||
i = istream + 20
|
i = istream + 20
|
||||||
continue
|
continue
|
||||||
iend = pdf.find("endstream", istart)
|
iend = pdf.find(b"endstream", istart)
|
||||||
if iend < 0:
|
if iend < 0:
|
||||||
raise Exception("Didn't find end of stream!")
|
raise Exception("Didn't find end of stream!")
|
||||||
iend = pdf.find(endmark, iend - 20)
|
iend = pdf.find(endmark, iend - 20)
|
||||||
if iend < 0:
|
if iend < 0:
|
||||||
raise Exception("Didn't find end of JPG!")
|
raise Exception("Didn't find end of JPG!")
|
||||||
|
|
||||||
istart += startfix
|
istart += startfix
|
||||||
iend += endfix
|
iend += endfix
|
||||||
print "JPG %d from %d to %d" % (njpg, istart, iend)
|
|
||||||
jpg = pdf[istart:iend]
|
jpg = pdf[istart:iend]
|
||||||
jpgfile = file(self.path + "/jpg%d.jpg" % njpg, "wb")
|
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
|
||||||
jpgfile.write(jpg)
|
jpgfile.write(jpg)
|
||||||
jpgfile.close()
|
jpgfile.close()
|
||||||
|
|
||||||
njpg += 1
|
njpg += 1
|
||||||
i = iend
|
i = iend
|
||||||
return self.path
|
return self.path, njpg
|
||||||
133
kindlecomicconverter/shared.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
from hashlib import md5
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
from re import split
|
||||||
|
from traceback import format_tb
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLStripper(HTMLParser):
|
||||||
|
def __init__(self):
|
||||||
|
HTMLParser.__init__(self)
|
||||||
|
self.reset()
|
||||||
|
self.strict = False
|
||||||
|
self.convert_charrefs = True
|
||||||
|
self.fed = []
|
||||||
|
|
||||||
|
def handle_data(self, d):
|
||||||
|
self.fed.append(d)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return ''.join(self.fed)
|
||||||
|
|
||||||
|
def error(self, message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def getImageFileName(imgfile):
|
||||||
|
name, ext = os.path.splitext(imgfile)
|
||||||
|
ext = ext.lower()
|
||||||
|
if (name.startswith('.') and len(name) == 1) or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp']:
|
||||||
|
return None
|
||||||
|
return [name, ext]
|
||||||
|
|
||||||
|
|
||||||
|
def walkSort(dirnames, filenames):
|
||||||
|
convert = lambda text: int(text) if text.isdigit() else text
|
||||||
|
alphanum_key = lambda key: [convert(c) for c in split('([0-9]+)', key)]
|
||||||
|
dirnames.sort(key=lambda name: alphanum_key(name.lower()))
|
||||||
|
filenames.sort(key=lambda name: alphanum_key(name.lower()))
|
||||||
|
return dirnames, filenames
|
||||||
|
|
||||||
|
|
||||||
|
def walkLevel(some_dir, level=1):
|
||||||
|
some_dir = some_dir.rstrip(os.path.sep)
|
||||||
|
assert os.path.isdir(some_dir)
|
||||||
|
num_sep = some_dir.count(os.path.sep)
|
||||||
|
for root, dirs, files in os.walk(some_dir):
|
||||||
|
dirs, files = walkSort(dirs, files)
|
||||||
|
yield root, dirs, files
|
||||||
|
num_sep_this = root.count(os.path.sep)
|
||||||
|
if num_sep + level <= num_sep_this:
|
||||||
|
del dirs[:]
|
||||||
|
|
||||||
|
|
||||||
|
def md5Checksum(fpath):
|
||||||
|
with open(fpath, 'rb') as fh:
|
||||||
|
m = md5()
|
||||||
|
while True:
|
||||||
|
data = fh.read(8192)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
m.update(data)
|
||||||
|
return m.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def sanitizeTrace(traceback):
|
||||||
|
return ''.join(format_tb(traceback))\
|
||||||
|
.replace('C:/projects/kcc/', '')\
|
||||||
|
.replace('c:/projects/kcc/', '')\
|
||||||
|
.replace('C:/python37-x64/', '')\
|
||||||
|
.replace('c:/python37-x64/', '')\
|
||||||
|
.replace('C:\\projects\\kcc\\', '')\
|
||||||
|
.replace('c:\\projects\\kcc\\', '')\
|
||||||
|
.replace('C:\\python37-x64\\', '')\
|
||||||
|
.replace('c:\\python37-x64\\', '')
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
def dependencyCheck(level):
|
||||||
|
missing = []
|
||||||
|
if level > 2:
|
||||||
|
try:
|
||||||
|
from PyQt5.QtCore import qVersion as qtVersion
|
||||||
|
if StrictVersion('5.6.0') > StrictVersion(qtVersion()):
|
||||||
|
missing.append('PyQt 5.6.0+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('PyQt 5.6.0+')
|
||||||
|
try:
|
||||||
|
import raven
|
||||||
|
except ImportError:
|
||||||
|
missing.append('raven 6.0.0+')
|
||||||
|
if level > 1:
|
||||||
|
try:
|
||||||
|
from psutil import __version__ as psutilVersion
|
||||||
|
if StrictVersion('5.0.0') > StrictVersion(psutilVersion):
|
||||||
|
missing.append('psutil 5.0.0+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('psutil 5.0.0+')
|
||||||
|
try:
|
||||||
|
from slugify import __version__ as slugifyVersion
|
||||||
|
if StrictVersion('1.2.1') > StrictVersion(slugifyVersion):
|
||||||
|
missing.append('python-slugify 1.2.1+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('python-slugify 1.2.1+')
|
||||||
|
try:
|
||||||
|
from PIL import __version__ as pillowVersion
|
||||||
|
if StrictVersion('5.2.0') > StrictVersion(pillowVersion):
|
||||||
|
missing.append('Pillow 5.2.0+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('Pillow 5.2.0+')
|
||||||
|
if len(missing) > 0:
|
||||||
|
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||||
|
exit(1)
|
||||||
56
kindlecomicconverter/startup.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
# any purpose with or without fee is hereby granted, provided that the
|
||||||
|
# above copyright notice and this permission notice appear in all
|
||||||
|
# copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||||
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||||
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from . import __version__
|
||||||
|
from .shared import dependencyCheck
|
||||||
|
|
||||||
|
|
||||||
|
def start():
|
||||||
|
dependencyCheck(3)
|
||||||
|
from . import KCC_gui
|
||||||
|
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1"
|
||||||
|
KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
|
||||||
|
if KCCAplication.isRunning():
|
||||||
|
for i in range(1, len(sys.argv)):
|
||||||
|
KCCAplication.sendMessage(sys.argv[i])
|
||||||
|
else:
|
||||||
|
KCCAplication.sendMessage('ARISE')
|
||||||
|
else:
|
||||||
|
KCCWindow = KCC_gui.QMainWindowKCC()
|
||||||
|
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
|
||||||
|
for i in range(1, len(sys.argv)):
|
||||||
|
KCCUI.handleMessage(sys.argv[i])
|
||||||
|
sys.exit(KCCAplication.exec_())
|
||||||
|
|
||||||
|
|
||||||
|
def startC2E():
|
||||||
|
dependencyCheck(2)
|
||||||
|
from .comic2ebook import main
|
||||||
|
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
|
|
||||||
|
|
||||||
|
def startC2P():
|
||||||
|
dependencyCheck(1)
|
||||||
|
from .comic2panel import main
|
||||||
|
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
BIN
other/osx/7z
Executable file
BIN
other/osx/7z.so
Normal file
72
other/osx/Info.plist
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Kindle Comic Converter</string>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>cbz</string>
|
||||||
|
<string>cbr</string>
|
||||||
|
<string>cb7</string>
|
||||||
|
<string>zip</string>
|
||||||
|
<string>rar</string>
|
||||||
|
<string>7z</string>
|
||||||
|
<string>pdf</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>comic2ebook.icns</string>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Comics</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>MacOS/Kindle Comic Converter</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>KindleComicConverter 5.5.2, written 2012-2019 by Ciro Mattia Gonano and Pawel Jastrzebski</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>comic2ebook.icns</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.kindlecomicconverter.KindleComicConverter</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Kindle Comic Converter</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>5.5.2</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>5.5.2</string>
|
||||||
|
<key>LSEnvironment</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>./../Resources:/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS:/usr/local/bin:/usr/bin:/bin</string>
|
||||||
|
</dict>
|
||||||
|
<key>LSHasLocalizedDisplayName</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.14.0</string>
|
||||||
|
<key>NSAppleScriptEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>ISC License (ISCL)</string>
|
||||||
|
<key>NSMainNibFile</key>
|
||||||
|
<string>MainMenu</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
<key>NSRequiresAquaSystemAppearance</key>
|
||||||
|
<string>false</string>
|
||||||
|
<key>NSInitialToolTipDelay</key>
|
||||||
|
<integer>1000</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
other/osx/Rar.so
Normal file
BIN
other/windows/7z.dll
Normal file
BIN
other/windows/7z.exe
Normal file
90
other/windows/Additional-LICENSE.txt
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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
|
||||||
233
other/windows/InstallWarning.rtf
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
{\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}}
|
||||||
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
PyQt5>=5.6.0
|
||||||
|
Pillow>=5.2.0
|
||||||
|
psutil>=5.0.0
|
||||||
|
python-slugify>=1.2.1,<3.0.0
|
||||||
|
raven>=6.0.0
|
||||||
148
setup.py
Normal file → Executable file
@@ -1,87 +1,89 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
py2app/cx_Freeze build script for KCC.
|
pip/pyinstaller build script for KCC.
|
||||||
|
|
||||||
Will automatically ensure that all build prerequisites are available via ez_setup
|
Install as Python package:
|
||||||
|
python3 setup.py install
|
||||||
|
|
||||||
Usage (Mac OS X):
|
Create EXE/APP:
|
||||||
python setup.py py2app
|
python3 setup.py build_binary
|
||||||
|
|
||||||
Usage (Windows):
|
|
||||||
python setup.py build
|
|
||||||
"""
|
"""
|
||||||
from ez_setup import use_setuptools
|
|
||||||
use_setuptools()
|
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
|
import setuptools
|
||||||
|
import distutils.cmd
|
||||||
|
from kindlecomicconverter import __version__
|
||||||
|
|
||||||
NAME = "KindleComicConverter"
|
NAME = 'KindleComicConverter'
|
||||||
VERSION = "2.7"
|
MAIN = 'kcc.py'
|
||||||
MAIN = "kcc.py"
|
VERSION = __version__
|
||||||
|
|
||||||
includefiles = ['README.md', 'MANIFEST.in', 'LICENSE.txt', 'comic2ebook.ico', 'comic2ebook.icns']
|
|
||||||
includes = []
|
|
||||||
excludes = []
|
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
# noinspection PyUnresolvedReferences
|
||||||
from setuptools import setup
|
class BuildBinaryCommand(distutils.cmd.Command):
|
||||||
extra_options = dict(
|
description = 'build binary release'
|
||||||
setup_requires=['py2app'],
|
user_options = []
|
||||||
app=[MAIN],
|
|
||||||
options=dict(
|
|
||||||
py2app=dict(
|
|
||||||
argv_emulation=True,
|
|
||||||
iconfile='comic2ebook.icns',
|
|
||||||
plist=dict(
|
|
||||||
CFBundleName=NAME,
|
|
||||||
CFBundleShortVersionString=VERSION,
|
|
||||||
CFBundleGetInfoString=NAME + " " + VERSION + ", written 2012-2013 by Ciro Mattia Gonano",
|
|
||||||
CFBundleExecutable=NAME,
|
|
||||||
CFBundleIdentifier='com.github.ciromattia.kcc',
|
|
||||||
CFBundleSignature='dplt'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif sys.platform == 'win32':
|
|
||||||
from cx_Freeze import setup, Executable
|
|
||||||
base = "Win32GUI"
|
|
||||||
extra_options = dict(
|
|
||||||
options={"build_exe": {"include_files": includefiles, 'excludes': excludes, 'compressed': True}},
|
|
||||||
executables=[Executable(MAIN,
|
|
||||||
base=base,
|
|
||||||
icon="comic2ebook.ico",
|
|
||||||
copyDependentFiles=True,
|
|
||||||
appendScriptToExe=True,
|
|
||||||
appendScriptToLibrary=False,
|
|
||||||
compress=True)])
|
|
||||||
else:
|
|
||||||
extra_options = dict(
|
|
||||||
scripts=[MAIN],
|
|
||||||
)
|
|
||||||
|
|
||||||
setup(
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def run(self):
|
||||||
|
VERSION = __version__
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
||||||
|
os.makedirs('dist/Kindle Comic Converter.app/Contents/Resources/Codecs')
|
||||||
|
shutil.copy('other/osx/7z', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||||
|
shutil.copy('other/osx/7z.so', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||||
|
shutil.copy('other/osx/Rar.so', 'dist/Kindle Comic Converter.app/Contents/Resources/Codecs')
|
||||||
|
shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents')
|
||||||
|
shutil.copy('LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||||
|
shutil.copy('other/windows/Additional-LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||||
|
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7z', 0o777)
|
||||||
|
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
|
||||||
|
exit(0)
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC -w --noupx kcc.py')
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
cmdclass={
|
||||||
|
'build_binary': BuildBinaryCommand,
|
||||||
|
},
|
||||||
name=NAME,
|
name=NAME,
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
author="Ciro Mattia Gonano",
|
author='Ciro Mattia Gonano, Pawel Jastrzebski',
|
||||||
author_email="ciromattia@gmail.com",
|
author_email='ciromattia@gmail.com, pawelj@iosphe.re',
|
||||||
description="A tool to convert comics (CBR/CBZ/PDFs/image folders) to Mobipocket.",
|
description='Comic and Manga converter for e-book readers.',
|
||||||
license="ISC License (ISCL)",
|
license='ISC License (ISCL)',
|
||||||
keywords="kindle comic mobipocket mobi cbz cbr manga",
|
keywords=['kindle', 'kobo', 'comic', 'manga', 'mobi', 'epub', 'cbz'],
|
||||||
url="http://github.com/ciromattia/kcc",
|
url='http://github.com/ciromattia/kcc',
|
||||||
classifiers=[
|
entry_points={
|
||||||
'Development Status :: 4 - Beta'
|
'console_scripts': [
|
||||||
'License :: OSI Approved :: ISC License (ISCL)',
|
'kcc-c2e = kindlecomicconverter.startup:startC2E',
|
||||||
'Environment :: Console',
|
'kcc-c2p = kindlecomicconverter.startup:startC2P',
|
||||||
'Environment :: MacOS X',
|
],
|
||||||
'Environment :: Win32 (MS Windows)',
|
'gui_scripts': [
|
||||||
'Environment :: X11 Applications',
|
'kcc = kindlecomicconverter.startup:start',
|
||||||
'Intended Audience :: End Users/Desktop',
|
],
|
||||||
'Operating System :: OS Independent',
|
},
|
||||||
'Programming Language :: Python',
|
packages=['kindlecomicconverter'],
|
||||||
'Programming Language :: Python :: 2.7',
|
install_requires=[
|
||||||
'Topic :: Multimedia :: Graphics :: Graphics Conversion',
|
'PyQt5>=5.6.0',
|
||||||
'Topic :: Utilities'
|
'Pillow>=5.2.0',
|
||||||
|
'psutil>=5.0.0',
|
||||||
|
'python-slugify>=1.2.1',
|
||||||
|
'raven>=6.0.0',
|
||||||
],
|
],
|
||||||
packages=['kcc'],
|
classifiers=[],
|
||||||
**extra_options
|
zip_safe=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
"""
|
|
||||||
cx_freeze build script for Windows KCC No-GUI release.
|
|
||||||
|
|
||||||
Usage (Windows):
|
|
||||||
python setup_console.py build
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from cx_Freeze import setup, Executable
|
|
||||||
sys.path.insert(0, 'kcc')
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name = "KindleComicConverter",
|
|
||||||
version = "2.7",
|
|
||||||
author = "Ciro Mattia Gonano",
|
|
||||||
author_email = "ciromattia@gmail.com",
|
|
||||||
description = "A tool to convert comics (CBR/CBZ/PDFs/image folders) to MOBI.",
|
|
||||||
license= "ISC License (ISCL)",
|
|
||||||
keywords= "kindle comic mobipocket mobi cbz cbr manga",
|
|
||||||
url = "http://github.com/ciromattia/kcc",
|
|
||||||
executables = [Executable("kcc/comic2ebook.py", compress=True, copyDependentFiles=True, appendScriptToExe=True, appendScriptToLibrary=False),
|
|
||||||
Executable("kcc/kindlestrip.py", compress=True, copyDependentFiles=True, appendScriptToExe=True, appendScriptToLibrary=False)]
|
|
||||||
)
|
|
||||||