Compare commits
672 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
14
.gitignore
vendored
@@ -2,6 +2,16 @@
|
|||||||
*.cbz
|
*.cbz
|
||||||
*.cbr
|
*.cbr
|
||||||
.idea
|
.idea
|
||||||
build
|
|
||||||
awkcc
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
dist
|
||||||
|
Output
|
||||||
|
test
|
||||||
|
solaio
|
||||||
|
kindlegen*
|
||||||
|
*.spec
|
||||||
|
setup.bat
|
||||||
|
setup.sh
|
||||||
|
kindlecomicconverter/sentry.py
|
||||||
|
build/
|
||||||
|
.python-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-2017 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 +0,0 @@
|
|||||||
include README.md MANIFEST.in LICENSE.txt
|
|
||||||
588
README.md
@@ -1,142 +1,506 @@
|
|||||||
# KCC
|
# KCC
|
||||||
|
|
||||||
`KCC` (a.k.a. `KindleComicConverter`) is a Python app to convert comic files or folders to ePub or Panel View MOBI.
|
|
||||||
It was initally developed for Kindle but since v2.2 it outputs valid ePub 2.0 so _**despite its name, KCC is
|
|
||||||
actually a comic 2 epub converter that every ereader owner can happily use**_.
|
|
||||||
|
|
||||||
|
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
|
||||||
|
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
|
||||||
|
actually a comic/manga to EPUB converter that every e-reader owner can happily use**_.
|
||||||
It can also optionally optimize images by applying a number of transformations.
|
It can also optionally optimize images by applying a number of transformations.
|
||||||
|
|
||||||
### A word of warning
|
### A word of warning
|
||||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||||
Amazon's tool is for comic _publishers_ and involves a lot of manual effort, while **KCC** is for comic _readers_.
|
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||||
If you want to read some comments over *Amazon's kc2* you can take a look at [this](http://www.mobileread.com/forums/showthread.php?t=207461&page=7#96) and [that](http://www.mobileread.com/forums/showthread.php?t=211047) threads on Mobileread.
|
_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 ;-)
|
||||||
_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)
|
||||||
|
- Bitcoin: 1W15wwqsfd7wbaZ6wvSJf1LW1bz6q5L8b
|
||||||
|
|
||||||
## 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.9.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.9.zip)
|
- **Windows (64-bit only):** [http://kcc.iosphe.re/Windows/](http://kcc.iosphe.re/Windows/)
|
||||||
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.9.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.9.zip)
|
- **Linux (Glibc 2.19+):** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/)
|
||||||
- Win32: [http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.9.zip](http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.9.zip) *(thanks to [AcidWeb](https://github.com/AcidWeb))*
|
- **OS X (10.9+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/)
|
||||||
- Linux: Just download sourcecode and launch `python kcc.py` *(Provided you have Python and Pillow installed)*
|
|
||||||
|
## DEPENDENCIES
|
||||||
|
Following software is required to run Linux version of **KCC** and/or bare sources:
|
||||||
|
- Python 3.3+
|
||||||
|
- [PyQt](https://pypi.python.org/pypi/PyQt5) 5.6.0+
|
||||||
|
- [Pillow](https://pypi.python.org/pypi/Pillow/) 3.2.0+
|
||||||
|
- [psutil](https://pypi.python.org/pypi/psutil) 4.1.0+
|
||||||
|
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.0+
|
||||||
|
- [raven](https://pypi.python.org/pypi/raven) 5.13.0+
|
||||||
|
- [scandir](https://pypi.python.org/pypi/scandir) 1.2.0+ _(needed only when using Python 3.3 or 3.4)_
|
||||||
|
|
||||||
|
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 unrar
|
||||||
|
sudo pip3 install --upgrade pillow python-slugify psutil scandir raven pyqt5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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)*
|
||||||
|
- [UnRAR](http://www.rarlab.com/download.htm) *(For CBR/RAR support)*
|
||||||
|
- [7za](http://www.7-zip.org/download.html) *(For 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, GIF, TIFF, BMP
|
- Folders containing: PNG, JPG or GIF files
|
||||||
- Folders
|
|
||||||
- CBZ, ZIP
|
- CBZ, ZIP
|
||||||
- CBR, RAR *(With `unrar` executable)*
|
- CBR, RAR *(With `unrar` executable)*
|
||||||
- PDF *(Extracting only contained JPG images)*
|
- CB7, 7Z *(With `7za` executable)*
|
||||||
|
- 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 informations 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 is not idiot-proof like GUI :-)
|
||||||
|
|
||||||
|
### Standalone `kcc-c2e.py` usage:
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: comic2ebook.py [options] comic_file|comic_folder
|
Usage: kcc-c2e [options] comic_file|comic_folder
|
||||||
|
|
||||||
Options:
|
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, K3, K45,
|
||||||
Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG or KHD) [Default=KHD]
|
KDX, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
|
||||||
-t TITLE, --title=TITLE
|
KoAO) [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]
|
-2, --two-panel Display two not four panels in Panel View mode
|
||||||
-c, --cbz-output Outputs a CBZ archive and does not generate EPUB
|
-w, --webtoon Webtoon processing mode
|
||||||
--nopanelviewhq Disable high quality Panel View [Default=False]
|
|
||||||
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
|
OUTPUT SETTINGS:
|
||||||
--forcepng Create PNG files instead JPEG (For non-Kindle devices) [Default=False]
|
-o OUTPUT, --output=OUTPUT
|
||||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
Output generated file to specified directory or file
|
||||||
--upscale Resize images smaller than device's resolution [Default=False]
|
-t TITLE, --title=TITLE
|
||||||
--stretch Stretch images to device's resolution [Default=False]
|
Comic title [Default=filename or directory name]
|
||||||
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
|
-f FORMAT, --format=FORMAT
|
||||||
--rotate Rotate landscape pages instead of splitting them [Default=False]
|
Output format (Available options: Auto, MOBI, EPUB,
|
||||||
--nosplitrotate Disable splitting and rotation [Default=False]
|
CBZ) [Default=Auto]
|
||||||
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
|
-b BATCHSPLIT, --batchsplit=BATCHSPLIT
|
||||||
-o OUTPUT, --output=OUTPUT
|
Split output into multiple files. 0: Don't split 1:
|
||||||
Output generated file (EPUB or CBZ) to specified directory or file
|
Automatic mode 2: Consider every subdirectory as
|
||||||
-v, --verbose Verbose output [Default=False]
|
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 splitted 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))
|
- `rarfile.py` script © 2005-2014 **Marko Kreen** <markokr@gmail.com>. Released with ISC License.
|
||||||
- `rarfile.py` script © 2005-2011 **Marko Kreen** <markokr@gmail.com>, released with ISC License
|
- `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.
|
||||||
- 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
|
- 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.
|
||||||
- `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
|
## SAMPLE FILES CREATED BY KCC
|
||||||
|
* [Kindle Paperwhite 3 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||||
|
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
||||||
|
* [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K45.mobi)
|
||||||
|
* [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub)
|
||||||
|
* [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub)
|
||||||
|
* [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub)
|
||||||
|
* [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub)
|
||||||
|
|
||||||
## CHANGELOG
|
## CHANGELOG
|
||||||
- 1.00: Initial version
|
####5.3:
|
||||||
- 1.10: Added support for CBZ/CBR files in comic2ebook.py
|
* Vastly improved output compatibility for non-Kindle devices
|
||||||
- 1.11: Added support for CBZ/CBR files in KindleComicConverter
|
* Enabled old pinch zoom for Kindle devices
|
||||||
- 1.20: Comic optimizations! Split pages not target-oriented (landscape with portrait target or portrait
|
* Re-enabled Panel View support for Kindle Keyboard
|
||||||
with landscape target), add palette and other image optimizations from Mangle.
|
* Partially re-enabled OS X file association mechanism
|
||||||
WARNING: PIL is required for all image mangling!
|
* Fixed multiple smaller issues
|
||||||
- 1.30: Fixed an issue in OPF generation for device resolution
|
|
||||||
Reworked options system (call with -h option to get the inline help)
|
|
||||||
- 1.40: Added some options for controlling image optimization
|
|
||||||
Further optimization (ImageOps, page numbering cut, autocontrast)
|
|
||||||
- 1.41: Fixed a serious bug on resizing when img ratio was bigger than device one
|
|
||||||
- 1.50: Added subfolder support for multiple chapters.
|
|
||||||
- 2.0: GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support.
|
|
||||||
- 2.1: Added basic error reporting
|
|
||||||
- 2.2: Added (valid!) ePub 2.0 output
|
|
||||||
Rename .zip files to .cbz to avoid overwriting
|
|
||||||
- 2.3: Fixed win32 ePub generation, folder handling, filenames with spaces and subfolders
|
|
||||||
- 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
|
|
||||||
- 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.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)
|
|
||||||
|
|
||||||
|
####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
|
||||||
|
|
||||||
|
## PRIVACY
|
||||||
|
**KCC** is initiating internet connections in three cases:
|
||||||
|
* During startup - Version check
|
||||||
|
* When MCD metadata are used - Cover download
|
||||||
|
* When error occurs - Automatic reporting
|
||||||
|
|
||||||
|
## KNOWN ISSUES
|
||||||
|
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||||
|
|
||||||
## COPYRIGHT
|
## COPYRIGHT
|
||||||
|
Copyright (c) 2012-2017 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.
|
|
||||||
|
|||||||
18
docker/Build
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
pip3 install --upgrade pip setuptools wheel
|
||||||
|
pip3 install pillow python-slugify psutil pyinstaller raven pyqt5 certifi
|
||||||
|
gem install fpm
|
||||||
|
|
||||||
|
cd /app
|
||||||
|
pyinstaller -F -s kcc.py
|
||||||
|
mkdir -p dist/usr/bin dist/usr/share/applications dist/usr/share/doc/kindlecomicconverter dist/usr/share/kindlecomicconverter dist/usr/share/lintian/overrides
|
||||||
|
mv dist/kcc dist/usr/bin
|
||||||
|
cp icons/comic2ebook.png dist/usr/share/kindlecomicconverter
|
||||||
|
cp LICENSE.txt dist/usr/share/doc/kindlecomicconverter/copyright
|
||||||
|
cp other/linux/kindlecomicconverter.desktop dist/usr/share/applications
|
||||||
|
cp other/linux/kindlecomicconverter dist/usr/share/lintian/overrides
|
||||||
|
|
||||||
|
cd /app/dist
|
||||||
|
fpm -f -s dir -t deb -n kindlecomicconverter -v $KCCVER -m "Paweł Jastrzębski <pawelj@iosphe.re>" --license "ISC" --description "$(printf "Comic and Manga converter for e-book readers.\nThis app allows you to transform your PNG, JPG, GIF, CBZ, CBR and CB7 files\ninto EPUB or MOBI format e-books.")" --url "https://kcc.iosphe.re/" --deb-priority "optional" --vendor "" --category "graphics" -d "unrar | unrar-free" -d "p7zip-full" -d "libc6" usr
|
||||||
15
docker/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# acidweb/kcc
|
||||||
|
FROM debian:jessie
|
||||||
|
MAINTAINER Paweł Jastrzębski <pawelj@iosphe.re>
|
||||||
|
|
||||||
|
ADD ./Build /Build
|
||||||
|
|
||||||
|
RUN printf "deb http://httpredir.debian.org/debian stretch main" > /etc/apt/sources.list.d/stretch.list
|
||||||
|
RUN printf "Package: *\nPin: release a=testing\nPin-Priority: 400\n" > /etc/apt/preferences.d/stretch.pref
|
||||||
|
RUN apt-get update && apt-get -y dist-upgrade
|
||||||
|
RUN apt-get -y install build-essential curl ruby ruby-dev libpng-dev libjpeg-dev
|
||||||
|
RUN apt-get -y -t testing install python3 python3-dev python3-pyqt5
|
||||||
|
RUN curl https://bootstrap.pypa.io/get-pip.py | python3
|
||||||
|
RUN apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
CMD /Build
|
||||||
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>
|
||||||
481
gui/KCC.ui
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
<?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="margin">
|
||||||
|
<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 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 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 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 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="margin">
|
||||||
|
<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;\">Checked - 2 panels<br/></span>Zoom only the top and bottom of the page.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Panel View 4/2</string>
|
||||||
|
</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>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>Output will be splitted 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 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="margin">
|
||||||
|
<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="margin">
|
||||||
|
<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="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="margin">
|
||||||
|
<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>
|
||||||
202
gui/MetaEditor.ui
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<?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>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string><html><head/><body><p><a href="https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support"><span style=" text-decoration: underline; color:#0000ff;">MUid:</span></a></p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="QLineEdit" name="muidLine"/>
|
||||||
|
</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 |
36
kcc-c2e.py
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2017 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 kindlecomicconverter.shared import dependencyCheck
|
||||||
|
dependencyCheck(2)
|
||||||
|
|
||||||
|
from multiprocessing import freeze_support
|
||||||
|
from kindlecomicconverter import __version__
|
||||||
|
from kindlecomicconverter.comic2ebook import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
freeze_support()
|
||||||
|
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
36
kcc-c2p.py
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2017 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 kindlecomicconverter.shared import dependencyCheck
|
||||||
|
dependencyCheck(1)
|
||||||
|
|
||||||
|
from multiprocessing import freeze_support
|
||||||
|
from kindlecomicconverter import __version__
|
||||||
|
from kindlecomicconverter.comic2panel import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
freeze_support()
|
||||||
|
print('comic2panel v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
125
kcc.iss
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#define MyAppName "Kindle Comic Converter"
|
||||||
|
#define MyAppVersion "5.3.0"
|
||||||
|
#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-2017 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\UnRAR.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "other\windows\7za.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "other\windows\vc_redist.x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafterinstall
|
||||||
|
|
||||||
|
[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: "{tmp}\vc_redist.x64.exe"; Parameters: "/install /passive /norestart"; StatusMsg: "Installing Microsoft Visual C++ 2015 Redistributable Package..."
|
||||||
|
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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
89
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-2017 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,70 @@
|
|||||||
# 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.9'
|
|
||||||
__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:/usr/local/bin:/usr/bin:/bin'
|
||||||
|
os.system('defaults write com.kindlecomicconverter.KindleComicConverter ApplePersistenceIgnoreState YES')
|
||||||
|
os.system('defaults write com.kindlecomicconverter.KindleComicConverter NSInitialToolTipDelay -int 1000')
|
||||||
|
else:
|
||||||
|
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/osx/:' + os.environ['PATH']
|
||||||
|
elif sys.platform.startswith('win'):
|
||||||
|
import multiprocessing.popen_spawn_win32 as forking
|
||||||
|
|
||||||
root = Tk()
|
class _Popen(forking.Popen):
|
||||||
root.resizable(width=False, height=False)
|
def __init__(self, *args, **kw):
|
||||||
root.config(padx=5, pady=5, takefocus=True)
|
if hasattr(sys, 'frozen'):
|
||||||
root.title("Kindle Comic Converter v" + __version__)
|
# noinspection PyProtectedMember
|
||||||
#root.wm_attributes("-topmost", 1)
|
os.putenv('_MEIPASS2', sys._MEIPASS)
|
||||||
if platform == 'darwin':
|
try:
|
||||||
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
|
super(_Popen, self).__init__(*args, **kw)
|
||||||
elif platform == 'win32':
|
finally:
|
||||||
root.iconbitmap(default='comic2ebook.ico')
|
if hasattr(sys, 'frozen'):
|
||||||
gui.MainWindow(master=root)
|
if hasattr(os, 'unsetenv'):
|
||||||
root.mainloop()
|
os.unsetenv('_MEIPASS2')
|
||||||
|
else:
|
||||||
|
os.putenv('_MEIPASS2', '')
|
||||||
|
forking.Popen = _Popen
|
||||||
|
|
||||||
|
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:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from kindlecomicconverter.shared import dependencyCheck
|
||||||
|
dependencyCheck(3)
|
||||||
|
|
||||||
|
from multiprocessing import freeze_support
|
||||||
|
from kindlecomicconverter import KCC_gui
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
freeze_support()
|
||||||
|
os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1"
|
||||||
|
KCCAplication = KCC_gui.QApplicationMessaging(sys.argv)
|
||||||
|
if KCCAplication.isRunning():
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
KCCAplication.sendMessage(sys.argv[1])
|
||||||
|
else:
|
||||||
|
KCCAplication.sendMessage('ARISE')
|
||||||
|
else:
|
||||||
|
KCCWindow = KCC_gui.QMainWindowKCC()
|
||||||
|
KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow)
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
KCCUI.handleMessage(sys.argv[1])
|
||||||
|
sys.exit(KCCAplication.exec_())
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
__version__ = '2.0'
|
|
||||||
__license__ = 'ISC'
|
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
@@ -1,81 +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)
|
|
||||||
filelist = []
|
|
||||||
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:
|
|
||||||
filelist.append(f)
|
|
||||||
cbzFile.extractall(targetdir, filelist)
|
|
||||||
|
|
||||||
def extractCBR(self, targetdir):
|
|
||||||
cbrFile = rarfile.RarFile(self.origFileName)
|
|
||||||
filelist = []
|
|
||||||
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:
|
|
||||||
filelist.append(f)
|
|
||||||
cbrFile.extractall(targetdir, filelist)
|
|
||||||
|
|
||||||
def extract(self, targetdir):
|
|
||||||
print "\n" + targetdir + "\n"
|
|
||||||
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,692 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# 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.9'
|
|
||||||
__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),
|
|
||||||
"style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
|
||||||
"</head>\n",
|
|
||||||
"<body>\n",
|
|
||||||
"<div class=\"fs\">\n",
|
|
||||||
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
|
||||||
imgfile, "\" class=\"singlePage\"/></div>\n"
|
|
||||||
])
|
|
||||||
if options.panelview:
|
|
||||||
if options.righttoleft:
|
|
||||||
f.writelines(["<div id=\"BoxTL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxTL-Panel-Parent\", \"ordinal\":2}'></a></div>\n",
|
|
||||||
"<div id=\"BoxTR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxTR-Panel-Parent\", \"ordinal\":1}'></a></div>\n",
|
|
||||||
"<div id=\"BoxBL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxBL-Panel-Parent\", \"ordinal\":4}'></a></div>\n",
|
|
||||||
"<div id=\"BoxBR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxBR-Panel-Parent\", \"ordinal\":3}'></a></div>\n"
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
f.writelines(["<div id=\"BoxTL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxTL-Panel-Parent\", \"ordinal\":1}'></a></div>\n",
|
|
||||||
"<div id=\"BoxTR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxTR-Panel-Parent\", \"ordinal\":2}'></a></div>\n",
|
|
||||||
"<div id=\"BoxBL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxBL-Panel-Parent\", \"ordinal\":3}'></a></div>\n",
|
|
||||||
"<div id=\"BoxBR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
|
||||||
"BoxBR-Panel-Parent\", \"ordinal\":4}'></a></div>\n"
|
|
||||||
])
|
|
||||||
f.writelines(["<div id=\"BoxTL-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxTL-Panel\" class=\"",
|
|
||||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
|
||||||
imgfile, "\"/></div></div>\n",
|
|
||||||
"<div id=\"BoxTR-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxTR-Panel\" class=\"",
|
|
||||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
|
||||||
imgfile, "\"/></div></div>\n",
|
|
||||||
"<div id=\"BoxBL-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxBL-Panel\" class=\"",
|
|
||||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
|
||||||
imgfile, "\"/></div></div>\n",
|
|
||||||
"<div id=\"BoxBR-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxBR-Panel\" class=\"",
|
|
||||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
|
||||||
imgfile, "\"/></div></div>\n"
|
|
||||||
])
|
|
||||||
f.writelines(["</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):
|
|
||||||
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
|
|
||||||
# read the first file resolution
|
|
||||||
profilelabel, deviceres, palette, gamma, panelviewsize = image.ProfileData.Profiles[profile]
|
|
||||||
imgres = str(deviceres[0]) + "x" + str(deviceres[1])
|
|
||||||
if options.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"
|
|
||||||
])
|
|
||||||
if options.landscapemode:
|
|
||||||
f.writelines(["<meta name=\"rendition:orientation\" content=\"auto\"/>\n",
|
|
||||||
"<meta name=\"orientation-lock\" content=\"none\"/>\n"])
|
|
||||||
else:
|
|
||||||
f.writelines(["<meta name=\"rendition:orientation\" content=\"portrait\"/>\n",
|
|
||||||
"<meta name=\"orientation-lock\" content=\"portrait\"/>\n"])
|
|
||||||
f.writelines(["<meta name=\"original-resolution\" content=\"", imgres, "\"/>\n",
|
|
||||||
"<meta name=\"primary-writing-mode\" content=\"", writingmode, "\"/>\n",
|
|
||||||
"<meta name=\"rendition:layout\" content=\"pre-paginated\"/>\n",
|
|
||||||
"</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ",
|
|
||||||
"media-type=\"application/x-dtbncx+xml\"/>\n"])
|
|
||||||
if cover is not None:
|
|
||||||
filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\'))
|
|
||||||
if '.png' == filename[1]:
|
|
||||||
mt = 'image/png'
|
|
||||||
else:
|
|
||||||
mt = 'image/jpeg'
|
|
||||||
f.write("<item id=\"cover\" href=\"" + 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.landscapemode 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("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n")
|
|
||||||
f.write("</manifest>\n<spine toc=\"ncx\">\n")
|
|
||||||
splitCountUsed = 1
|
|
||||||
for entry in reflist:
|
|
||||||
if entry.endswith("-1"):
|
|
||||||
# noinspection PyRedundantParentheses
|
|
||||||
if ((options.righttoleft and facing == 'left') or (not options.righttoleft and facing == 'right')) and\
|
|
||||||
options.landscapemode:
|
|
||||||
f.write("<itemref idref=\"blank-page" + str(splitCountUsed) + "\" properties=\"layout-blank\"/>\n")
|
|
||||||
splitCountUsed += 1
|
|
||||||
if options.landscapemode:
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing1 + "\"/>\n")
|
|
||||||
else:
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
|
|
||||||
elif entry.endswith("-2"):
|
|
||||||
if options.landscapemode:
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing2 + "\"/>\n")
|
|
||||||
else:
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
|
|
||||||
if options.righttoleft:
|
|
||||||
facing = "right"
|
|
||||||
else:
|
|
||||||
facing = "left"
|
|
||||||
else:
|
|
||||||
if options.landscapemode:
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing + "\"/>\n")
|
|
||||||
else:
|
|
||||||
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
|
|
||||||
if facing == 'right':
|
|
||||||
facing = 'left'
|
|
||||||
else:
|
|
||||||
facing = 'right'
|
|
||||||
f.write("</spine>\n<guide>\n</guide>\n</package>\n")
|
|
||||||
f.close()
|
|
||||||
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() != '.gif' and
|
|
||||||
filename[1].lower() != '.tif' and
|
|
||||||
filename[1].lower() != '.tiff' and
|
|
||||||
filename[1].lower() != '.bmp' 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.cropWhiteSpace(10.0)
|
|
||||||
if options.cutpagenumbers:
|
|
||||||
img.cutPageNumber()
|
|
||||||
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight, options.landscapemode,
|
|
||||||
options.nopanelviewhq)
|
|
||||||
img.optimizeImage(options.gamma)
|
|
||||||
if options.forcepng:
|
|
||||||
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
|
|
||||||
if facing == "left":
|
|
||||||
splitCount += 1
|
|
||||||
facing = "right"
|
|
||||||
else:
|
|
||||||
toRight1 = True
|
|
||||||
toRight2 = False
|
|
||||||
if facing == "right":
|
|
||||||
splitCount += 1
|
|
||||||
facing = "left"
|
|
||||||
img0 = image.ComicPage(split[0], options.profile)
|
|
||||||
applyImgOptimization(img0, True, toRight1)
|
|
||||||
img0.saveToDir(dirpath, options.forcepng)
|
|
||||||
img1 = image.ComicPage(split[1], options.profile)
|
|
||||||
applyImgOptimization(img1, True, toRight2)
|
|
||||||
img1.saveToDir(dirpath, options.forcepng)
|
|
||||||
else:
|
|
||||||
if facing == "right":
|
|
||||||
facing = "left"
|
|
||||||
else:
|
|
||||||
facing = "right"
|
|
||||||
applyImgOptimization(img)
|
|
||||||
img.saveToDir(dirpath, options.forcepng)
|
|
||||||
|
|
||||||
|
|
||||||
def genEpubStruct(path):
|
|
||||||
global options
|
|
||||||
filelist = []
|
|
||||||
chapterlist = []
|
|
||||||
cover = None
|
|
||||||
_, deviceres, _, _, panelviewsize = image.ProfileData.Profiles[options.profile]
|
|
||||||
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
|
||||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
|
||||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w')
|
|
||||||
#DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT.
|
|
||||||
#Generic Panel View support + Margins fix for Non-Kindle devices.
|
|
||||||
f.writelines(["@page {\n",
|
|
||||||
"margin-bottom: 0;\n",
|
|
||||||
"margin-top: 0\n",
|
|
||||||
"}\n",
|
|
||||||
"body {\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",
|
|
||||||
"div.fs {\n",
|
|
||||||
"height: ", str(deviceres[1]), "px;\n",
|
|
||||||
"width: ", str(deviceres[0]), "px;\n",
|
|
||||||
"position: relative;\n",
|
|
||||||
"display: block;\n",
|
|
||||||
"text-align: center\n",
|
|
||||||
"}\n",
|
|
||||||
"div.fs a {\n",
|
|
||||||
"display: block;\n",
|
|
||||||
"width : 100%;\n",
|
|
||||||
"height: 100%;\n",
|
|
||||||
"}\n",
|
|
||||||
"div.fs div {\n",
|
|
||||||
"position: absolute;\n",
|
|
||||||
"}\n",
|
|
||||||
"img.singlePage {\n",
|
|
||||||
"position: absolute;\n",
|
|
||||||
"height: ", str(deviceres[1]), "px;\n",
|
|
||||||
"width: ", str(deviceres[0]), "px;\n",
|
|
||||||
"}\n",
|
|
||||||
"div.target-mag-parent {\n",
|
|
||||||
"width:100%;\n",
|
|
||||||
"height:100%;\n",
|
|
||||||
"display:none;\n",
|
|
||||||
"}\n",
|
|
||||||
"div.target-mag {\n",
|
|
||||||
"position: absolute;\n",
|
|
||||||
"display: block;\n",
|
|
||||||
"overflow: hidden;\n",
|
|
||||||
"}\n",
|
|
||||||
"div.target-mag img {\n",
|
|
||||||
"position: absolute;\n",
|
|
||||||
"height: ", str(panelviewsize[1]), "px;\n",
|
|
||||||
"width: ", str(panelviewsize[0]), "px;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxTL {\n",
|
|
||||||
"top: 0;\n",
|
|
||||||
"left: 0;\n",
|
|
||||||
"height: 50%;\n",
|
|
||||||
"width: 50%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxTR {\n",
|
|
||||||
"top: 0;\n",
|
|
||||||
"right: 0;\n",
|
|
||||||
"height: 50%;\n",
|
|
||||||
"width: 50%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxBL {\n",
|
|
||||||
"bottom: 0;\n",
|
|
||||||
"left: 0;\n",
|
|
||||||
"height: 50%;\n",
|
|
||||||
"width: 50%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxBR {\n",
|
|
||||||
"bottom: 0;\n",
|
|
||||||
"right: 0;\n",
|
|
||||||
"height: 50%;\n",
|
|
||||||
"width: 50%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxTL-Panel {\n",
|
|
||||||
"top: 0;\n",
|
|
||||||
"left: 0;\n",
|
|
||||||
"height: 100%;\n",
|
|
||||||
"width: 100%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxTL-Panel img {\n",
|
|
||||||
"top: 0%;\n",
|
|
||||||
"left: 0%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxTR-Panel {\n",
|
|
||||||
"top: 0;\n",
|
|
||||||
"right: 0;\n",
|
|
||||||
"height: 100%;\n",
|
|
||||||
"width: 100%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxTR-Panel img {\n",
|
|
||||||
"top: 0%;\n",
|
|
||||||
"right: 0%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxBL-Panel {\n",
|
|
||||||
"bottom: 0;\n",
|
|
||||||
"left: 0;\n",
|
|
||||||
"height: 100%;\n",
|
|
||||||
"width: 100%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxBL-Panel img {\n",
|
|
||||||
"bottom: 0%;\n",
|
|
||||||
"left: 0%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxBR-Panel {\n",
|
|
||||||
"bottom: 0;\n",
|
|
||||||
"right: 0;\n",
|
|
||||||
"height: 100%;\n",
|
|
||||||
"width: 100%;\n",
|
|
||||||
"}\n",
|
|
||||||
"#BoxBR-Panel img {\n",
|
|
||||||
"bottom: 0%;\n",
|
|
||||||
"right: 0%;\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:
|
|
||||||
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)
|
|
||||||
if options.landscapemode 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 slugify(value):
|
|
||||||
"""
|
|
||||||
Normalizes string, converts to lowercase, removes non-alpha characters,
|
|
||||||
and converts spaces to hyphens.
|
|
||||||
"""
|
|
||||||
import unicodedata
|
|
||||||
value = unicodedata.normalize('NFKD', unicode(value, 'latin1')).encode('ascii', 'ignore')
|
|
||||||
value = re.sub('[^\w\s\.-]', '', value).strip().lower()
|
|
||||||
value = re.sub('[-\.\s]+', '-', value)
|
|
||||||
value = re.sub(r'([0-9]+)', r'00000\1', value)
|
|
||||||
value = re.sub(r'0*([0-9]{6,})', r'\1', value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def sanitizeTree(filetree):
|
|
||||||
for root, dirs, files in os.walk(filetree):
|
|
||||||
for name in files:
|
|
||||||
if name.startswith('.') or name.lower() == 'thumbs.db':
|
|
||||||
os.remove(os.path.join(root, name))
|
|
||||||
else:
|
|
||||||
splitname = os.path.splitext(name)
|
|
||||||
os.rename(os.path.join(root, name),
|
|
||||||
os.path.join(root, slugify(splitname[0]) + splitname[1]))
|
|
||||||
for name in dirs:
|
|
||||||
if name.startswith('.'):
|
|
||||||
os.remove(os.path.join(root, name))
|
|
||||||
else:
|
|
||||||
os.rename(os.path.join(root, name), os.path.join(root, slugify(name)))
|
|
||||||
|
|
||||||
|
|
||||||
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."
|
|
||||||
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, K4NT, K4T, 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("-c", "--cbz-output", action="store_true", dest="cbzoutput", default=False,
|
|
||||||
help="Outputs a CBZ archive and does not generate EPUB")
|
|
||||||
parser.add_option("--nopanelviewhq", action="store_true", dest="nopanelviewhq", default=False,
|
|
||||||
help="Disable high quality Panel View [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("--forcepng", action="store_true", dest="forcepng", default=False,
|
|
||||||
help="Create PNG files instead JPEG (For non-Kindle devices) [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 file (EPUB or CBZ) 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)
|
|
||||||
checkOptions()
|
|
||||||
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/")
|
|
||||||
if options.cbzoutput:
|
|
||||||
# if CBZ output wanted, compress all images and return filepath
|
|
||||||
print "\nCreating CBZ file..."
|
|
||||||
filepath = getOutputFilename(args[0], options.output, '.cbz')
|
|
||||||
make_archive(path + '_comic', 'zip', path + '/OEBPS/Images')
|
|
||||||
else:
|
|
||||||
print "\nCreating ePub structure..."
|
|
||||||
genEpubStruct(path)
|
|
||||||
# actually zip the ePub
|
|
||||||
filepath = getOutputFilename(args[0], options.output, '.epub')
|
|
||||||
make_archive(path + '_comic', 'zip', path)
|
|
||||||
move(path + '_comic.zip', filepath)
|
|
||||||
rmtree(path)
|
|
||||||
return filepath
|
|
||||||
|
|
||||||
|
|
||||||
def getOutputFilename(srcpath, wantedname, ext):
|
|
||||||
if not ext.startswith('.'):
|
|
||||||
ext = '.' + ext
|
|
||||||
if wantedname is not None:
|
|
||||||
if wantedname.endswith(ext):
|
|
||||||
filename = os.path.abspath(wantedname)
|
|
||||||
elif os.path.isdir(srcpath):
|
|
||||||
filename = os.path.abspath(options.output) + "/" + os.path.basename(srcpath) + ext
|
|
||||||
else:
|
|
||||||
filename = os.path.abspath(options.output) + "/" \
|
|
||||||
+ os.path.basename(os.path.splitext(srcpath)[0]) + ext
|
|
||||||
elif os.path.isdir(srcpath):
|
|
||||||
filename = srcpath + ext
|
|
||||||
else:
|
|
||||||
filename = os.path.splitext(srcpath)[0] + ext
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
filename = os.path.splitext(filename)[0] + '_kcc' + ext
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
|
||||||
def checkOptions():
|
|
||||||
global options
|
|
||||||
if options.profile == 'K4T' or options.profile == 'KHD':
|
|
||||||
options.landscapemode = True
|
|
||||||
else:
|
|
||||||
options.landscapemode = False
|
|
||||||
if options.profile == 'K3' or options.profile == 'K4NT':
|
|
||||||
#Real Panel View
|
|
||||||
options.panelview = True
|
|
||||||
else:
|
|
||||||
#Virtual Panel View
|
|
||||||
options.panelview = False
|
|
||||||
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX' or options.profile == 'KDXG':
|
|
||||||
options.nopanelviewhq = True
|
|
||||||
|
|
||||||
|
|
||||||
def getEpubPath():
|
|
||||||
global epub_path
|
|
||||||
return epub_path
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
Copyright()
|
|
||||||
main(sys.argv[1:])
|
|
||||||
sys.exit(0)
|
|
||||||
267
kcc/gui.py
@@ -1,267 +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
|
|
||||||
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),
|
|
||||||
'Bcbz_only': IntVar(None, 0),
|
|
||||||
'Cmangastyle': IntVar(None, 0),
|
|
||||||
'Dnopanelviewhq': IntVar(None, 0),
|
|
||||||
'Eimage_preprocess': IntVar(None, 0),
|
|
||||||
'Fforcepng': IntVar(None, 0),
|
|
||||||
'Gimage_gamma': DoubleVar(None, 0.0),
|
|
||||||
'Himage_upscale': IntVar(None, 0),
|
|
||||||
'Iimage_stretch': IntVar(None, 0),
|
|
||||||
'Jblack_borders': IntVar(None, 0),
|
|
||||||
'Krotate': IntVar(None, 0),
|
|
||||||
'Lnosplitrotate': IntVar(None, 0),
|
|
||||||
'Mcut_page_numbers': IntVar(None, 0)
|
|
||||||
}
|
|
||||||
self.optionlabels = {
|
|
||||||
'Aepub_only': "Generate EPUB only",
|
|
||||||
'Bcbz_only': "Generate CBZ only (skip EPUB/Mobi generation)",
|
|
||||||
'Cmangastyle': "Manga mode",
|
|
||||||
'Dnopanelviewhq': "Disable high quality Panel View",
|
|
||||||
'Eimage_preprocess': "Disable image optimizations",
|
|
||||||
'Fforcepng': "Create PNG files instead of JPEG",
|
|
||||||
'Gimage_gamma': "Custom gamma correction",
|
|
||||||
'Himage_upscale': "Allow image upscaling",
|
|
||||||
'Iimage_stretch': "Stretch images",
|
|
||||||
'Jblack_borders': "Use black borders (instead of white ones)",
|
|
||||||
'Krotate': "Rotate images (instead of splitting them)",
|
|
||||||
'Lnosplitrotate': "Disable both splitting and rotation",
|
|
||||||
'Mcut_page_numbers': "Disable page numbers cutting"
|
|
||||||
}
|
|
||||||
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['Bcbz_only'].get() == 1:
|
|
||||||
argv.append("-c")
|
|
||||||
if self.options['Cmangastyle'].get() == 1:
|
|
||||||
argv.append("-m")
|
|
||||||
if self.options['Dnopanelviewhq'].get() == 1:
|
|
||||||
argv.append("--nopanelviewhq")
|
|
||||||
if self.options['Eimage_preprocess'].get() == 1:
|
|
||||||
argv.append("--noprocessing")
|
|
||||||
if self.options['Fforcepng'].get() == 1:
|
|
||||||
argv.append("--forcepng")
|
|
||||||
if self.options['Gimage_gamma'].get() != 0.0:
|
|
||||||
argv.append("--gamma")
|
|
||||||
argv.append(self.options['Gimage_gamma'].get())
|
|
||||||
if self.options['Himage_upscale'].get() == 1:
|
|
||||||
argv.append("--upscale")
|
|
||||||
if self.options['Iimage_stretch'].get() == 1:
|
|
||||||
argv.append("--stretch")
|
|
||||||
if self.options['Jblack_borders'].get() == 1:
|
|
||||||
argv.append("--blackborders")
|
|
||||||
if self.options['Krotate'].get() == 1:
|
|
||||||
argv.append("--rotate")
|
|
||||||
if self.options['Lnosplitrotate'].get() == 1:
|
|
||||||
argv.append("--nosplitrotate")
|
|
||||||
if self.options['Mcut_page_numbers'].get() == 1:
|
|
||||||
argv.append("--nocutpagenumbers")
|
|
||||||
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 and self.options['Bcbz_only'].get() == 0:
|
|
||||||
try:
|
|
||||||
if os.path.getsize(epub_path) > 314572800:
|
|
||||||
# do not call kindlegen if source is bigger than 300MB
|
|
||||||
tkMessageBox.showwarning('KindleGen Warning',
|
|
||||||
"ePub file %s is bigger than 300MB, 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()
|
|
||||||
375
kcc/image.py
@@ -1,375 +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, 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 1", (600, 800), Palette4, 1.8, (900, 1200)),
|
|
||||||
'K2': ("Kindle 2", (600, 800), Palette15, 1.8, (900, 1200)),
|
|
||||||
'K3': ("Kindle Keyboard", (600, 800), Palette16, 1.8, (900, 1200)),
|
|
||||||
'K4NT': ("Kindle Non-Touch", (600, 800), Palette16, 1.8, (900, 1200)),
|
|
||||||
'K4T': ("Kindle Touch", (600, 800), Palette16, 1.8, (900, 1200)),
|
|
||||||
'KHD': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
|
||||||
'KDX': ("Kindle DX", (824, 1200), Palette15, 1.8, (1236, 1800)),
|
|
||||||
'KDXG': ("Kindle DXG", (824, 1200), Palette16, 1.8, (1236, 1800))
|
|
||||||
}
|
|
||||||
|
|
||||||
ProfileLabels = {
|
|
||||||
"Kindle 1": 'K1',
|
|
||||||
"Kindle 2": 'K2',
|
|
||||||
"Kindle 3/Keyboard": 'K3',
|
|
||||||
"Kindle 4/Non-Touch": 'K4NT',
|
|
||||||
"Kindle 4/Touch": 'K4T',
|
|
||||||
"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, self.panelviewsize = 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, forcepng):
|
|
||||||
filename = os.path.basename(self.origFileName)
|
|
||||||
try:
|
|
||||||
self.image = self.image.convert('L') # convert to grayscale
|
|
||||||
os.remove(os.path.join(targetdir, filename))
|
|
||||||
if forcepng:
|
|
||||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".png"), "PNG")
|
|
||||||
else:
|
|
||||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".jpg"), "JPEG")
|
|
||||||
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):
|
|
||||||
self.image = self.image.convert('L') # convert to grayscale
|
|
||||||
self.image = self.image.convert("RGB") # convert back to RGB
|
|
||||||
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,
|
|
||||||
landscapeMode=False, noPanelViewHQ=False):
|
|
||||||
method = Image.ANTIALIAS
|
|
||||||
if black_borders:
|
|
||||||
fill = 'black'
|
|
||||||
else:
|
|
||||||
fill = 'white'
|
|
||||||
if noPanelViewHQ:
|
|
||||||
size = (self.size[0], self.size[1])
|
|
||||||
else:
|
|
||||||
size = (self.panelviewsize[0], self.panelviewsize[1])
|
|
||||||
if isSplit and landscapeMode:
|
|
||||||
upscale = True
|
|
||||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
|
||||||
if not upscale:
|
|
||||||
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.BILINEAR
|
|
||||||
if stretch: # if stretching call directly resize() without other considerations.
|
|
||||||
self.image = self.image.resize(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:
|
|
||||||
if isSplit and landscapeMode:
|
|
||||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
|
||||||
self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill)
|
|
||||||
tempImg = Image.new(self.image.mode, (self.image.size[0] + diff, self.image.size[1]), fill)
|
|
||||||
if toRight:
|
|
||||||
tempImg.paste(self.image, (diff, 0))
|
|
||||||
else:
|
|
||||||
tempImg.paste(self.image, (0, 0))
|
|
||||||
self.image = tempImg
|
|
||||||
else:
|
|
||||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
|
||||||
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, 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 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
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
@@ -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)
|
|
||||||
1133
kindlecomicconverter/KCC_gui.py
Normal file
11315
kindlecomicconverter/KCC_rc.py
Normal file
271
kindlecomicconverter/KCC_ui.py
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file 'gui\KCC.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_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.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 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 target device.</p></body></html>"))
|
||||||
|
self.wLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of 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 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;\">Checked - 2 panels<br/></span>Zoom only the top and bottom of the page.</p></body></html>"))
|
||||||
|
self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2"))
|
||||||
|
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>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>Output will be splitted 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 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.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
|
||||||
124
kindlecomicconverter/KCC_ui_editor.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# -*- 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.label_8 = QtWidgets.QLabel(self.editorWidget)
|
||||||
|
self.label_8.setOpenExternalLinks(True)
|
||||||
|
self.label_8.setObjectName("label_8")
|
||||||
|
self.gridLayout.addWidget(self.label_8, 7, 0, 1, 1)
|
||||||
|
self.muidLine = QtWidgets.QLineEdit(self.editorWidget)
|
||||||
|
self.muidLine.setObjectName("muidLine")
|
||||||
|
self.gridLayout.addWidget(self.muidLine, 7, 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.label_8.setText(_translate("editorDialog", "<html><head/><body><p><a href=\"https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support\"><span style=\" text-decoration: underline; color:#0000ff;\">MUid:</span></a></p></body></html>"))
|
||||||
|
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.3.0'
|
||||||
|
__license__ = 'ISC'
|
||||||
|
__copyright__ = '2012-2017, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
105
kindlecomicconverter/cbxarchive.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2017 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
|
||||||
|
import os
|
||||||
|
from zipfile import is_zipfile, ZipFile
|
||||||
|
from subprocess import STDOUT, PIPE
|
||||||
|
from psutil import Popen
|
||||||
|
from shutil import move, copy
|
||||||
|
try:
|
||||||
|
from scandir import walk
|
||||||
|
except ImportError:
|
||||||
|
walk = os.walk
|
||||||
|
from . import rarfile
|
||||||
|
from .shared import check7ZFile as is_7zfile, saferReplace, saferRemove
|
||||||
|
|
||||||
|
|
||||||
|
class CBxArchive:
|
||||||
|
def __init__(self, origFileName):
|
||||||
|
self.origFileName = origFileName
|
||||||
|
if is_zipfile(origFileName):
|
||||||
|
self.compressor = 'zip'
|
||||||
|
elif rarfile.is_rarfile(origFileName):
|
||||||
|
self.compressor = 'rar'
|
||||||
|
elif is_7zfile(origFileName):
|
||||||
|
self.compressor = '7z'
|
||||||
|
else:
|
||||||
|
self.compressor = None
|
||||||
|
|
||||||
|
def isCbxFile(self):
|
||||||
|
return self.compressor is not None
|
||||||
|
|
||||||
|
def extractCBZ(self, targetdir):
|
||||||
|
cbzFile = ZipFile(self.origFileName)
|
||||||
|
filelist = []
|
||||||
|
for f in cbzFile.namelist():
|
||||||
|
if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('humbs.db'):
|
||||||
|
pass # skip MacOS special files
|
||||||
|
elif f.endswith('/'):
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.join(targetdir, f))
|
||||||
|
except Exception:
|
||||||
|
pass # the dir exists so we are going to extract the images only.
|
||||||
|
else:
|
||||||
|
filelist.append(f)
|
||||||
|
cbzFile.extractall(targetdir, filelist)
|
||||||
|
|
||||||
|
def extractCBR(self, targetdir):
|
||||||
|
cbrFile = rarfile.RarFile(self.origFileName)
|
||||||
|
cbrFile.extractall(targetdir)
|
||||||
|
for root, dirnames, filenames in walk(targetdir):
|
||||||
|
for filename in filenames:
|
||||||
|
if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'):
|
||||||
|
saferRemove(os.path.join(root, filename))
|
||||||
|
|
||||||
|
def extractCB7(self, targetdir):
|
||||||
|
# Workaround for some wide UTF-8 + Popen abnormalities
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
copy(self.origFileName, os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP'))
|
||||||
|
self.origFileName = os.path.join(os.path.dirname(self.origFileName), 'TMP_KCC_TMP')
|
||||||
|
output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' +
|
||||||
|
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||||
|
extracted = False
|
||||||
|
for line in output.stdout:
|
||||||
|
if b"Everything is Ok" in line:
|
||||||
|
extracted = True
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
saferRemove(self.origFileName)
|
||||||
|
if not extracted:
|
||||||
|
raise OSError
|
||||||
|
|
||||||
|
def extract(self, targetdir):
|
||||||
|
if self.compressor == 'rar':
|
||||||
|
self.extractCBR(targetdir)
|
||||||
|
elif self.compressor == 'zip':
|
||||||
|
self.extractCBZ(targetdir)
|
||||||
|
elif self.compressor == '7z':
|
||||||
|
self.extractCB7(targetdir)
|
||||||
|
adir = os.listdir(targetdir)
|
||||||
|
if 'ComicInfo.xml' in adir:
|
||||||
|
adir.remove('ComicInfo.xml')
|
||||||
|
if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])):
|
||||||
|
for f in os.listdir(os.path.join(targetdir, adir[0])):
|
||||||
|
# If directory names contain UTF-8 chars shutil.move can't clean up the mess alone
|
||||||
|
if os.path.isdir(os.path.join(targetdir, f)):
|
||||||
|
saferReplace(os.path.join(targetdir, adir[0], f), os.path.join(targetdir, adir[0], f + '-A'))
|
||||||
|
f += '-A'
|
||||||
|
move(os.path.join(targetdir, adir[0], f), targetdir)
|
||||||
|
os.rmdir(os.path.join(targetdir, adir[0]))
|
||||||
|
return targetdir
|
||||||
1185
kindlecomicconverter/comic2ebook.py
Executable file
303
kindlecomicconverter/comic2panel.py
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2017 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, ImageStat, ImageOps
|
||||||
|
from .shared import getImageFileName, walkLevel, walkSort, saferRemove, sanitizeTrace
|
||||||
|
try:
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
except ImportError:
|
||||||
|
QtCore = None
|
||||||
|
try:
|
||||||
|
from scandir import walk
|
||||||
|
except ImportError:
|
||||||
|
walk = os.walk
|
||||||
|
|
||||||
|
|
||||||
|
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, dirs, 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)
|
||||||
|
img = img.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]
|
||||||
|
saferRemove(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 sanitizePanelSize(panel, opt):
|
||||||
|
newPanels = []
|
||||||
|
if panel[2] > 6 * opt.height:
|
||||||
|
diff = int(panel[2] / 8)
|
||||||
|
newPanels.append([panel[0], panel[1] - diff * 7, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 7, panel[1] - diff * 6, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 6, panel[1] - diff * 5, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 5, panel[1] - diff * 4, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 4, panel[1] - diff * 3, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
|
||||||
|
newPanels.append([panel[1] - diff, panel[1], diff])
|
||||||
|
elif panel[2] > 3 * opt.height:
|
||||||
|
diff = int(panel[2] / 4)
|
||||||
|
newPanels.append([panel[0], panel[1] - diff * 3, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
|
||||||
|
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
|
||||||
|
newPanels.append([panel[1] - diff, panel[1], diff])
|
||||||
|
elif panel[2] > 1.5 * opt.height:
|
||||||
|
newPanels.append([panel[0], panel[1] - int(panel[2] / 2), int(panel[2] / 2)])
|
||||||
|
newPanels.append([panel[1] - int(panel[2] / 2), panel[1], int(panel[2] / 2)])
|
||||||
|
else:
|
||||||
|
newPanels = [panel]
|
||||||
|
return newPanels
|
||||||
|
|
||||||
|
|
||||||
|
def splitImageTick(output):
|
||||||
|
if output:
|
||||||
|
splitWorkerOutput.append(output)
|
||||||
|
splitWorkerPool.terminate()
|
||||||
|
if GUI:
|
||||||
|
GUI.progressBarTick.emit('tick')
|
||||||
|
if not GUI.conversionAlive:
|
||||||
|
splitWorkerPool.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def splitImage(work):
|
||||||
|
try:
|
||||||
|
path = work[0]
|
||||||
|
name = work[1]
|
||||||
|
opt = work[2]
|
||||||
|
# Hardcoded options
|
||||||
|
threshold = 1.0
|
||||||
|
delta = 15
|
||||||
|
fileExpanded = os.path.splitext(name)
|
||||||
|
filePath = os.path.join(path, name)
|
||||||
|
image = Image.open(filePath)
|
||||||
|
image = image.convert('RGB')
|
||||||
|
widthImg, heightImg = image.size
|
||||||
|
if heightImg > opt.height:
|
||||||
|
if opt.debug:
|
||||||
|
from PIL import ImageDraw
|
||||||
|
debugImage = Image.open(filePath)
|
||||||
|
draw = ImageDraw.Draw(debugImage)
|
||||||
|
|
||||||
|
# Find panels
|
||||||
|
y1 = 0
|
||||||
|
y2 = 15
|
||||||
|
panels = []
|
||||||
|
while y2 < heightImg:
|
||||||
|
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] < threshold and y2 < heightImg:
|
||||||
|
y2 += delta
|
||||||
|
y2 -= delta
|
||||||
|
y1Temp = y2
|
||||||
|
y1 = y2 + delta
|
||||||
|
y2 = y1 + delta
|
||||||
|
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] >= threshold and y2 < heightImg:
|
||||||
|
y1 += delta
|
||||||
|
y2 += delta
|
||||||
|
if y1 + delta >= heightImg:
|
||||||
|
y1 = heightImg - 1
|
||||||
|
y2Temp = y1
|
||||||
|
if opt.debug:
|
||||||
|
draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0))
|
||||||
|
draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0))
|
||||||
|
panelHeight = y2Temp - y1Temp
|
||||||
|
if panelHeight > delta:
|
||||||
|
# Panels that can't be cut nicely will be forcefully splitted
|
||||||
|
panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], opt)
|
||||||
|
for panel in panelsCleaned:
|
||||||
|
panels.append(panel)
|
||||||
|
if opt.debug:
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG')
|
||||||
|
|
||||||
|
# Create virtual pages
|
||||||
|
pages = []
|
||||||
|
currentPage = []
|
||||||
|
pageLeft = opt.height
|
||||||
|
panelNumber = 0
|
||||||
|
for panel in panels:
|
||||||
|
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 += panels[panel][2]
|
||||||
|
if pageHeight > delta:
|
||||||
|
newPage = Image.new('RGB', (widthImg, pageHeight))
|
||||||
|
for panel in page:
|
||||||
|
panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]])
|
||||||
|
newPage.paste(panelImg, (0, targetHeight))
|
||||||
|
targetHeight += panels[panel][2]
|
||||||
|
newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG')
|
||||||
|
pageNumber += 1
|
||||||
|
saferRemove(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 splitted 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()
|
||||||
|
if options.merge:
|
||||||
|
print("Merging images...")
|
||||||
|
directoryNumer = 1
|
||||||
|
mergeWork = []
|
||||||
|
mergeWorkerOutput = []
|
||||||
|
mergeWorkerPool = Pool()
|
||||||
|
mergeWork.append([options.targetDir])
|
||||||
|
for root, dirs, files in 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, dirs, files in walk(options.targetDir, False):
|
||||||
|
for name in files:
|
||||||
|
if getImageFileName(name) is not None:
|
||||||
|
pagenumber += 1
|
||||||
|
work.append([root, name, options])
|
||||||
|
else:
|
||||||
|
saferRemove(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.")
|
||||||
185
kindlecomicconverter/dualmetafix.py
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks
|
||||||
|
# Changes for KCC Copyright (C) 2014-2017 Pawel Jastrzebski <pawelj@iosphe.re>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import mmap
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
class DualMetaFixException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# palm database offset constants
|
||||||
|
number_of_pdb_records = 76
|
||||||
|
first_pdb_record = 78
|
||||||
|
|
||||||
|
# important rec0 offsets
|
||||||
|
mobi_header_base = 16
|
||||||
|
mobi_header_length = 20
|
||||||
|
mobi_version = 36
|
||||||
|
title_offset = 84
|
||||||
|
|
||||||
|
|
||||||
|
def getint(data, ofs, sz='L'):
|
||||||
|
i, = struct.unpack_from('>' + sz, data, ofs)
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def writeint(data, ofs, n, slen='L'):
|
||||||
|
if slen == 'L':
|
||||||
|
return data[:ofs] + struct.pack('>L', n) + data[ofs + 4:]
|
||||||
|
else:
|
||||||
|
return data[:ofs] + struct.pack('>H', n) + data[ofs + 2:]
|
||||||
|
|
||||||
|
|
||||||
|
def getsecaddr(datain, secno):
|
||||||
|
nsec = getint(datain, number_of_pdb_records, 'H')
|
||||||
|
if (secno < 0) | (secno >= nsec):
|
||||||
|
emsg = 'requested section number %d out of range (nsec=%d)' % (secno, nsec)
|
||||||
|
raise DualMetaFixException(emsg)
|
||||||
|
secstart = getint(datain, first_pdb_record + secno * 8)
|
||||||
|
if secno == nsec - 1:
|
||||||
|
secend = len(datain)
|
||||||
|
else:
|
||||||
|
secend = getint(datain, first_pdb_record + (secno + 1) * 8)
|
||||||
|
return secstart, secend
|
||||||
|
|
||||||
|
|
||||||
|
def readsection(datain, secno):
|
||||||
|
secstart, secend = getsecaddr(datain, secno)
|
||||||
|
return datain[secstart:secend]
|
||||||
|
|
||||||
|
|
||||||
|
# overwrite section - must be exact same length as original
|
||||||
|
def replacesection(datain, secno, secdata):
|
||||||
|
secstart, secend = getsecaddr(datain, secno)
|
||||||
|
seclen = secend - secstart
|
||||||
|
if len(secdata) != seclen:
|
||||||
|
raise DualMetaFixException('section length change in replacesection')
|
||||||
|
datain[secstart:secstart + seclen] = secdata
|
||||||
|
|
||||||
|
|
||||||
|
def get_exth_params(rec0):
|
||||||
|
ebase = mobi_header_base + getint(rec0, mobi_header_length)
|
||||||
|
if rec0[ebase:ebase + 4] != b'EXTH':
|
||||||
|
raise DualMetaFixException('EXTH tag not found where expected')
|
||||||
|
elen = getint(rec0, ebase + 4)
|
||||||
|
enum = getint(rec0, ebase + 8)
|
||||||
|
rlen = len(rec0)
|
||||||
|
return ebase, elen, enum, rlen
|
||||||
|
|
||||||
|
|
||||||
|
def add_exth(rec0, exth_num, exth_bytes):
|
||||||
|
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||||
|
newrecsize = 8 + len(exth_bytes)
|
||||||
|
newrec0 = rec0[0:ebase + 4] + struct.pack('>L', elen + newrecsize) + struct.pack('>L', enum + 1) + \
|
||||||
|
struct.pack('>L', exth_num) + struct.pack('>L', newrecsize) + exth_bytes + rec0[ebase + 12:]
|
||||||
|
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset) + newrecsize)
|
||||||
|
# keep constant record length by removing newrecsize null bytes from end
|
||||||
|
sectail = newrec0[-newrecsize:]
|
||||||
|
if sectail != b'\0' * newrecsize:
|
||||||
|
raise DualMetaFixException('add_exth: trimmed non-null bytes at end of section')
|
||||||
|
newrec0 = newrec0[0:rlen]
|
||||||
|
return newrec0
|
||||||
|
|
||||||
|
|
||||||
|
def read_exth(rec0, exth_num):
|
||||||
|
exth_values = []
|
||||||
|
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||||
|
ebase += 12
|
||||||
|
while enum > 0:
|
||||||
|
exth_id = getint(rec0, ebase)
|
||||||
|
if exth_id == exth_num:
|
||||||
|
# We might have multiple exths, so build a list.
|
||||||
|
exth_values.append(rec0[ebase + 8:ebase + getint(rec0, ebase + 4)])
|
||||||
|
enum -= 1
|
||||||
|
ebase = ebase + getint(rec0, ebase + 4)
|
||||||
|
return exth_values
|
||||||
|
|
||||||
|
|
||||||
|
def del_exth(rec0, exth_num):
|
||||||
|
ebase, elen, enum, rlen = get_exth_params(rec0)
|
||||||
|
ebase_idx = ebase + 12
|
||||||
|
enum_idx = 0
|
||||||
|
while enum_idx < enum:
|
||||||
|
exth_id = getint(rec0, ebase_idx)
|
||||||
|
exth_size = getint(rec0, ebase_idx + 4)
|
||||||
|
if exth_id == exth_num:
|
||||||
|
newrec0 = rec0
|
||||||
|
newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset) - exth_size)
|
||||||
|
newrec0 = newrec0[:ebase_idx] + newrec0[ebase_idx + exth_size:]
|
||||||
|
newrec0 = newrec0[0:ebase + 4] + struct.pack('>L', elen - exth_size) + \
|
||||||
|
struct.pack('>L', enum - 1) + newrec0[ebase + 12:]
|
||||||
|
newrec0 += b'\0' * exth_size
|
||||||
|
if rlen != len(newrec0):
|
||||||
|
raise DualMetaFixException('del_exth: incorrect section size change')
|
||||||
|
return newrec0
|
||||||
|
enum_idx += 1
|
||||||
|
ebase_idx = ebase_idx + exth_size
|
||||||
|
return rec0
|
||||||
|
|
||||||
|
|
||||||
|
class DualMobiMetaFix:
|
||||||
|
def __init__(self, infile, outfile, asin):
|
||||||
|
shutil.copyfile(infile, outfile)
|
||||||
|
f = open(outfile, "r+b")
|
||||||
|
self.datain = mmap.mmap(f.fileno(), 0)
|
||||||
|
self.datain_rec0 = readsection(self.datain, 0)
|
||||||
|
|
||||||
|
# in the first mobi header
|
||||||
|
# add 501 to "EBOK", add 113 as asin, add 504 as asin
|
||||||
|
rec0 = self.datain_rec0
|
||||||
|
rec0 = del_exth(rec0, 501)
|
||||||
|
rec0 = del_exth(rec0, 113)
|
||||||
|
rec0 = del_exth(rec0, 504)
|
||||||
|
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||||
|
rec0 = add_exth(rec0, 113, asin)
|
||||||
|
rec0 = add_exth(rec0, 504, asin)
|
||||||
|
replacesection(self.datain, 0, rec0)
|
||||||
|
|
||||||
|
ver = getint(self.datain_rec0, mobi_version)
|
||||||
|
self.combo = (ver != 8)
|
||||||
|
if not self.combo:
|
||||||
|
return
|
||||||
|
|
||||||
|
exth121 = read_exth(self.datain_rec0, 121)
|
||||||
|
if len(exth121) == 0:
|
||||||
|
self.combo = False
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# only pay attention to first exth121
|
||||||
|
# (there should only be one)
|
||||||
|
datain_kf8, = struct.unpack_from('>L', exth121[0], 0)
|
||||||
|
if datain_kf8 == 0xffffffff:
|
||||||
|
self.combo = False
|
||||||
|
return
|
||||||
|
self.datain_kfrec0 = readsection(self.datain, datain_kf8)
|
||||||
|
|
||||||
|
# in the second header
|
||||||
|
# add 501 to "EBOK", add 113 as asin, add 504 as asin
|
||||||
|
rec0 = self.datain_kfrec0
|
||||||
|
rec0 = del_exth(rec0, 501)
|
||||||
|
rec0 = del_exth(rec0, 113)
|
||||||
|
rec0 = del_exth(rec0, 504)
|
||||||
|
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||||
|
rec0 = add_exth(rec0, 113, asin)
|
||||||
|
rec0 = add_exth(rec0, 504, asin)
|
||||||
|
replacesection(self.datain, datain_kf8, rec0)
|
||||||
|
|
||||||
|
self.datain.flush()
|
||||||
|
self.datain.close()
|
||||||
374
kindlecomicconverter/image.py
Executable file
@@ -0,0 +1,374 @@
|
|||||||
|
# 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-2017 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 io import BytesIO
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
from urllib.parse import quote
|
||||||
|
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
||||||
|
from .shared import md5Checksum
|
||||||
|
from . import __version__
|
||||||
|
|
||||||
|
|
||||||
|
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),
|
||||||
|
'K3': ("Kindle", (600, 800), Palette16, 1.8),
|
||||||
|
'K45': ("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/Voyage/Oasis", (1072, 1448), 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),
|
||||||
|
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ComicPageParser:
|
||||||
|
def __init__(self, source, options):
|
||||||
|
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:
|
||||||
|
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, mode, path, image, color, fill, options):
|
||||||
|
self.opt = options
|
||||||
|
_, self.size, self.palette, self.gamma = self.opt.profileData
|
||||||
|
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('BlackFill')
|
||||||
|
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=80)
|
||||||
|
return [md5Checksum(self.targetPath), flags, self.orgPath]
|
||||||
|
except IOError:
|
||||||
|
raise RuntimeError('Cannot save image.')
|
||||||
|
|
||||||
|
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:
|
||||||
|
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':
|
||||||
|
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':
|
||||||
|
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, tmpImg):
|
||||||
|
min_margin = [int(0.005 * i + 0.5) for i in tmpImg.size]
|
||||||
|
max_margin = [int(0.1 * i + 0.5) for i in tmpImg.size]
|
||||||
|
bbox = tmpImg.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(tmpImg.size[0],
|
||||||
|
max(tmpImg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
|
||||||
|
min(tmpImg.size[1],
|
||||||
|
max(tmpImg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
|
||||||
|
)
|
||||||
|
return bbox
|
||||||
|
|
||||||
|
def cropPageNumber(self, power):
|
||||||
|
if self.fill != 'white':
|
||||||
|
tmpImg = self.image.convert(mode='L')
|
||||||
|
else:
|
||||||
|
tmpImg = ImageOps.invert(self.image.convert(mode='L'))
|
||||||
|
tmpImg = tmpImg.point(lambda x: x and 255)
|
||||||
|
tmpImg = tmpImg.filter(ImageFilter.MinFilter(size=3))
|
||||||
|
tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=5))
|
||||||
|
tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
|
||||||
|
self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image
|
||||||
|
|
||||||
|
def cropMargin(self, power):
|
||||||
|
if self.fill != 'white':
|
||||||
|
tmpImg = self.image.convert(mode='L')
|
||||||
|
else:
|
||||||
|
tmpImg = ImageOps.invert(self.image.convert(mode='L'))
|
||||||
|
tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=3))
|
||||||
|
tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
|
||||||
|
self.image = self.image.crop(self.getBoundingBox(tmpImg)) if tmpImg.getbbox() else self.image
|
||||||
|
|
||||||
|
|
||||||
|
class Cover:
|
||||||
|
def __init__(self, source, target, opt, tomeNumber):
|
||||||
|
self.options = opt
|
||||||
|
self.source = source
|
||||||
|
self.target = target
|
||||||
|
if tomeNumber == 0:
|
||||||
|
self.tomeNumber = 1
|
||||||
|
else:
|
||||||
|
self.tomeNumber = tomeNumber
|
||||||
|
if self.tomeNumber in self.options.remoteCovers:
|
||||||
|
try:
|
||||||
|
source = urlopen(Request(quote(self.options.remoteCovers[self.tomeNumber]).replace('%3A', ':', 1),
|
||||||
|
headers={'User-Agent': 'KindleComicConverter/' + __version__})).read()
|
||||||
|
self.image = Image.open(BytesIO(source))
|
||||||
|
except Exception:
|
||||||
|
self.image = Image.open(source)
|
||||||
|
else:
|
||||||
|
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=80)
|
||||||
|
except IOError:
|
||||||
|
raise RuntimeError('Failed to process downloaded 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')
|
||||||
|
except IOError:
|
||||||
|
raise RuntimeError('Failed to upload cover.')
|
||||||
44
kindlecomicconverter/kindle.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013-2017 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
|
||||||
173
kindlecomicconverter/metadata.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013-2017 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 re import compile
|
||||||
|
from zipfile import is_zipfile, ZipFile, ZIP_DEFLATED
|
||||||
|
from subprocess import STDOUT, PIPE
|
||||||
|
from psutil import Popen
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from shutil import rmtree
|
||||||
|
from .shared import removeFromZIP, check7ZFile as is_7zfile
|
||||||
|
from . import rarfile
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataParser:
|
||||||
|
def __init__(self, source):
|
||||||
|
self.source = source
|
||||||
|
self.data = {'Series': '',
|
||||||
|
'Volume': '',
|
||||||
|
'Number': '',
|
||||||
|
'Writers': [],
|
||||||
|
'Pencillers': [],
|
||||||
|
'Inkers': [],
|
||||||
|
'Colorists': [],
|
||||||
|
'Summary': '',
|
||||||
|
'MUid': '',
|
||||||
|
'Bookmarks': []}
|
||||||
|
self.rawdata = None
|
||||||
|
self.compressor = None
|
||||||
|
if self.source.endswith('.xml'):
|
||||||
|
self.rawdata = parse(self.source)
|
||||||
|
self.parseXML()
|
||||||
|
else:
|
||||||
|
if is_zipfile(self.source):
|
||||||
|
self.compressor = 'zip'
|
||||||
|
with ZipFile(self.source) as zip_file:
|
||||||
|
for member in zip_file.namelist():
|
||||||
|
if member != 'ComicInfo.xml':
|
||||||
|
continue
|
||||||
|
with zip_file.open(member) as xml_file:
|
||||||
|
self.rawdata = parse(xml_file)
|
||||||
|
elif rarfile.is_rarfile(self.source):
|
||||||
|
self.compressor = 'rar'
|
||||||
|
with rarfile.RarFile(self.source) as rar_file:
|
||||||
|
for member in rar_file.namelist():
|
||||||
|
if member != 'ComicInfo.xml':
|
||||||
|
continue
|
||||||
|
with rar_file.open(member) as xml_file:
|
||||||
|
self.rawdata = parse(xml_file)
|
||||||
|
elif is_7zfile(self.source):
|
||||||
|
self.compressor = '7z'
|
||||||
|
workdir = mkdtemp('', 'KCC-')
|
||||||
|
tmpXML = os.path.join(workdir, 'ComicInfo.xml')
|
||||||
|
output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"',
|
||||||
|
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||||
|
extracted = False
|
||||||
|
for line in output.stdout:
|
||||||
|
if b"Everything is Ok" in line or b"No files to process" in line:
|
||||||
|
extracted = True
|
||||||
|
if not extracted:
|
||||||
|
rmtree(workdir)
|
||||||
|
raise OSError('Failed to extract 7ZIP file.')
|
||||||
|
if os.path.isfile(tmpXML):
|
||||||
|
self.rawdata = parse(tmpXML)
|
||||||
|
rmtree(workdir)
|
||||||
|
else:
|
||||||
|
raise OSError('Failed to detect archive format.')
|
||||||
|
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('ScanInformation')) != 0:
|
||||||
|
coverId = compile('(MCD\\()(\\d+)(\\))')\
|
||||||
|
.search(self.rawdata.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue)
|
||||||
|
if coverId:
|
||||||
|
self.data['MUid'] = coverId.group(2)
|
||||||
|
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']],
|
||||||
|
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
|
||||||
|
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']],
|
||||||
|
['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']):
|
||||||
|
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')
|
||||||
|
if is_zipfile(self.source):
|
||||||
|
removeFromZIP(self.source, 'ComicInfo.xml')
|
||||||
|
with ZipFile(self.source, mode='a', compression=ZIP_DEFLATED) as zip_file:
|
||||||
|
zip_file.write(tmpXML, arcname=tmpXML.split(os.sep)[-1])
|
||||||
|
elif rarfile.is_rarfile(self.source):
|
||||||
|
raise NotImplementedError
|
||||||
|
elif is_7zfile(self.source):
|
||||||
|
output = Popen('7za a "' + self.source + '" "' + tmpXML + '"',
|
||||||
|
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||||
|
extracted = False
|
||||||
|
for line in output.stdout:
|
||||||
|
if b"Everything is Ok" in line:
|
||||||
|
extracted = True
|
||||||
|
if not extracted:
|
||||||
|
rmtree(workdir)
|
||||||
|
raise OSError('Failed to modify 7ZIP file.')
|
||||||
|
rmtree(workdir)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2017 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 +19,50 @@
|
|||||||
# 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, origFileName):
|
||||||
self.origFileName = origFileName
|
self.origFileName = origFileName
|
||||||
self.filename = os.path.splitext(origFileName)
|
self.filename = os.path.splitext(origFileName)
|
||||||
self.path = self.filename[0]
|
# noinspection PyUnusedLocal
|
||||||
|
self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for x 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.origFileName, "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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# rarfile.py
|
# rarfile.py
|
||||||
#
|
#
|
||||||
# Copyright (c) 2005-2013 Marko Kreen <markokr@gmail.com>
|
# Copyright (c) 2005-2014 Marko Kreen <markokr@gmail.com>
|
||||||
#
|
#
|
||||||
# Permission to use, copy, modify, and/or distribute this software for any
|
# Permission to use, copy, modify, and/or distribute this software for any
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
# purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -74,7 +74,7 @@ For more details, refer to source.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '2.6'
|
__version__ = '2.7-kcc'
|
||||||
|
|
||||||
# export only interesting items
|
# export only interesting items
|
||||||
__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
|
__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
|
||||||
@@ -108,6 +108,8 @@ if sys.hexversion < 0x3000000:
|
|||||||
# py2.6 has broken bytes()
|
# py2.6 has broken bytes()
|
||||||
def bytes(s, enc):
|
def bytes(s, enc):
|
||||||
return str(s)
|
return str(s)
|
||||||
|
else:
|
||||||
|
unicode = str
|
||||||
|
|
||||||
# see if compat bytearray() is needed
|
# see if compat bytearray() is needed
|
||||||
try:
|
try:
|
||||||
@@ -176,8 +178,25 @@ EXTRACT_ARGS = ('x', '-y', '-idq')
|
|||||||
#: args for testrar()
|
#: args for testrar()
|
||||||
TEST_ARGS = ('t', '-idq')
|
TEST_ARGS = ('t', '-idq')
|
||||||
|
|
||||||
|
#
|
||||||
|
# Allow use of tool that is not compatible with unrar.
|
||||||
|
#
|
||||||
|
# By default use 'bsdtar' which is 'tar' program that
|
||||||
|
# sits on top of libarchive.
|
||||||
|
#
|
||||||
|
# Problems with libarchive RAR backend:
|
||||||
|
# - Does not support solid archives.
|
||||||
|
# - Does not support password-protected archives.
|
||||||
|
#
|
||||||
|
|
||||||
|
ALT_TOOL = 'bsdtar'
|
||||||
|
ALT_OPEN_ARGS = ('-x', '--to-stdout', '-f')
|
||||||
|
ALT_EXTRACT_ARGS = ('-x', '-f')
|
||||||
|
ALT_TEST_ARGS = ('-t', '-f')
|
||||||
|
ALT_CHECK_ARGS = ('--help',)
|
||||||
|
|
||||||
#: whether to speed up decompression by using tmp archive
|
#: whether to speed up decompression by using tmp archive
|
||||||
USE_EXTRACT_HACK = 1
|
USE_EXTRACT_HACK = 0
|
||||||
|
|
||||||
#: limit the filesize for tmp archive usage
|
#: limit the filesize for tmp archive usage
|
||||||
HACK_SIZE_LIMIT = 20*1024*1024
|
HACK_SIZE_LIMIT = 20*1024*1024
|
||||||
@@ -188,10 +207,6 @@ NEED_COMMENTS = 1
|
|||||||
#: whether to convert comments to unicode strings
|
#: whether to convert comments to unicode strings
|
||||||
UNICODE_COMMENTS = 0
|
UNICODE_COMMENTS = 0
|
||||||
|
|
||||||
#: When RAR is corrupt, stopping on bad header is better
|
|
||||||
#: On unknown/misparsed RAR headers reporting is better
|
|
||||||
REPORT_BAD_HEADER = 0
|
|
||||||
|
|
||||||
#: Convert RAR time tuple into datetime() object
|
#: Convert RAR time tuple into datetime() object
|
||||||
USE_DATETIME = 0
|
USE_DATETIME = 0
|
||||||
|
|
||||||
@@ -280,6 +295,7 @@ RAR_M5 = 0x35
|
|||||||
##
|
##
|
||||||
|
|
||||||
RAR_ID = bytes("Rar!\x1a\x07\x00", 'ascii')
|
RAR_ID = bytes("Rar!\x1a\x07\x00", 'ascii')
|
||||||
|
RAR5_ID = bytes("Rar!\x1a\x07\x01", 'ascii')
|
||||||
ZERO = bytes("\0", 'ascii')
|
ZERO = bytes("\0", 'ascii')
|
||||||
EMPTY = bytes("", 'ascii')
|
EMPTY = bytes("", 'ascii')
|
||||||
|
|
||||||
@@ -338,12 +354,18 @@ class RarUnknownError(RarExecError):
|
|||||||
"""Unknown exit code"""
|
"""Unknown exit code"""
|
||||||
class RarSignalExit(RarExecError):
|
class RarSignalExit(RarExecError):
|
||||||
"""Unrar exited with signal"""
|
"""Unrar exited with signal"""
|
||||||
|
class RarCannotExec(RarExecError):
|
||||||
|
"""Executable not found."""
|
||||||
|
|
||||||
|
|
||||||
def is_rarfile(fn):
|
def is_rarfile(xfile):
|
||||||
'''Check quickly whether file is rar archive.'''
|
'''Check quickly whether file is rar archive.'''
|
||||||
buf = open(fn, "rb").read(len(RAR_ID))
|
with open(xfile, 'rb') as fh:
|
||||||
return buf == RAR_ID
|
buf = fh.read(len(RAR_ID))
|
||||||
|
if buf == RAR_ID or buf == RAR5_ID:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class RarInfo(object):
|
class RarInfo(object):
|
||||||
@@ -453,11 +475,12 @@ class RarFile(object):
|
|||||||
'''Parse RAR structure, provide access to files in archive.
|
'''Parse RAR structure, provide access to files in archive.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
#: Archive comment. Byte string or None. Use UNICODE_COMMENTS
|
#: Archive comment. Byte string or None. Use :data:`UNICODE_COMMENTS`
|
||||||
#: to get automatic decoding to unicode.
|
#: to get automatic decoding to unicode.
|
||||||
comment = None
|
comment = None
|
||||||
|
|
||||||
def __init__(self, rarfile, mode="r", charset=None, info_callback=None, crc_check = True):
|
def __init__(self, rarfile, mode="r", charset=None, info_callback=None,
|
||||||
|
crc_check = True, errors = "stop"):
|
||||||
"""Open and parse a RAR archive.
|
"""Open and parse a RAR archive.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
@@ -472,6 +495,9 @@ class RarFile(object):
|
|||||||
debug callback, gets to see all archive entries.
|
debug callback, gets to see all archive entries.
|
||||||
crc_check
|
crc_check
|
||||||
set to False to disable CRC checks
|
set to False to disable CRC checks
|
||||||
|
errors
|
||||||
|
Either "stop" to quietly stop parsing on errors,
|
||||||
|
or "strict" to raise errors. Default is "stop".
|
||||||
"""
|
"""
|
||||||
self.rarfile = rarfile
|
self.rarfile = rarfile
|
||||||
self.comment = None
|
self.comment = None
|
||||||
@@ -485,6 +511,13 @@ class RarFile(object):
|
|||||||
self._crc_check = crc_check
|
self._crc_check = crc_check
|
||||||
self._vol_list = []
|
self._vol_list = []
|
||||||
|
|
||||||
|
if errors == "stop":
|
||||||
|
self._strict = False
|
||||||
|
elif errors == "strict":
|
||||||
|
self._strict = True
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid value for 'errors' parameter.")
|
||||||
|
|
||||||
self._main = None
|
self._main = None
|
||||||
|
|
||||||
if mode != "r":
|
if mode != "r":
|
||||||
@@ -548,8 +581,9 @@ class RarFile(object):
|
|||||||
'''Returns file-like object (:class:`RarExtFile`),
|
'''Returns file-like object (:class:`RarExtFile`),
|
||||||
from where the data can be read.
|
from where the data can be read.
|
||||||
|
|
||||||
The object implements io.RawIOBase interface, so it can
|
The object implements :class:`io.RawIOBase` interface, so it can
|
||||||
be further wrapped with io.BufferedReader and io.TextIOWrapper.
|
be further wrapped with :class:`io.BufferedReader`
|
||||||
|
and :class:`io.TextIOWrapper`.
|
||||||
|
|
||||||
On older Python where io module is not available, it implements
|
On older Python where io module is not available, it implements
|
||||||
only .read(), .seek(), .tell() and .close() methods.
|
only .read(), .seek(), .tell() and .close() methods.
|
||||||
@@ -588,16 +622,19 @@ class RarFile(object):
|
|||||||
psw = None
|
psw = None
|
||||||
|
|
||||||
# is temp write usable?
|
# is temp write usable?
|
||||||
if not USE_EXTRACT_HACK or not self._main:
|
use_hack = 1
|
||||||
|
if not self._main:
|
||||||
use_hack = 0
|
use_hack = 0
|
||||||
elif self._main.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD):
|
elif self._main.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD):
|
||||||
use_hack = 0
|
use_hack = 0
|
||||||
elif inf.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
|
elif inf.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
|
||||||
use_hack = 0
|
use_hack = 0
|
||||||
|
elif is_filelike(self.rarfile):
|
||||||
|
pass
|
||||||
elif inf.file_size > HACK_SIZE_LIMIT:
|
elif inf.file_size > HACK_SIZE_LIMIT:
|
||||||
use_hack = 0
|
use_hack = 0
|
||||||
else:
|
elif not USE_EXTRACT_HACK:
|
||||||
use_hack = 1
|
use_hack = 0
|
||||||
|
|
||||||
# now extract
|
# now extract
|
||||||
if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0:
|
if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0:
|
||||||
@@ -610,7 +647,7 @@ class RarFile(object):
|
|||||||
def read(self, fname, psw = None):
|
def read(self, fname, psw = None):
|
||||||
"""Return uncompressed data for archive entry.
|
"""Return uncompressed data for archive entry.
|
||||||
|
|
||||||
For longer files using .open() may be better idea.
|
For longer files using :meth:`RarFile.open` may be better idea.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
@@ -641,7 +678,7 @@ class RarFile(object):
|
|||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
member
|
member
|
||||||
filename or RarInfo instance
|
filename or :class:`RarInfo` instance
|
||||||
path
|
path
|
||||||
optional destination path
|
optional destination path
|
||||||
pwd
|
pwd
|
||||||
@@ -661,7 +698,7 @@ class RarFile(object):
|
|||||||
path
|
path
|
||||||
optional destination path
|
optional destination path
|
||||||
members
|
members
|
||||||
optional filename or RarInfo instance list to extract
|
optional filename or :class:`RarInfo` instance list to extract
|
||||||
pwd
|
pwd
|
||||||
optional password to use
|
optional password to use
|
||||||
"""
|
"""
|
||||||
@@ -678,19 +715,29 @@ class RarFile(object):
|
|||||||
"""Let 'unrar' test the archive.
|
"""Let 'unrar' test the archive.
|
||||||
"""
|
"""
|
||||||
cmd = [UNRAR_TOOL] + list(TEST_ARGS)
|
cmd = [UNRAR_TOOL] + list(TEST_ARGS)
|
||||||
if self._password is not None:
|
add_password_arg(cmd, self._password)
|
||||||
cmd.append('-p' + self._password)
|
|
||||||
else:
|
|
||||||
cmd.append('-p-')
|
|
||||||
cmd.append(self.rarfile)
|
cmd.append(self.rarfile)
|
||||||
p = custom_popen(cmd)
|
p = custom_popen(cmd)
|
||||||
output = p.communicate()[0]
|
output = p.communicate()[0]
|
||||||
check_returncode(p, output)
|
check_returncode(p, output)
|
||||||
|
|
||||||
|
def strerror(self):
|
||||||
|
"""Return error string if parsing failed,
|
||||||
|
or None if no problems.
|
||||||
|
"""
|
||||||
|
return self._parse_error
|
||||||
|
|
||||||
##
|
##
|
||||||
## private methods
|
## private methods
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def _set_error(self, msg, *args):
|
||||||
|
if args:
|
||||||
|
msg = msg % args
|
||||||
|
self._parse_error = msg
|
||||||
|
if self._strict:
|
||||||
|
raise BadRarFile(msg)
|
||||||
|
|
||||||
# store entry
|
# store entry
|
||||||
def _process_entry(self, item):
|
def _process_entry(self, item):
|
||||||
if item.type == RAR_BLOCK_FILE:
|
if item.type == RAR_BLOCK_FILE:
|
||||||
@@ -738,10 +785,10 @@ class RarFile(object):
|
|||||||
self._fd = None
|
self._fd = None
|
||||||
|
|
||||||
def _parse_real(self):
|
def _parse_real(self):
|
||||||
fd = open(self.rarfile, "rb")
|
fd = XFile(self.rarfile)
|
||||||
self._fd = fd
|
self._fd = fd
|
||||||
id = fd.read(len(RAR_ID))
|
id = fd.read(len(RAR_ID))
|
||||||
if id != RAR_ID:
|
if id != RAR_ID and id != RAR5_ID:
|
||||||
raise NotRarFile("Not a Rar archive: "+self.rarfile)
|
raise NotRarFile("Not a Rar archive: "+self.rarfile)
|
||||||
|
|
||||||
volume = 0 # first vol (.rar) is 0
|
volume = 0 # first vol (.rar) is 0
|
||||||
@@ -757,9 +804,13 @@ class RarFile(object):
|
|||||||
if not h:
|
if not h:
|
||||||
if more_vols:
|
if more_vols:
|
||||||
volume += 1
|
volume += 1
|
||||||
volfile = self._next_volname(volfile)
|
|
||||||
fd.close()
|
fd.close()
|
||||||
fd = open(volfile, "rb")
|
try:
|
||||||
|
volfile = self._next_volname(volfile)
|
||||||
|
fd = XFile(volfile)
|
||||||
|
except IOError:
|
||||||
|
self._set_error("Cannot open next volume: %s", volfile)
|
||||||
|
break
|
||||||
self._fd = fd
|
self._fd = fd
|
||||||
more_vols = 0
|
more_vols = 0
|
||||||
endarc = 0
|
endarc = 0
|
||||||
@@ -824,8 +875,7 @@ class RarFile(object):
|
|||||||
# now read actual header
|
# now read actual header
|
||||||
return self._parse_block_header(fd)
|
return self._parse_block_header(fd)
|
||||||
except struct.error:
|
except struct.error:
|
||||||
if REPORT_BAD_HEADER:
|
self._set_error('Broken header in RAR file')
|
||||||
raise BadRarFile('Broken header in RAR file')
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# common header
|
# common header
|
||||||
@@ -852,8 +902,7 @@ class RarFile(object):
|
|||||||
|
|
||||||
# unexpected EOF?
|
# unexpected EOF?
|
||||||
if len(h.header_data) != h.header_size:
|
if len(h.header_data) != h.header_size:
|
||||||
if REPORT_BAD_HEADER:
|
self._set_error('Unexpected EOF when reading header')
|
||||||
raise BadRarFile('Unexpected EOF when reading header')
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# block has data assiciated with it?
|
# block has data assiciated with it?
|
||||||
@@ -896,18 +945,9 @@ class RarFile(object):
|
|||||||
if h.header_crc == calc_crc:
|
if h.header_crc == calc_crc:
|
||||||
return h
|
return h
|
||||||
|
|
||||||
# need to panic?
|
# header parsing failed.
|
||||||
if REPORT_BAD_HEADER:
|
self._set_error('Header CRC error (%02x): exp=%x got=%x (xlen = %d)',
|
||||||
xlen = len(crcdat)
|
h.type, h.header_crc, calc_crc, len(crcdat))
|
||||||
crcdat = h.header_data[2:]
|
|
||||||
msg = 'Header CRC error (%02x): exp=%x got=%x (xlen = %d)' % ( h.type, h.header_crc, calc_crc, xlen )
|
|
||||||
xlen = len(crcdat)
|
|
||||||
while xlen >= S_BLK_HDR.size - 2:
|
|
||||||
crc = crc32(crcdat[:xlen]) & 0xFFFF
|
|
||||||
if crc == h.header_crc:
|
|
||||||
msg += ' / crc match, xlen = %d' % xlen
|
|
||||||
xlen -= 1
|
|
||||||
raise BadRarFile(msg)
|
|
||||||
|
|
||||||
# instead panicing, send eof
|
# instead panicing, send eof
|
||||||
return None
|
return None
|
||||||
@@ -1053,6 +1093,8 @@ class RarFile(object):
|
|||||||
|
|
||||||
# given current vol name, construct next one
|
# given current vol name, construct next one
|
||||||
def _next_volname(self, volfile):
|
def _next_volname(self, volfile):
|
||||||
|
if is_filelike(volfile):
|
||||||
|
raise IOError("Working on single FD")
|
||||||
if self._main.flags & RAR_MAIN_NEWNUMBERING:
|
if self._main.flags & RAR_MAIN_NEWNUMBERING:
|
||||||
return self._next_newvol(volfile)
|
return self._next_newvol(volfile)
|
||||||
return self._next_oldvol(volfile)
|
return self._next_oldvol(volfile)
|
||||||
@@ -1093,7 +1135,7 @@ class RarFile(object):
|
|||||||
BSIZE = 32*1024
|
BSIZE = 32*1024
|
||||||
|
|
||||||
size = inf.compress_size + inf.header_size
|
size = inf.compress_size + inf.header_size
|
||||||
rf = open(inf.volume_file, "rb", 0)
|
rf = XFile(inf.volume_file, 0)
|
||||||
rf.seek(inf.header_offset)
|
rf.seek(inf.header_offset)
|
||||||
|
|
||||||
tmpfd, tmpname = mkstemp(suffix='.rar')
|
tmpfd, tmpname = mkstemp(suffix='.rar')
|
||||||
@@ -1125,7 +1167,7 @@ class RarFile(object):
|
|||||||
def _read_comment_v3(self, inf, psw=None):
|
def _read_comment_v3(self, inf, psw=None):
|
||||||
|
|
||||||
# read data
|
# read data
|
||||||
rf = open(inf.volume_file, "rb")
|
rf = XFile(inf.volume_file)
|
||||||
rf.seek(inf.file_offset)
|
rf.seek(inf.file_offset)
|
||||||
data = rf.read(inf.compress_size)
|
data = rf.read(inf.compress_size)
|
||||||
rf.close()
|
rf.close()
|
||||||
@@ -1146,9 +1188,10 @@ class RarFile(object):
|
|||||||
|
|
||||||
# extract using unrar
|
# extract using unrar
|
||||||
def _open_unrar(self, rarfile, inf, psw = None, tmpfile = None):
|
def _open_unrar(self, rarfile, inf, psw = None, tmpfile = None):
|
||||||
|
if is_filelike(rarfile):
|
||||||
|
raise ValueError("Cannot use unrar directly on memory buffer")
|
||||||
cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
|
cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
|
||||||
if psw is not None:
|
add_password_arg(cmd, psw)
|
||||||
cmd.append("-p" + psw)
|
|
||||||
cmd.append(rarfile)
|
cmd.append(rarfile)
|
||||||
|
|
||||||
# not giving filename avoids encoding related problems
|
# not giving filename avoids encoding related problems
|
||||||
@@ -1180,10 +1223,7 @@ class RarFile(object):
|
|||||||
|
|
||||||
# pasoword
|
# pasoword
|
||||||
psw = psw or self._password
|
psw = psw or self._password
|
||||||
if psw is not None:
|
add_password_arg(cmd, psw)
|
||||||
cmd.append('-p' + psw)
|
|
||||||
else:
|
|
||||||
cmd.append('-p-')
|
|
||||||
|
|
||||||
# rar file
|
# rar file
|
||||||
cmd.append(self.rarfile)
|
cmd.append(self.rarfile)
|
||||||
@@ -1553,7 +1593,7 @@ class DirectReader(RarExtFile):
|
|||||||
RarExtFile._open(self)
|
RarExtFile._open(self)
|
||||||
|
|
||||||
self.volfile = self.inf.volume_file
|
self.volfile = self.inf.volume_file
|
||||||
self.fd = open(self.volfile, "rb", 0)
|
self.fd = XFile(self.volfile, 0)
|
||||||
self.fd.seek(self.inf.header_offset, 0)
|
self.fd.seek(self.inf.header_offset, 0)
|
||||||
self.cur = self.rf._parse_header(self.fd)
|
self.cur = self.rf._parse_header(self.fd)
|
||||||
self.cur_avail = self.cur.add_size
|
self.cur_avail = self.cur.add_size
|
||||||
@@ -1705,10 +1745,47 @@ class HeaderDecrypt:
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
# handle (filename|filelike) object
|
||||||
|
class XFile(object):
|
||||||
|
__slots__ = ('_fd', '_need_close')
|
||||||
|
def __init__(self, xfile, bufsize = 1024):
|
||||||
|
if is_filelike(xfile):
|
||||||
|
self._need_close = False
|
||||||
|
self._fd = xfile
|
||||||
|
self._fd.seek(0)
|
||||||
|
else:
|
||||||
|
self._need_close = True
|
||||||
|
self._fd = open(xfile, 'rb', bufsize)
|
||||||
|
def read(self, n=None):
|
||||||
|
return self._fd.read(n)
|
||||||
|
def tell(self):
|
||||||
|
return self._fd.tell()
|
||||||
|
def seek(self, ofs, whence=0):
|
||||||
|
return self._fd.seek(ofs, whence)
|
||||||
|
def readinto(self, dst):
|
||||||
|
return self._fd.readinto(dst)
|
||||||
|
def close(self):
|
||||||
|
if self._need_close:
|
||||||
|
self._fd.close()
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
def __exit__(self, typ, val, tb):
|
||||||
|
self.close()
|
||||||
|
|
||||||
##
|
##
|
||||||
## Utility functions
|
## Utility functions
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def is_filelike(obj):
|
||||||
|
if isinstance(obj, str) or isinstance(obj, unicode):
|
||||||
|
return False
|
||||||
|
res = True
|
||||||
|
for a in ('read', 'tell', 'seek'):
|
||||||
|
res = res and hasattr(obj, a)
|
||||||
|
if not res:
|
||||||
|
raise ValueError("Invalid object passed as file")
|
||||||
|
return True
|
||||||
|
|
||||||
def rar3_s2k(psw, salt):
|
def rar3_s2k(psw, salt):
|
||||||
"""String-to-key hash for RAR3."""
|
"""String-to-key hash for RAR3."""
|
||||||
|
|
||||||
@@ -1768,10 +1845,7 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No
|
|||||||
tmpf.close()
|
tmpf.close()
|
||||||
|
|
||||||
cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
|
cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
|
||||||
if psw is not None and (flags & RAR_FILE_PASSWORD):
|
add_password_arg(cmd, psw, (flags & RAR_FILE_PASSWORD))
|
||||||
cmd.append("-p" + psw)
|
|
||||||
else:
|
|
||||||
cmd.append("-p-")
|
|
||||||
cmd.append(tmpname)
|
cmd.append(tmpname)
|
||||||
|
|
||||||
p = custom_popen(cmd)
|
p = custom_popen(cmd)
|
||||||
@@ -1840,22 +1914,43 @@ def custom_popen(cmd):
|
|||||||
except OSError:
|
except OSError:
|
||||||
ex = sys.exc_info()[1]
|
ex = sys.exc_info()[1]
|
||||||
if ex.errno == errno.ENOENT:
|
if ex.errno == errno.ENOENT:
|
||||||
raise RarExecError("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
|
raise RarCannotExec("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
|
||||||
raise
|
raise
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
def custom_check(cmd, ignore_retcode=False):
|
||||||
|
"""Run command, collect output, raise error if needed."""
|
||||||
|
p = custom_popen(cmd)
|
||||||
|
out, err = p.communicate()
|
||||||
|
if p.returncode and not ignore_retcode:
|
||||||
|
raise RarExecError("Check-run failed")
|
||||||
|
return out
|
||||||
|
|
||||||
|
def add_password_arg(cmd, psw, required=False):
|
||||||
|
"""Append password switch to commandline."""
|
||||||
|
if UNRAR_TOOL == ALT_TOOL:
|
||||||
|
return
|
||||||
|
if psw is not None:
|
||||||
|
cmd.append('-p' + psw)
|
||||||
|
else:
|
||||||
|
cmd.append('-p-')
|
||||||
|
|
||||||
def check_returncode(p, out):
|
def check_returncode(p, out):
|
||||||
"""Raise exception according to unrar exit code"""
|
"""Raise exception according to unrar exit code"""
|
||||||
|
|
||||||
code = p.returncode
|
code = p.returncode
|
||||||
if code == 0:
|
if code == 0:
|
||||||
return
|
return
|
||||||
|
if code == 9:
|
||||||
|
return
|
||||||
|
|
||||||
# map return code to exception class
|
# map return code to exception class
|
||||||
errmap = [None,
|
errmap = [None,
|
||||||
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
|
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
|
||||||
RarWriteError, RarOpenError, RarUserError, RarMemoryError,
|
RarWriteError, RarOpenError, RarUserError, RarMemoryError,
|
||||||
RarCreateError, RarNoFilesError] # codes from rar.txt
|
RarCreateError, RarNoFilesError] # codes from rar.txt
|
||||||
|
if UNRAR_TOOL == ALT_TOOL:
|
||||||
|
errmap = [None]
|
||||||
if code > 0 and code < len(errmap):
|
if code > 0 and code < len(errmap):
|
||||||
exc = errmap[code]
|
exc = errmap[code]
|
||||||
elif code == 255:
|
elif code == 255:
|
||||||
@@ -1873,3 +1968,23 @@ def check_returncode(p, out):
|
|||||||
|
|
||||||
raise exc(msg)
|
raise exc(msg)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check if unrar works
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
# does UNRAR_TOOL work?
|
||||||
|
custom_check([UNRAR_TOOL], True)
|
||||||
|
except RarCannotExec:
|
||||||
|
try:
|
||||||
|
# does ALT_TOOL work?
|
||||||
|
custom_check([ALT_TOOL] + list(ALT_CHECK_ARGS), True)
|
||||||
|
# replace config
|
||||||
|
UNRAR_TOOL = ALT_TOOL
|
||||||
|
OPEN_ARGS = ALT_OPEN_ARGS
|
||||||
|
EXTRACT_ARGS = ALT_EXTRACT_ARGS
|
||||||
|
TEST_ARGS = ALT_TEST_ARGS
|
||||||
|
except RarCannotExec:
|
||||||
|
# no usable tool, only uncompressed archives work
|
||||||
|
pass
|
||||||
|
|
||||||
196
kindlecomicconverter/shared.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
|
# Copyright (c) 2013-2017 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 sys import version_info
|
||||||
|
from hashlib import md5
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
from time import sleep
|
||||||
|
from shutil import rmtree, copy
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from zipfile import ZipFile, ZIP_DEFLATED
|
||||||
|
from re import split
|
||||||
|
from traceback import format_tb
|
||||||
|
try:
|
||||||
|
from scandir import walk
|
||||||
|
except ImportError:
|
||||||
|
walk = os.walk
|
||||||
|
|
||||||
|
|
||||||
|
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('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg' and ext != '.gif'):
|
||||||
|
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 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(filePath):
|
||||||
|
with open(filePath, 'rb') as fh:
|
||||||
|
m = md5()
|
||||||
|
while True:
|
||||||
|
data = fh.read(8192)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
m.update(data)
|
||||||
|
return m.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def check7ZFile(filePath):
|
||||||
|
with open(filePath, 'rb') as fh:
|
||||||
|
header = fh.read(6)
|
||||||
|
return header == b"7z\xbc\xaf'\x1c"
|
||||||
|
|
||||||
|
|
||||||
|
def saferReplace(old, new):
|
||||||
|
for x in range(30):
|
||||||
|
try:
|
||||||
|
os.replace(old, new)
|
||||||
|
except PermissionError:
|
||||||
|
sleep(1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise PermissionError("Failed to move the file.")
|
||||||
|
|
||||||
|
|
||||||
|
def saferRemove(target):
|
||||||
|
for x in range(30):
|
||||||
|
try:
|
||||||
|
os.remove(target)
|
||||||
|
except PermissionError:
|
||||||
|
sleep(1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise PermissionError("Failed to remove the file.")
|
||||||
|
|
||||||
|
|
||||||
|
def removeFromZIP(zipfname, *filenames):
|
||||||
|
tempdir = mkdtemp('', 'KCC-')
|
||||||
|
try:
|
||||||
|
tempname = os.path.join(tempdir, 'KCC.zip')
|
||||||
|
with ZipFile(zipfname, 'r') as zipread:
|
||||||
|
with ZipFile(tempname, 'w', compression=ZIP_DEFLATED) as zipwrite:
|
||||||
|
for item in zipread.infolist():
|
||||||
|
if item.filename not in filenames:
|
||||||
|
zipwrite.writestr(item, zipread.read(item.filename))
|
||||||
|
for x in range(30):
|
||||||
|
try:
|
||||||
|
copy(tempname, zipfname)
|
||||||
|
except PermissionError:
|
||||||
|
sleep(1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise PermissionError
|
||||||
|
finally:
|
||||||
|
rmtree(tempdir, True)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitizeTrace(traceback):
|
||||||
|
return ''.join(format_tb(traceback))\
|
||||||
|
.replace('C:/Users/Pawel/Documents/Projekty/KCC/', '')\
|
||||||
|
.replace('C:/Python35/', '')\
|
||||||
|
.replace('c:/python35/', '')\
|
||||||
|
.replace('C:\\Users\\Pawel\\Documents\\Projekty\\KCC\\', '')\
|
||||||
|
.replace('C:\\Python35\\', '')\
|
||||||
|
.replace('c:\\python35\\', '')
|
||||||
|
|
||||||
|
|
||||||
|
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 5.13.0+')
|
||||||
|
if level > 1:
|
||||||
|
try:
|
||||||
|
from psutil import __version__ as psutilVersion
|
||||||
|
if StrictVersion('4.1.0') > StrictVersion(psutilVersion):
|
||||||
|
missing.append('psutil 4.1.0+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('psutil 4.1.0+')
|
||||||
|
try:
|
||||||
|
from slugify import __version__ as slugifyVersion
|
||||||
|
if StrictVersion('1.2.0') > StrictVersion(slugifyVersion):
|
||||||
|
missing.append('python-slugify 1.2.0+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('python-slugify 1.2.0+')
|
||||||
|
try:
|
||||||
|
from PIL import PILLOW_VERSION as pillowVersion
|
||||||
|
if StrictVersion('3.2.0') > StrictVersion(pillowVersion):
|
||||||
|
missing.append('Pillow 3.2.0+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('Pillow 3.2.0+')
|
||||||
|
if version_info[1] < 5:
|
||||||
|
try:
|
||||||
|
from scandir import __version__ as scandirVersion
|
||||||
|
if StrictVersion('1.2') > StrictVersion(scandirVersion):
|
||||||
|
missing.append('scandir 1.2+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('scandir 1.2+')
|
||||||
|
if len(missing) > 0:
|
||||||
|
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||||
|
exit(1)
|
||||||
4
other/linux/kindlecomicconverter
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
kindlecomicconverter: binary-without-manpage usr/bin/kcc
|
||||||
|
kindlecomicconverter: wrong-name-for-changelog-of-native-package usr/share/doc/kindlecomicconverter/changelog.Debian.gz
|
||||||
|
kindlecomicconverter: file-missing-in-md5sums usr/share/doc/kindlecomicconverter/changelog.Debian.gz
|
||||||
|
kindlecomicconverter: hardening-no-relro usr/bin/kcc
|
||||||
11
other/linux/kindlecomicconverter.desktop
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Version=1.0
|
||||||
|
Name=Kindle Comic Converter
|
||||||
|
GenericName=Kindle Comic Converter
|
||||||
|
Comment=Comic and Manga converter for e-book readers
|
||||||
|
Icon=/usr/share/kindlecomicconverter/comic2ebook.png
|
||||||
|
Exec=/usr/bin/kcc %f
|
||||||
|
Terminal=false
|
||||||
|
Categories=Graphics;
|
||||||
|
MimeType=application/zip;application/x-rar;application/x-7z-compressed;
|
||||||
BIN
other/osx/7za
Executable file
68
other/osx/Info.plist
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?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.3.0, written 2012-2017 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.3.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>5.3.0</string>
|
||||||
|
<key>LSEnvironment</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>./../Resources:/usr/local/bin:/usr/bin:/bin</string>
|
||||||
|
</dict>
|
||||||
|
<key>LSHasLocalizedDisplayName</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.9.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>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
other/osx/unrar
Executable file
BIN
other/windows/7za.exe
Normal file
91
other/windows/Additional-LICENSE.txt
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
****** ***** ****** UnRAR - free utility for RAR archives
|
||||||
|
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
****** ******* ****** License for use and distribution of
|
||||||
|
** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
** ** ** ** ** ** FREEWARE version
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The UnRAR utility is freeware. This means:
|
||||||
|
|
||||||
|
1. All copyrights to RAR and the utility UnRAR are exclusively
|
||||||
|
owned by the author - Alexander Roshal.
|
||||||
|
|
||||||
|
2. The UnRAR utility may be freely distributed. It is allowed
|
||||||
|
to distribute UnRAR inside of other software packages.
|
||||||
|
|
||||||
|
3. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS".
|
||||||
|
NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT
|
||||||
|
YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS,
|
||||||
|
DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
|
||||||
|
OR MISUSING THIS SOFTWARE.
|
||||||
|
|
||||||
|
4. Neither RAR binary code, WinRAR binary code, UnRAR source or UnRAR
|
||||||
|
binary code may be used or reverse engineered to re-create the RAR
|
||||||
|
compression algorithm, which is proprietary, without written
|
||||||
|
permission of the author.
|
||||||
|
|
||||||
|
5. If you don't agree with terms of the license you must remove
|
||||||
|
UnRAR files from your storage devices and cease to use the
|
||||||
|
utility.
|
||||||
|
|
||||||
|
Thank you for your interest in RAR and UnRAR.
|
||||||
|
|
||||||
|
|
||||||
|
Alexander L. Roshal
|
||||||
|
|
||||||
|
7-Zip
|
||||||
|
~~~~~
|
||||||
|
License for use and distribution
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
7-Zip Copyright (C) 1999-2012 Igor Pavlov.
|
||||||
|
|
||||||
|
Licenses for files are:
|
||||||
|
|
||||||
|
1) 7z.dll: GNU LGPL + unRAR restriction
|
||||||
|
2) All other files: GNU LGPL
|
||||||
|
|
||||||
|
The GNU LGPL + unRAR restriction means that you must follow both
|
||||||
|
GNU LGPL rules and unRAR restriction rules.
|
||||||
|
|
||||||
|
|
||||||
|
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/
|
||||||
|
|
||||||
|
|
||||||
|
unRAR 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
|
||||||
229
other/windows/InstallWarning.rtf
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
{\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;}{\f41\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||||
|
{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||||
|
{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f41\fbidi \froman\fcharset0\fprq2 Times New Roman;}
|
||||||
|
{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||||
|
{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f411\fbidi \fswiss\fcharset0\fprq2 Calibri;}
|
||||||
|
{\f410\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f412\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f413\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f416\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
|
||||||
|
{\f417\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\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\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\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\yr2013\mo10\dy29\hr15\min28}{\version8}{\edmins8}{\nofpages1}{\nofwords33}{\nofchars200}{\nofcharsws232}{\vern57435}}{\*\xmlnstbl {\xmlns1 h
|
||||||
|
ttp://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 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
|
||||||
|
5500540046003800260064006f006300490064003d0031003000300030003700360035003200310031000000795881f43b1d7f48af2c825dc485276300000000a5ab000000}}}{\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\lsdstimax371\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;}}{\*\datastore 010500000200000018000000
|
||||||
|
4d73786d6c322e534158584d4c5265616465722e362e30000000000000000000000e0000
|
||||||
|
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000000200000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
fffffffffffffffffdffffff04000000feffffff05000000fefffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff010000000c6ad98892f1d411a65f0040963251e50000000000000000000000006069
|
||||||
|
e214b3d4ce010300000080020000000000004d0073006f004400610074006100530074006f0072006500000000000000000000000000000000000000000000000000000000000000000000000000000000001a000101ffffffffffffffff0200000000000000000000000000000000000000000000006069e214b3d4ce01
|
||||||
|
6069e214b3d4ce010000000000000000000000003500d900ca00dd00ce004400cc00c8005a0045004700c400cd0057004900c500d400c900cb00ce00570051003d003d000000000000000000000000000000000032000101ffffffffffffffff0300000000000000000000000000000000000000000000006069e214b3d4
|
||||||
|
ce016069e214b3d4ce010000000000000000000000004900740065006d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000
|
||||||
|
00000000000000000000000000000000fc00000000000000010000000200000003000000feffffff0500000006000000070000000800000009000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3c623a536f75726365732053656c65637465645374796c653d225c415041536978746845646974696f6e4f66666963654f6e6c696e652e78736c22205374796c654e616d653d22415041222056657273696f6e3d22362220786d6c6e733a
|
||||||
|
623d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f6772617068792220786d6c6e733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e74
|
||||||
|
2f323030362f6269626c696f677261706879223e3c2f623a536f75726365733e000000003c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e0d0a3c64733a6461746173746f72654974656d2064733a6974656d49443d227b42384244
|
||||||
|
394137462d323833422d343136342d413442352d3632323544323941454535397d2220786d6c6e733a64733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f637573746f6d586d6c223e3c64733a736368656d61526566733e3c
|
||||||
|
64733a736368656d615265662064733a7572693d22687474703a2f2f736368656d61732e6f70656e500072006f007000650072007400690065007300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000200ffffffffffffffffffffffff000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000400000055010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000
|
||||||
|
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000
|
||||||
|
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
|
||||||
|
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222f3e3c2f64733a736368656d61526566733e3c2f64733a6461746173746f
|
||||||
|
72654974656d3e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105000000000000}}
|
||||||
BIN
other/windows/UnRAR.exe
Normal file
BIN
other/windows/vc_redist.x64.exe
Normal file
202
setup.py
Normal file → Executable file
@@ -1,87 +1,143 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
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
|
|
||||||
|
|
||||||
Usage (Mac OS X):
|
|
||||||
python setup.py py2app
|
|
||||||
|
|
||||||
Usage (Windows):
|
Usage (Windows):
|
||||||
python setup.py build
|
py -3 setup.py build_binary
|
||||||
|
|
||||||
|
Usage (Linux/OS X):
|
||||||
|
python3 setup.py build_binary or python3 setup.py build_binary --pyz
|
||||||
"""
|
"""
|
||||||
from ez_setup import use_setuptools
|
|
||||||
use_setuptools()
|
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
|
import setuptools
|
||||||
|
import distutils.cmd
|
||||||
|
from distutils.command.build import build
|
||||||
|
from kindlecomicconverter import __version__
|
||||||
|
|
||||||
NAME = "KindleComicConverter"
|
NAME = 'KindleComicConverter'
|
||||||
VERSION = "2.9"
|
MAIN = 'kcc.py'
|
||||||
MAIN = "kcc.py"
|
VERSION = __version__
|
||||||
|
OPTIONS = {}
|
||||||
|
|
||||||
includefiles = ['README.md', 'MANIFEST.in', 'LICENSE.txt', 'comic2ebook.ico', 'comic2ebook.icns']
|
|
||||||
includes = []
|
|
||||||
excludes = []
|
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
class BuildBinaryCommand(distutils.cmd.Command):
|
||||||
from setuptools import setup
|
description = 'build binary release'
|
||||||
extra_options = dict(
|
user_options = [
|
||||||
setup_requires=['py2app'],
|
('pyz', None, 'build PYZ package'),
|
||||||
app=[MAIN],
|
]
|
||||||
options=dict(
|
|
||||||
py2app=dict(
|
def initialize_options(self):
|
||||||
argv_emulation=True,
|
# noinspection PyAttributeOutsideInit
|
||||||
iconfile='comic2ebook.icns',
|
self.pyz = False
|
||||||
plist=dict(
|
|
||||||
CFBundleName=NAME,
|
def finalize_options(self):
|
||||||
CFBundleShortVersionString=VERSION,
|
pass
|
||||||
CFBundleGetInfoString=NAME + " " + VERSION + ", written 2012-2013 by Ciro Mattia Gonano",
|
|
||||||
CFBundleExecutable=NAME,
|
def run(self):
|
||||||
CFBundleIdentifier='com.github.ciromattia.kcc',
|
if sys.platform == 'darwin':
|
||||||
CFBundleSignature='dplt'
|
if os.path.isfile('Kindle Comic Converter.spec'):
|
||||||
)
|
os.system('pyinstaller "Kindle Comic Converter.spec"')
|
||||||
)
|
else:
|
||||||
|
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s --noupx kcc.py')
|
||||||
|
shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||||
|
shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources')
|
||||||
|
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/unrar', 0o777)
|
||||||
|
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7za', 0o777)
|
||||||
|
if os.path.isfile('setup.sh'):
|
||||||
|
os.system('./setup.sh')
|
||||||
|
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
|
||||||
|
exit(0)
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
if os.path.isfile('KCC.spec'):
|
||||||
|
os.system('pyinstaller KCC.spec')
|
||||||
|
else:
|
||||||
|
os.system('pyinstaller -y -F -i icons\comic2ebook.ico -n KCC -w --noupx kcc.py')
|
||||||
|
if os.path.isfile('setup.bat'):
|
||||||
|
os.system('setup.bat')
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
|
if self.pyz:
|
||||||
|
script = '''
|
||||||
|
cp kcc.py __main__.py
|
||||||
|
zip kcc.zip __main__.py kindlecomicconverter/*.py
|
||||||
|
echo "#!/usr/bin/env python3" > kcc-bin
|
||||||
|
cat kcc.zip >> kcc-bin
|
||||||
|
chmod +x kcc-bin
|
||||||
|
|
||||||
|
cp kcc-c2e.py __main__.py
|
||||||
|
zip kcc-c2e.zip __main__.py kindlecomicconverter/*.py
|
||||||
|
echo "#!/usr/bin/env python3" > kcc-c2e-bin
|
||||||
|
cat kcc-c2e.zip >> kcc-c2e-bin
|
||||||
|
chmod +x kcc-c2e-bin
|
||||||
|
|
||||||
|
cp kcc-c2p.py __main__.py
|
||||||
|
zip kcc-c2p.zip __main__.py kindlecomicconverter/*.py
|
||||||
|
echo "#!/usr/bin/env python3" > kcc-c2p-bin
|
||||||
|
cat kcc-c2p.zip >> kcc-c2p-bin
|
||||||
|
chmod +x kcc-c2p-bin
|
||||||
|
|
||||||
|
mkdir dist
|
||||||
|
tar --xform s:^.*/:: \
|
||||||
|
--xform s/LICENSE.txt/LICENSE/ \
|
||||||
|
--xform s/kcc-bin/kcc/ \
|
||||||
|
--xform s/kcc-c2p-bin/kcc-c2p/ \
|
||||||
|
--xform s/kcc-c2e-bin/kcc-c2e/ \
|
||||||
|
--xform s/comic2ebook/kcc/ \
|
||||||
|
-czf dist/KindleComicConverter_linux_''' + VERSION + '''.tar.gz \
|
||||||
|
kcc-bin kcc-c2e-bin kcc-c2p-bin LICENSE.txt README.md icons/comic2ebook.png
|
||||||
|
rm __main__.py kcc.zip kcc-c2e.zip kcc-c2p.zip kcc-bin kcc-c2e-bin kcc-c2p-bin
|
||||||
|
'''
|
||||||
|
os.system("bash -c '%s'" % script)
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
|
os.system('docker run --rm -v ' + os.getcwd() + ':/app -e KCCVER=' + VERSION + ' acidweb/kcc')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildCommand(build):
|
||||||
|
def run(self):
|
||||||
|
os.makedirs('build/_scripts/', exist_ok=True)
|
||||||
|
shutil.copyfile('kcc.py', 'build/_scripts/kcc')
|
||||||
|
shutil.copyfile('kcc-c2e.py', 'build/_scripts/kcc-c2e')
|
||||||
|
shutil.copyfile('kcc-c2p.py', 'build/_scripts/kcc-c2p')
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
OPTIONS = dict(
|
||||||
|
scripts=['build/_scripts/kcc',
|
||||||
|
'build/_scripts/kcc-c2e',
|
||||||
|
'build/_scripts/kcc-c2p'],
|
||||||
|
packages=['kcc'],
|
||||||
|
install_requires=[
|
||||||
|
'PyQt5>=5.6.0'
|
||||||
|
'Pillow>=3.2.0',
|
||||||
|
'psutil>=4.1.0',
|
||||||
|
'python-slugify>=1.2.0',
|
||||||
|
'raven>=5.13.0',
|
||||||
|
],
|
||||||
|
zip_safe=False,
|
||||||
)
|
)
|
||||||
)
|
if sys.version_info[1] < 5:
|
||||||
elif sys.platform == 'win32':
|
OPTIONS['install_requires'].append('scandir>=1.2.0')
|
||||||
from cx_Freeze import setup, Executable
|
build.run(self)
|
||||||
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(
|
|
||||||
|
setuptools.setup(
|
||||||
|
cmdclass={
|
||||||
|
'build_binary': BuildBinaryCommand,
|
||||||
|
'build': BuildCommand,
|
||||||
|
},
|
||||||
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 comic mobipocket mobi cbz cbr manga',
|
||||||
url="http://github.com/ciromattia/kcc",
|
url='http://github.com/ciromattia/kcc',
|
||||||
classifiers=[
|
**OPTIONS
|
||||||
'Development Status :: 4 - Beta'
|
|
||||||
'License :: OSI Approved :: ISC License (ISCL)',
|
|
||||||
'Environment :: Console',
|
|
||||||
'Environment :: MacOS X',
|
|
||||||
'Environment :: Win32 (MS Windows)',
|
|
||||||
'Environment :: X11 Applications',
|
|
||||||
'Intended Audience :: End Users/Desktop',
|
|
||||||
'Operating System :: OS Independent',
|
|
||||||
'Programming Language :: Python',
|
|
||||||
'Programming Language :: Python :: 2.7',
|
|
||||||
'Topic :: Multimedia :: Graphics :: Graphics Conversion',
|
|
||||||
'Topic :: Utilities'
|
|
||||||
],
|
|
||||||
packages=['kcc'],
|
|
||||||
**extra_options
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.9",
|
|
||||||
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)]
|
|
||||||
)
|
|
||||||