1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-18 06:58:58 +00:00

Compare commits

..

114 Commits
2.6 ... 2.9

Author SHA1 Message Date
Ciro Mattia Gonano
148211a5c7 Update for 2.9 release. 2013-04-19 12:14:24 +02:00
Ciro Mattia Gonano
23e07f47f0 Merge pull request #46 from ciromattia/slugify
Filenames slugification
2013-04-19 02:27:54 -07:00
Ciro Mattia Gonano
724156c554 Small fixes 2013-04-12 01:36:51 +02:00
Ciro Mattia Gonano
b972e4c746 Remove Windows silly 'thumbs.db' too 2013-04-11 12:33:14 +02:00
Ciro Mattia Gonano
f0afa1fff2 Convert dot char to hyphen.
Removes UNIX-hidden files and dirs from the final archive (prevents .DS_Store and stuff)
2013-04-11 12:18:02 +02:00
Ciro Mattia Gonano
a36c05f0c5 Merge from master 2013-04-11 12:00:51 +02:00
Ciro Mattia Gonano
4f3a66b4eb Update README.md 2013-04-11 12:59:01 +03:00
Ciro Mattia Gonano
6369c7ea44 Update after merging of #44 2013-04-11 12:54:16 +03:00
Ciro Mattia Gonano
f1b8aff8d4 Merge pull request #44 from devernay/master
Support more input image formats: GIF, TIFF, ...
2013-04-11 02:50:37 -07:00
Ciro Mattia Gonano
be270aa797 Add number padding and lowering for file names (not directory) 2013-04-11 11:49:29 +02:00
Ciro Mattia Gonano
f33d355024 Filenames slugifications (#28, #31, #9, #8) 2013-04-11 10:34:33 +02:00
Ciro Mattia Gonano
6f913b026e rarfile updated to 2.6 2013-04-11 09:34:20 +02:00
Ciro Mattia Gonano
220b4e0954 Add an option to generate a CBZ skipping all the EPUB/Mobi stuff.
Prevent output files to overwrite the source (add _kcc if duplicate is detected)
Fixes #45
2013-04-10 12:29:31 +02:00
Frédéric Devernay
bac4a4fd86 support more input image formats 2013-04-04 14:09:21 +02:00
Paweł Jastrzębski
d923299230 Updated README 2013-03-21 09:02:17 +01:00
Ciro Mattia Gonano
a79ebcec86 Version bump 2013-03-21 01:28:27 +01:00
Ciro Mattia Gonano
69bbee7648 Merge pull request #41 from ciromattia/kcc-outputformat2
Change default file output format
2013-03-20 14:33:43 -07:00
Paweł Jastrzębski
57d7729d05 Decreasing quality
File size/quality ratio is too high. It is not worth it.
Sadly huffman table optimization build-in PIL is just simply broken.
2013-03-20 21:12:30 +01:00
Paweł Jastrzębski
25a1dcb72c Updated GUI 2013-03-20 16:34:04 +01:00
Paweł Jastrzębski
cff9b73b80 Force JPEG output 2013-03-20 16:20:17 +01:00
Paweł Jastrzębski
23336de5b5 Version bump 2013-03-20 10:58:27 +01:00
Ciro Mattia Gonano
d346ca0466 Update version and readme 2013-03-20 10:39:42 +01:00
Ciro Mattia Gonano
22fadf078e Optimize archive extraction for zip/rar files (closes #40) 2013-03-20 10:35:50 +01:00
Ciro Mattia Gonano
74acf85683 Merge pull request #36 from ciromattia/kcc-panelview
Panel View support
2013-03-20 02:14:17 -07:00
Ciro Mattia Gonano
713de03d1d Merge branch 'master' into kcc-panelview 2013-03-20 10:12:57 +01:00
Ciro Mattia Gonano
5817f52110 Update rarfile 2013-03-20 10:10:01 +01:00
Paweł Jastrzębski
dfc03aee38 Restoring more safe filesize limit 2013-03-18 14:13:53 +01:00
Paweł Jastrzębski
6bc8038068 Yet another refactoring of resizeImage
I hope all bugs are dead this time!
2013-03-17 12:52:33 +01:00
Paweł Jastrzębski
eb7d56c1b9 Force upscaling for spitted pages
Only when landscape mode is on.
Without it smaller splitted pages looks really bad on PW.
2013-03-17 10:45:46 +01:00
Paweł Jastrzębski
f5b515ef79 GUI update 2013-03-17 09:37:57 +01:00
Paweł Jastrzębski
943431346d MAJOR Landscape Mode improvement
No idea how this change will impact Non-Kindle devices but quality
increase of spitted pages is amazing.
2013-03-17 08:47:35 +01:00
Paweł Jastrzębski
a597173b71 Added high quality Panel View 2013-03-16 21:53:23 +01:00
Paweł Jastrzębski
c484cc8fff Code inspection 2013-03-16 20:05:27 +01:00
Paweł Jastrzębski
f195a4ccbf Preliminary Panel View support for K3/KNT 2013-03-16 19:51:23 +01:00
Paweł Jastrzębski
f55bb6dce6 Code cleaning 2013-03-16 13:33:13 +01:00
Paweł Jastrzębski
e7f49f8330 New profiles
Kindle Touch support Virtual Panel View.
Kindle Classic/NT is not.
2013-03-16 10:19:57 +01:00
Paweł Jastrzębski
cec9356fc2 Code cleaning 2013-03-16 10:10:19 +01:00
Paweł Jastrzębski
6d704b382d Fixed merge bug 2013-03-13 21:10:50 +01:00
Ciro Mattia Gonano
1c5f67111f Merge branch 'master' into kcc-fakepanelview 2013-03-13 00:38:20 +01:00
Ciro Mattia Gonano
660df4b370 Bump to 2.8-dev 2013-03-13 00:38:03 +01:00
Ciro Mattia Gonano
19dc5cc6f2 Merge branch 'master' into kcc-fakepanelview
Apply PEP-8 codestyle
2013-03-13 00:21:09 +01:00
Ciro Mattia Gonano
67baf90d04 Merge branch 'master' into kcc-fakepanelview
Apply PEP-8 codestyle
2013-03-13 00:20:38 +01:00
Paweł Jastrzębski
07bf41ea6c Made commands little shorter 2013-03-12 19:56:19 +01:00
Ciro Mattia Gonano
c2cfc9b8db Apply the correct ordering for image processing (closes #33, thanks to @devernay) 2013-03-12 16:54:14 +01:00
Ciro Mattia Gonano
7a7a2bc10b Don't set topmost as we don't want GUI to be ALWAYS on top 2013-03-12 16:47:11 +01:00
Paweł Jastrzębski
e3127ed516 Code cleanup 2013-03-12 14:00:52 +01:00
Paweł Jastrzębski
93a98db43e Disabled unused functions 2013-03-12 10:48:30 +01:00
Paweł Jastrzębski
750b55649a Draft of FakePanelView Landscape mode 2013-03-12 10:47:37 +01:00
Paweł Jastrzębski
ca5854a8bd Real Panel View don't split in half 2013-03-10 18:18:20 +01:00
Paweł Jastrzębski
712f728a5e Draft of FakePanelView mode #2 2013-03-10 17:29:06 +01:00
Paweł Jastrzębski
eb7766a5e5 Draft of FakePanelView mode 2013-03-10 11:58:52 +01:00
Paweł Jastrzębski
0cabbfde96 Additional tweaks of Landscape mode 2013-03-10 10:43:34 +01:00
Paweł Jastrzębski
c5983276e5 Added --fakepanelview option 2013-03-10 00:03:11 +01:00
Ciro Mattia Gonano
bb0856c841 Binary release 2013-03-09 23:42:22 +01:00
Paweł Jastrzębski
c21e8e398e Small tweaks of setup files 2013-03-09 18:03:49 +01:00
Paweł Jastrzębski
bba0223efc Win32 section was doubled
It builds correctly on Win32. Please define "problematic files".
2013-03-09 17:57:10 +01:00
Ciro Mattia Gonano
9709bc9c12 comments! 2013-03-09 17:29:41 +01:00
Ciro Mattia Gonano
0d61442c18 Try with icon bundling... 2013-03-09 17:24:27 +01:00
Ciro Mattia Gonano
89fa1393db Merge remote-tracking branch 'origin/master' 2013-03-09 17:17:57 +01:00
Ciro Mattia Gonano
15e86b28ab Do not use python-magic, instead use is_zipfile and is_rarfile, falling back on extension for PDFs. 2013-03-09 17:17:40 +01:00
Paweł Jastrzębski
a72d1128dd Fixed OS X build script 2013-03-09 16:16:01 +01:00
Paweł Jastrzębski
0fd3f6bc0f setup.py py2exe/app -> cx_freeze
Not tested on OS X.
2013-03-08 17:55:26 +01:00
Paweł Jastrzębski
d76ce6c92f Improved setup_console.py 2013-03-08 17:27:54 +01:00
Paweł Jastrzębski
b6fa993c17 Fixed splitCount global 2013-03-08 17:05:17 +01:00
Paweł Jastrzębski
81509012b4 Limit is slightly higher 2013-03-07 13:43:09 +01:00
Ciro Mattia Gonano
6db1ab0792 Update README 2013-03-07 12:22:37 +01:00
Ciro Mattia Gonano
513ea0a7cd Use python-magic (https://github.com/ahupp/python-magic) to get mimetype from files (extension does not matter anymore) 2013-03-07 11:56:06 +01:00
Ciro Mattia Gonano
640b5117f9 Use python-magic (https://github.com/ahupp/python-magic) to get mimetype from files (extension does not matter anymore) 2013-03-07 11:54:37 +01:00
Ciro Mattia Gonano
c9d558a353 Do not call kindlegen if source epub is bigger than 300MB (closes #17) 2013-03-07 11:30:40 +01:00
Ciro Mattia Gonano
378aff4f35 Gracefully exit if unrar missing (#15) 2013-03-07 11:21:17 +01:00
Ciro Mattia Gonano
6142c87fa3 remove brackets 2013-03-07 11:04:28 +01:00
Ciro Mattia Gonano
b01e8e2bc2 Version bumping 2013-03-07 10:53:28 +01:00
Ciro Mattia Gonano
10724489fc Update icon and focusing (#12) 2013-03-07 10:51:45 +01:00
Paweł Jastrzębski
3365f111e4 setup_console.py py2exe -> cx_freeze 2013-03-07 09:26:41 +01:00
Ciro Mattia Gonano
b1d22cd05b Merge branch 'master' of https://github.com/ciromattia/kcc 2013-03-07 00:48:33 +01:00
Ciro Mattia Gonano
74f2250952 Icon! 2013-03-06 23:22:18 +01:00
Ciro Mattia Gonano
36516fd594 Convert button disabled if no file is on the list 2013-03-06 23:17:22 +01:00
Ciro Mattia Gonano
2dab7a707b Update setup with Windows icon 2013-03-06 23:16:50 +01:00
Paweł Jastrzębski
fc8b26e292 Mother of all bug fixes 2013-03-06 23:08:24 +01:00
Ciro Mattia Gonano
5c0964c8fa Use splitlines() instead of regexp (fixes #25) 2013-03-06 22:51:47 +01:00
Ciro Mattia Gonano
6836c6ee17 PEP-8 codestyle (sorry for newline on long lines, PEP-8 want at max 78char-long lines...) 2013-03-06 22:47:17 +01:00
Paweł Jastrzębski
6adc2dff93 Updated README.md 2013-03-06 21:19:12 +01:00
Paweł Jastrzębski
63deec88db Updated README.md 2013-03-06 21:13:38 +01:00
Paweł Jastrzębski
c37e281d69 GUI now will detect missing KindleGen 2013-03-06 21:03:04 +01:00
Paweł Jastrzębski
4a0497addc Fixed bug - CSS directory depth 2013-03-06 20:23:18 +01:00
Paweł Jastrzębski
e2f5c549aa GUI improvements
Option sorting resolved slighty lame but it work.
2013-03-06 20:12:27 +01:00
Paweł Jastrzębski
a8195d44ee Little cleaning and normalisation 2013-03-06 18:01:12 +01:00
Ciro Mattia Gonano
18a505637d Merge pull request #30 from ciromattia/kcc-epubmargins
Pull request for epub margin support
2013-03-06 07:31:49 -08:00
Ciro Mattia Gonano
62475e12c6 Merge branch 'master' 2013-03-06 16:29:56 +01:00
Ciro Mattia Gonano
46888e10d8 Merge pull request #27 from ciromattia/kcc-gamma
KCC Gamma + GUI
2013-03-06 07:19:06 -08:00
Ciro Mattia Gonano
eb406aada0 Merge branch 'master' into kcc-gamma 2013-03-06 16:18:45 +01:00
Ciro Mattia Gonano
1582d03fab Optimization: if gamma is 1.0 don't eval image 2013-03-06 15:15:24 +01:00
Ciro Mattia Gonano
4cfac52d6a Display progressbars (fixes #13)
Still to change into something less eye-bleeding ;)
2013-03-06 15:08:42 +01:00
Ciro Mattia Gonano
04ea816365 Merge branch 'master' of github.com:ciromattia/kcc 2013-03-06 14:27:25 +01:00
Paweł Jastrzębski
6a298175f0 CSS Formatting 2013-03-06 13:39:51 +01:00
Paweł Jastrzębski
690f0298e6 Merge https://github.com/devernay/kcc into kcc-epubmargins 2013-03-06 13:08:26 +01:00
Paweł Jastrzębski
fc93697c28 Merge https://github.com/ciromattia/kcc into kcc-epubmargins 2013-03-06 13:07:38 +01:00
Frédéric Devernay
32391f6a5a better epub support (grabbed from calibre): get rid of those small margins 2013-03-06 12:11:50 +01:00
Ciro Mattia Gonano
f163eac853 Make GUI error reporting more useful 2013-03-06 11:33:28 +01:00
Ciro Mattia Gonano
ce824f4cab Add default gamma by profile support 2013-03-06 11:32:20 +01:00
Ciro Mattia Gonano
df41ad405e Merge branch 'master' into kcc-gamma 2013-03-06 11:10:55 +01:00
Ciro Mattia Gonano
685cdc929b Missing return, now PDF conversion works again 2013-03-06 10:29:59 +01:00
Paweł Jastrzębski
5e65c5149c Litte cleanup of setup files
Added setup_console script that generate binaries used by AWKCC.
2013-03-06 09:41:54 +01:00
Paweł Jastrzębski
718af10be7 Updated GUI
Plus small notquantize tweaks.
2013-03-05 23:41:48 +01:00
Paweł Jastrzębski
b162425e52 Added option to disable dithering 2013-03-05 22:31:26 +01:00
Paweł Jastrzębski
f1b63420f6 Merge branch 'master' of https://github.com/devernay/kcc into kcc-gamma 2013-03-05 21:49:24 +01:00
Ciro Mattia Gonano
95e7329abf Revert to POSIX path - win7 kindlegen seems to want them 2013-03-05 19:25:00 +01:00
Frédéric Devernay
5d3b7e83f5 add --nosplitrotate option (is the name right?) 2013-03-05 17:58:24 +01:00
Frédéric Devernay
751e6eb4e7 save dithered images as PNG, and linearize (inverse gamma) before dithering 2013-03-05 17:56:43 +01:00
Ciro Mattia Gonano
074e31cb2e Revert to POSIX path - win kindlegen seems to work 2013-03-05 17:31:34 +01:00
Ciro Mattia Gonano
e7e87d03cd Replace backslashes in path, too (win paths) 2013-03-05 17:12:48 +01:00
Paweł Jastrzębski
38669d08ed Small .gitignore tweak 2013-03-05 16:55:14 +01:00
Paweł Jastrzębski
825eafad7f Fixed bug caused by formating
2:0 Mr. Ciromattia :-)
2013-03-05 16:52:22 +01:00
Paweł Jastrzębski
f805984c1c Fixed logic - Inserting blank-page records
Variable "facing" could be bool but this way code is more readable.
2013-03-05 16:28:22 +01:00
18 changed files with 967 additions and 668 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@
*.cbr *.cbr
.idea .idea
build build
awkcc
.DS_Store

123
README.md
View File

@@ -1,38 +1,38 @@
# KCC # KCC
`KCC` (a.k.a. `KindleComicConverter`) is a Python app to convert comic files or folders to ePub or Panel View Mobipocket. `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 as of version 2.2 it outputs valid ePub 2.0 so _**despite its name, KCC is 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**_. actually a comic 2 epub converter that every ereader owner can happily use**_.
It also optimizes comic images by:
- enhancing contrast It can also optionally optimize images by applying a number of transformations.
- cutting page numbering
- cropping white borders ### A word of warning
- resizing larger images to device's native resolution **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.
- quantizing images to device's palette Amazon's tool is for comic _publishers_ and involves a lot of manual effort, while **KCC** is for comic _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 ;)
## 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.6.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.6.zip) - OS X: [https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.9.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.9.zip)
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.6.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.6.zip) - 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)
- Win32: [http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.6.zip](http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.6.zip) *(thanks to [AcidWeb](https://github.com/AcidWeb))* - 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))*
- Linux: just download sourcecode and launch `python kcc.py` *(provided you have Python and Pillow installed)* - Linux: Just download sourcecode and launch `python kcc.py` *(Provided you have Python and Pillow installed)*
## 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 file types:
- PNG, JPG, GIF, TIFF, BMP
- Folders
- CBZ, ZIP - CBZ, ZIP
- CBR, RAR *(with `unrar` executable)* - CBR, RAR *(With `unrar` executable)*
- folders - PDF *(Extracting only contained JPG images)*
- PDF *(extracting only contained JPG images)*
~~For now the script does not understand folder depth, so it will work on flat folders/archives only.~~
As of v. 1.50, KCC supports subfolders!
## OPTIONAL REQUIREMENTS ## OPTIONAL REQUIREMENTS
- `kindlegen` in /usr/local/bin/ *(for .mobi generation)* - `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)* - [unrar](http://www.rarlab.com/download.htm) *(For CBR support)*
### for compiling/running from source: ### 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) - 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. - [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. Please refer to official documentation for installing into your system.
@@ -47,41 +47,32 @@ Conversion being done, you should find an .epub and a .mobi files alongside the
### Standalone `comic2ebook.py` usage: ### Standalone `comic2ebook.py` usage:
``` ```
comic2ebook.py [options] comic_file|comic_folder Usage: comic2ebook.py [options] comic_file|comic_folder
Options: Options:
--version show program's version number and exit --version show program's version number and exit
-h, --help show this help message and exit -h, --help show this help message and exit
-p PROFILE, --profile=PROFILE -p PROFILE, --profile=PROFILE
Device profile (choose one among K1, K2, K3, K4, KDX, Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG or KHD) [Default=KHD]
KDXG or KHD) [default=KHD]
-t TITLE, --title=TITLE -t TITLE, --title=TITLE
Comic title [default=filename] 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]
[default=False] -c, --cbz-output Outputs a CBZ archive and does not generate EPUB
-v, --verbose Verbose output [default=False] --nopanelviewhq Disable high quality Panel View [Default=False]
--no-image-processing --noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
Do not apply image preprocessing (page splitting and --forcepng Create PNG files instead JPEG (For non-Kindle devices) [Default=False]
optimizations) [default=True] --gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
--upscale-images Resize images smaller than device's resolution --upscale Resize images smaller than device's resolution [Default=False]
[default=False] --stretch Stretch images to device's resolution [Default=False]
--stretch-images Stretch images to device's resolution [default=False] --blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
--black-borders Use black borders (instead of white ones) when not --rotate Rotate landscape pages instead of splitting them [Default=False]
stretching and ratio is not like the device's one --nosplitrotate Disable splitting and rotation [Default=False]
[default=False] --nocutpagenumbers Do not try to cut page numbering on images [Default=True]
--no-cut-page-numbers
Do not try to cut page numbering on images
[default=True]
--rotate Disable page spliting. Instead rotate images
[default=False]
-o OUTPUT, --output=OUTPUT -o OUTPUT, --output=OUTPUT
Output directory or file for generated ePub Output generated file (EPUB or CBZ) to specified directory or file
-v, --verbose Verbose output [Default=False]
``` ```
The script takes care of creating an *.epub* from your archive/folder, then:
1. Run `Kindlegen` on the generated *.epub*. Depending on how many images you have, this may take awhile. Once completed, the `.mobi` file should be in the directory.
2. (optionally) remove the SRCS record to reduce the `.mobi` filesize in half. You can use [Kindlestrip](http://www.mobileread.com/forums/showthread.php?t=96903).
3. Copy the `.mobi` file to your Kindle!
## 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)
@@ -94,10 +85,7 @@ The app relies and includes the following scripts/binaries:
- `rarfile.py` script &copy; 2005-2011 **Marko Kreen** <markokr@gmail.com>, released with ISC License - `rarfile.py` script &copy; 2005-2011 **Marko Kreen** <markokr@gmail.com>, released with ISC License
- the icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC Attribution-NonCommercial-ShareAlike 3.0 Unported](http://creativecommons.org/licenses/by-nc-sa/3.0/) License - the icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC Attribution-NonCommercial-ShareAlike 3.0 Unported](http://creativecommons.org/licenses/by-nc-sa/3.0/) License
- `image.py` class from **Alex Yatskov**'s [Mangle](http://foosoft.net/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches - `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
Also, for .mobi generation you need to have `kindlegen` v2.7+ (with KF8 support) which is downloadable from Amazon website
and installed in a directory reachable by your PATH (e.g. `/usr/local/bin/` or `C:\Windows\`)
## CHANGELOG ## CHANGELOG
- 1.00: Initial version - 1.00: Initial version
@@ -120,10 +108,33 @@ and installed in a directory reachable by your PATH (e.g. `/usr/local/bin/` or `
- 2.4: Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming) - 2.4: Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
Fixed "add folders" from GUI. 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). - 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). Fixes epub containing zipped itself (#10)
- 2.6: Added --rotate option to rotate landscape images instead of splitting them. - 2.6: Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
Added --output option to customize ePub output dir/file. 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) 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)
## COPYRIGHT ## COPYRIGHT

BIN
comic2ebook.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

13
kcc.py
View File

@@ -16,7 +16,7 @@
# 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.6' __version__ = '2.9'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>' __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
@@ -26,9 +26,14 @@ from kcc import gui
from sys import platform from sys import platform
import os import os
root = Tk()
root.resizable(width=False, height=False)
root.config(padx=5, pady=5, takefocus=True)
root.title("Kindle Comic Converter v" + __version__)
#root.wm_attributes("-topmost", 1)
if platform == 'darwin': if platform == 'darwin':
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH'] os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
root = Tk() elif platform == 'win32':
app = gui.MainWindow(master=root,title="Kindle Comic Converter v" + __version__) root.iconbitmap(default='comic2ebook.ico')
root.tkraise() gui.MainWindow(master=root)
root.mainloop() root.mainloop()

View File

@@ -19,23 +19,26 @@ __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os import os
import zipfile
import rarfile
class CBxArchive: class CBxArchive:
def __init__(self, origFileName): def __init__(self, origFileName):
self.cbxexts = ['.zip', '.cbz', '.rar', '.cbr']
self.origFileName = origFileName self.origFileName = origFileName
self.filename = os.path.splitext(origFileName) if zipfile.is_zipfile(origFileName):
self.compressor = 'zip'
elif rarfile.is_rarfile(origFileName):
self.compressor = 'rar'
else:
self.compressor = None
def isCbxFile(self): def isCbxFile(self):
return self.filename[1].lower() in self.cbxexts return self.compressor is not None
def extractCBZ(self, targetdir): def extractCBZ(self, targetdir):
try: cbzFile = zipfile.ZipFile(self.origFileName)
from zipfile import ZipFile filelist = []
except ImportError:
self.cbzFile = None
cbzFile = ZipFile(self.origFileName)
for f in cbzFile.namelist(): for f in cbzFile.namelist():
if f.startswith('__MACOSX') or f.endswith('.DS_Store'): if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
pass # skip MacOS special files pass # skip MacOS special files
@@ -45,15 +48,12 @@ class CBxArchive:
except: except:
pass # the dir exists so we are going to extract the images only. pass # the dir exists so we are going to extract the images only.
else: else:
cbzFile.extract(f, targetdir) filelist.append(f)
cbzFile.extractall(targetdir, filelist)
def extractCBR(self, targetdir): def extractCBR(self, targetdir):
try:
import rarfile
except ImportError:
self.cbrFile = None
return
cbrFile = rarfile.RarFile(self.origFileName) cbrFile = rarfile.RarFile(self.origFileName)
filelist = []
for f in cbrFile.namelist(): for f in cbrFile.namelist():
if f.startswith('__MACOSX') or f.endswith('.DS_Store'): if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
pass # skip MacOS special files pass # skip MacOS special files
@@ -63,12 +63,14 @@ class CBxArchive:
except: except:
pass # the dir exists so we are going to extract the images only. pass # the dir exists so we are going to extract the images only.
else: else:
cbrFile.extract(f, targetdir) filelist.append(f)
cbrFile.extractall(targetdir, filelist)
def extract(self, targetdir): def extract(self, targetdir):
if '.cbr' == self.filename[1].lower() or '.rar' == self.filename[1].lower(): print "\n" + targetdir + "\n"
if self.compressor == 'rar':
self.extractCBR(targetdir) self.extractCBR(targetdir)
elif '.cbz' == self.filename[1].lower() or '.zip' == self.filename[1].lower(): elif self.compressor == 'zip':
self.extractCBZ(targetdir) self.extractCBZ(targetdir)
adir = os.listdir(targetdir) adir = os.listdir(targetdir)
if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])): if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])):

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# #
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
# #
@@ -16,7 +17,7 @@
# 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.6' __version__ = '2.9'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>' __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
@@ -60,23 +61,49 @@ def buildHTML(path, imgfile):
"<head>\n", "<head>\n",
"<title>", filename[0], "</title>\n", "<title>", filename[0], "</title>\n",
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\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", "</head>\n",
"<body>\n", "<body>\n",
"<div class=\"fs\">\n",
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"", "<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
imgfile, "\" class=\"singlePage\"/></div>\n", imgfile, "\" class=\"singlePage\"/></div>\n"
#"<div id=\"", filename[0], "-1\">\n",
#"<a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"", filename[0],
#"-1-magTargetParent\", \"ordinal\":1}'></a>\n",
#"</div>\n",
#"<div id=\"", filename[0], "-1-magTargetParent\" class=\"target-mag-parent\">\n",
#"<div class=\"target-mag-lb\">\n",
#"</div>\n",
#"<div id=\"", filename[0], "-1-magTarget\" class=\"target-mag\">\n",
#"<img src=\"../" * backref, "Images/", postfix, imgfile, "\" alt=\"", imgfile, "\"/>\n",
#"</div></div>\n",
"</body>\n",
"</html>"
]) ])
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() f.close()
return path, imgfile return path, imgfile
@@ -102,7 +129,7 @@ def buildNCX(dstdir, title, chapters):
f = open(ncxfile, "w") f = open(ncxfile, "w")
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\" ", "<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\" ",
"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n", "\"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", "<ncx version=\"2005-1\" xml:lang=\"en-US\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n",
"<head>\n", "<head>\n",
"<meta name=\"dtb:uid\" content=\"015ffaec-9340-42f8-b163-a0c5ab7d0611\"/>\n", "<meta name=\"dtb:uid\" content=\"015ffaec-9340-42f8-b163-a0c5ab7d0611\"/>\n",
@@ -117,19 +144,19 @@ def buildNCX(dstdir, title, chapters):
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
title = os.path.basename(folder) title = os.path.basename(folder)
filename = getImageFileName(os.path.join(folder, chapter[1])) filename = getImageFileName(os.path.join(folder, chapter[1]))
f.write("<navPoint id=\"" + folder.replace('/', '_') + "\"><navLabel><text>" + title f.write("<navPoint id=\"" + folder.replace('/', '_').replace('\\', '_') + "\"><navLabel><text>" + title
+ "</text></navLabel><content src=\"" + filename[0] + ".html\"/></navPoint>\n") + "</text></navLabel><content src=\"" + filename[0] + ".html\"/></navPoint>\n")
f.write("</navMap>\n</ncx>") f.write("</navMap>\n</ncx>")
f.close() f.close()
return return
def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False): def buildOPF(profile, dstdir, title, filelist, cover=None):
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf') opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
# read the first file resolution # read the first file resolution
profilelabel, deviceres, palette = image.ProfileData.Profiles[profile] profilelabel, deviceres, palette, gamma, panelviewsize = image.ProfileData.Profiles[profile]
imgres = str(deviceres[0]) + "x" + str(deviceres[1]) imgres = str(deviceres[0]) + "x" + str(deviceres[1])
if righttoleft: if options.righttoleft:
writingmode = "horizontal-rl" writingmode = "horizontal-rl"
facing = "right" facing = "right"
facing1 = "right" facing1 = "right"
@@ -155,16 +182,19 @@ def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False):
"<meta name=\"book-type\" content=\"comic\"/>\n", "<meta name=\"book-type\" content=\"comic\"/>\n",
"<meta name=\"zero-gutter\" content=\"true\"/>\n", "<meta name=\"zero-gutter\" content=\"true\"/>\n",
"<meta name=\"zero-margin\" content=\"true\"/>\n", "<meta name=\"zero-margin\" content=\"true\"/>\n",
"<meta name=\"fixed-layout\" content=\"true\"/>\n", "<meta name=\"fixed-layout\" content=\"true\"/>\n"
"<meta name=\"orientation-lock\" content=\"none\"/>\n", ])
"<meta name=\"original-resolution\" content=\"", imgres, "\"/>\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=\"primary-writing-mode\" content=\"", writingmode, "\"/>\n",
"<meta name=\"rendition:layout\" content=\"pre-paginated\"/>\n", "<meta name=\"rendition:layout\" content=\"pre-paginated\"/>\n",
"<meta name=\"rendition:orientation\" content=\"auto\"/>\n",
"</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ", "</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ",
"media-type=\"application/x-dtbncx+xml\"/>\n" "media-type=\"application/x-dtbncx+xml\"/>\n"])
])
# set cover
if cover is not None: if cover is not None:
filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')) filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\'))
if '.png' == filename[1]: if '.png' == filename[1]:
@@ -176,43 +206,57 @@ def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False):
for path in filelist: for path in filelist:
folder = path[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') folder = path[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
filename = getImageFileName(path[1]) filename = getImageFileName(path[1])
uniqueid = os.path.join(folder, filename[0]).replace('/', '_') uniqueid = os.path.join(folder, filename[0]).replace('/', '_').replace('\\', '_')
reflist.append(uniqueid) reflist.append(uniqueid)
f.write("<item id=\"page_" + uniqueid + "\" href=\"" f.write("<item id=\"page_" + uniqueid + "\" href=\""
+ os.path.join(folder.replace('Images', 'Text'), filename[0]) + folder.replace('Images', 'Text') + "/" + filename[0]
+ ".html\" media-type=\"application/xhtml+xml\"/>\n") + ".html\" media-type=\"application/xhtml+xml\"/>\n")
if '.png' == filename[1]: if '.png' == filename[1]:
mt = 'image/png' mt = 'image/png'
else: else:
mt = 'image/jpeg' mt = 'image/jpeg'
f.write("<item id=\"img_" + uniqueid + "\" href=\"" + os.path.join(folder, path[1]) + "\" media-type=\"" f.write("<item id=\"img_" + uniqueid + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\""
+ mt + "\"/>\n") + mt + "\"/>\n")
if (options.profile == 'K4' or options.profile == 'KHD') and splittedSomething: if options.landscapemode and splitCount > 0:
f.write("<item id=\"blank-page\" href=\"" splitCountUsed = 1
+ os.path.join('Text', 'blank.html') while splitCountUsed <= splitCount:
+ "\" media-type=\"application/xhtml+xml\"/>\n") 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") f.write("</manifest>\n<spine toc=\"ncx\">\n")
splitCountUsed = 1
for entry in reflist: for entry in reflist:
if entry.endswith("-1"): if entry.endswith("-1"):
if (righttoleft and facing == 'left') or (not righttoleft and facing == 'right') and \ # noinspection PyRedundantParentheses
(options.profile == 'K4' or options.profile == 'KHD'): if ((options.righttoleft and facing == 'left') or (not options.righttoleft and facing == 'right')) and\
f.write("<itemref idref=\"blank-page\" properties=\"layout-blank\"/>\n") 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") f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing1 + "\"/>\n")
else:
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
elif entry.endswith("-2"): elif entry.endswith("-2"):
if options.landscapemode:
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing2 + "\"/>\n") f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing2 + "\"/>\n")
if righttoleft: else:
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
if options.righttoleft:
facing = "right" facing = "right"
else: else:
facing = "left" facing = "left"
else: else:
if options.landscapemode:
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing + "\"/>\n") f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing + "\"/>\n")
else:
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
if facing == 'right': if facing == 'right':
facing = 'left' facing = 'left'
else: else:
facing = 'right' facing = 'right'
f.write("</spine>\n<guide>\n</guide>\n</package>\n") f.write("</spine>\n<guide>\n</guide>\n</package>\n")
f.close() f.close()
# finish with standard ePub folders
os.mkdir(os.path.join(dstdir, 'META-INF')) os.mkdir(os.path.join(dstdir, 'META-INF'))
f = open(os.path.join(dstdir, 'mimetype'), 'w') f = open(os.path.join(dstdir, 'mimetype'), 'w')
f.write('application/epub+zip') f.write('application/epub+zip')
@@ -233,6 +277,10 @@ def getImageFileName(imgfile):
if filename[0].startswith('.') or\ if filename[0].startswith('.') or\
(filename[1].lower() != '.png' and (filename[1].lower() != '.png' and
filename[1].lower() != '.jpg' 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'): filename[1].lower() != '.jpeg'):
return None return None
return filename return filename
@@ -248,18 +296,22 @@ def isInFilelist(filename, filelist):
def applyImgOptimization(img, isSplit=False, toRight=False): def applyImgOptimization(img, isSplit=False, toRight=False):
img.optimizeImage()
img.cropWhiteSpace(10.0) img.cropWhiteSpace(10.0)
if options.cutpagenumbers: if options.cutpagenumbers:
img.cutPageNumber() img.cutPageNumber()
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight) img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight, options.landscapemode,
options.nopanelviewhq)
img.optimizeImage(options.gamma)
if options.forcepng:
img.quantizeImage() img.quantizeImage()
def dirImgProcess(path): def dirImgProcess(path):
global options global options, splitCount
global splittedSomething if options.righttoleft:
splittedSomething = False facing = "right"
else:
facing = "left"
for (dirpath, dirnames, filenames) in os.walk(path): for (dirpath, dirnames, filenames) in os.walk(path):
for afile in filenames: for afile in filenames:
@@ -269,26 +321,38 @@ def dirImgProcess(path):
else: else:
print ".", print ".",
img = image.ComicPage(os.path.join(dirpath, afile), options.profile) img = image.ComicPage(os.path.join(dirpath, afile), options.profile)
if options.nosplitrotate:
split = None
else:
split = img.splitPage(dirpath, options.righttoleft, options.rotate) split = img.splitPage(dirpath, options.righttoleft, options.rotate)
if split is not None: if split is not None:
splittedSomething = True
if options.verbose: if options.verbose:
print "Splitted " + afile print "Splitted " + afile
if options.righttoleft: if options.righttoleft:
toRight1 = False toRight1 = False
toRight2 = True toRight2 = True
if facing == "left":
splitCount += 1
facing = "right"
else: else:
toRight1 = True toRight1 = True
toRight2 = False toRight2 = False
if facing == "right":
splitCount += 1
facing = "left"
img0 = image.ComicPage(split[0], options.profile) img0 = image.ComicPage(split[0], options.profile)
applyImgOptimization(img0, True, toRight1) applyImgOptimization(img0, True, toRight1)
img0.saveToDir(dirpath) img0.saveToDir(dirpath, options.forcepng)
img1 = image.ComicPage(split[1], options.profile) img1 = image.ComicPage(split[1], options.profile)
applyImgOptimization(img1, True, toRight2) applyImgOptimization(img1, True, toRight2)
img1.saveToDir(dirpath) img1.saveToDir(dirpath, options.forcepng)
else: else:
if facing == "right":
facing = "left"
else:
facing = "right"
applyImgOptimization(img) applyImgOptimization(img)
img.saveToDir(dirpath) img.saveToDir(dirpath, options.forcepng)
def genEpubStruct(path): def genEpubStruct(path):
@@ -296,13 +360,134 @@ def genEpubStruct(path):
filelist = [] filelist = []
chapterlist = [] chapterlist = []
cover = None cover = None
_, deviceres, _, _, panelviewsize = image.ProfileData.Profiles[options.profile]
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
os.mkdir(os.path.join(path, 'OEBPS', 'Text')) 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')): for (dirpath, dirnames, filenames) in os.walk(os.path.join(path, 'OEBPS', 'Images')):
chapter = False chapter = False
for afile in filenames: for afile in filenames:
filename = getImageFileName(afile) filename = getImageFileName(afile)
if filename is not None: if filename is not None:
# put credits at the end
if "credit" in afile.lower(): if "credit" in afile.lower():
os.rename(os.path.join(dirpath, afile), os.path.join(dirpath, 'ZZZ999_' + afile)) os.rename(os.path.join(dirpath, afile), os.path.join(dirpath, 'ZZZ999_' + afile))
afile = 'ZZZ999_' + afile afile = 'ZZZ999_' + afile
@@ -322,14 +507,13 @@ def genEpubStruct(path):
convert = lambda text: int(text) if text.isdigit() else text convert = lambda text: int(text) if text.isdigit() else text
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] 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()))) filelist.sort(key=lambda name: (alphanum_key(name[0].lower()), alphanum_key(name[1].lower())))
buildOPF(options.profile, path, options.title, filelist, cover, options.righttoleft) buildOPF(options.profile, path, options.title, filelist, cover)
if (options.profile == 'K4' or options.profile == 'KHD') and splittedSomething: if options.landscapemode and splitCount > 0:
filelist.append(buildBlankHTML(os.path.join(path, 'OEBPS', 'Text'))) filelist.append(buildBlankHTML(os.path.join(path, 'OEBPS', 'Text')))
def getWorkFolder(afile): def getWorkFolder(afile):
workdir = tempfile.mkdtemp() workdir = tempfile.mkdtemp()
fname = os.path.splitext(afile)
if os.path.isdir(afile): if os.path.isdir(afile):
try: try:
import shutil import shutil
@@ -338,13 +522,18 @@ def getWorkFolder(afile):
path = workdir path = workdir
except OSError: except OSError:
raise raise
elif fname[1].lower() == '.pdf': elif afile.lower().endswith('.pdf'):
pdf = pdfjpgextract.PdfJpgExtract(afile) pdf = pdfjpgextract.PdfJpgExtract(afile)
path = pdf.extract() path = pdf.extract()
else: else:
cbx = cbxarchive.CBxArchive(afile) cbx = cbxarchive.CBxArchive(afile)
if cbx.isCbxFile(): if cbx.isCbxFile():
try:
path = cbx.extract(workdir) 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: else:
raise TypeError raise TypeError
move(path, path + "_temp") move(path, path + "_temp")
@@ -352,73 +541,145 @@ def getWorkFolder(afile):
return path 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(): def Copyright():
print ('comic2ebook v%(__version__)s. ' print ('comic2ebook v%(__version__)s. '
'Written 2012 by Ciro Mattia Gonano.' % globals()) 'Written 2012 by Ciro Mattia Gonano.' % globals())
def Usage(): def Usage():
print "Generates HTML, NCX and OPF for a Comic ebook from a bunch of images" print "Generates HTML, NCX and OPF for a Comic ebook from a bunch of images."
print "Optimized for creating Mobipockets to be read into Kindle Paperwhite"
parser.print_help() parser.print_help()
def main(argv=None): def main(argv=None):
global parser, options, epub_path global parser, options, epub_path, splitCount
usage = "Usage: %prog [options] comic_file|comic_folder" usage = "Usage: %prog [options] comic_file|comic_folder"
parser = OptionParser(usage=usage, version=__version__) parser = OptionParser(usage=usage, version=__version__)
parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD", parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
help="Device profile (choose one among K1, K2, K3, K4, KDX, KDXG or KHD) [default=KHD]") 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", parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
help="Comic title [default=filename]") help="Comic title [Default=filename]")
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="'Manga style' (right-to-left reading and splitting) [default=False]") help="Manga style (Right-to-left reading and splitting) [Default=False]")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, parser.add_option("-c", "--cbz-output", action="store_true", dest="cbzoutput", default=False,
help="Verbose output [default=False]") help="Outputs a CBZ archive and does not generate EPUB")
parser.add_option("--no-image-processing", action="store_false", dest="imgproc", default=True, parser.add_option("--nopanelviewhq", action="store_true", dest="nopanelviewhq", default=False,
help="Do not apply image preprocessing (page splitting and optimizations) [default=True]") help="Disable high quality Panel View [Default=False]")
parser.add_option("--upscale-images", action="store_true", dest="upscale", default=False, parser.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
help="Resize images smaller than device's resolution [default=False]") help="Do not apply image preprocessing (Page splitting and optimizations) [Default=True]")
parser.add_option("--stretch-images", action="store_true", dest="stretch", default=False, parser.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Stretch images to device's resolution [default=False]") help="Create PNG files instead JPEG (For non-Kindle devices) [Default=False]")
parser.add_option("--black-borders", action="store_true", dest="black_borders", default=False, parser.add_option("--gamma", type="float", dest="gamma", default="0.0",
help="Use black borders (instead of white ones) when not stretching and ratio " help="Apply gamma correction to linearize the image [Default=Auto]")
+ "is not like the device's one [default=False]") parser.add_option("--upscale", action="store_true", dest="upscale", default=False,
parser.add_option("--no-cut-page-numbers", action="store_false", dest="cutpagenumbers", default=True, help="Resize images smaller than device's resolution [Default=False]")
help="Do not try to cut page numbering on images [default=True]") 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, parser.add_option("--rotate", action="store_true", dest="rotate", default=False,
help="Rotate landscape pages instead of splitting them [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, parser.add_option("-o", "--output", action="store", dest="output", default=None,
help="Output directory or file for generated ePub") 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) options, args = parser.parse_args(argv)
checkOptions()
if len(args) != 1: if len(args) != 1:
parser.print_help() parser.print_help()
return return
path = getWorkFolder(args[0]) path = getWorkFolder(args[0])
if options.title == 'defaulttitle': if options.title == 'defaulttitle':
options.title = os.path.splitext(os.path.basename(args[0]))[0] options.title = os.path.splitext(os.path.basename(args[0]))[0]
splitCount = 0
if options.imgproc: if options.imgproc:
print "Processing images..." print "Processing images..."
dirImgProcess(path + "/OEBPS/Images/") dirImgProcess(path + "/OEBPS/Images/")
print "Creating ePub structure..." 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) genEpubStruct(path)
# actually zip the ePub # actually zip the ePub
if options.output is not None: filepath = getOutputFilename(args[0], options.output, '.epub')
if options.output.endswith('.epub'):
epubpath = os.path.abspath(options.output)
elif os.path.isdir(args[0]):
epubpath = os.path.abspath(options.output) + "/" + os.path.basename(args[0]) + '.epub'
else:
epubpath = os.path.abspath(options.output) + "/" \
+ os.path.basename(os.path.splitext(args[0])[0]) + '.epub'
elif os.path.isdir(args[0]):
epubpath = args[0] + '.epub'
else:
epubpath = os.path.splitext(args[0])[0] + '.epub'
make_archive(path + '_comic', 'zip', path) make_archive(path + '_comic', 'zip', path)
move(path + '_comic.zip', epubpath) move(path + '_comic.zip', filepath)
rmtree(path) rmtree(path)
return epubpath 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(): def getEpubPath():

View File

@@ -31,6 +31,7 @@ from subprocess import call
import os import os
import shutil import shutil
import stat import stat
import traceback
class MainWindow: class MainWindow:
@@ -39,25 +40,22 @@ class MainWindow:
self.filelist = [] self.filelist = []
self.refresh_list() 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): def open_files(self):
filetypes = [('all files', '.*'), ('Comic files', ('*.cbr', '*.cbz', '*.zip', '*.rar', '*.pdf'))] filetypes = [('All files', '.*'), ('Comic files', ('*.cbr', '*.cbz', '*.zip', '*.rar', '*.pdf'))]
f = tkFileDialog.askopenfilenames(title="Choose a file...", filetypes=filetypes) f = tkFileDialog.askopenfilenames(title="Choose files", filetypes=filetypes)
if not isinstance(f, tuple): if not isinstance(f, tuple):
try: f = self.master.tk.splitlist(f)
import re
f = re.findall('\{(.*?)\}', f)
except:
import tkMessageBox
tkMessageBox.showerror(
"Open file",
"askopenfilename() returned other than a tuple and no regex module could be found"
)
sys.exit(1)
self.filelist.extend(f) self.filelist.extend(f)
self.refresh_list() self.refresh_list()
def open_folder(self): def open_folder(self):
f = tkFileDialog.askdirectory(title="Choose a folder...") f = tkFileDialog.askdirectory(title="Choose folder:")
self.filelist.extend([f]) self.filelist.extend([f])
self.refresh_list() self.refresh_list()
@@ -67,6 +65,13 @@ class MainWindow:
for afile in self.filelist: for afile in self.filelist:
self.filelocation.insert(END, afile) self.filelocation.insert(END, afile)
self.filelocation.config(state=DISABLED) 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): def initialize(self):
self.filelocation = Listbox(self.master) self.filelocation = Listbox(self.master)
@@ -74,97 +79,155 @@ class MainWindow:
self.refresh_list() self.refresh_list()
self.clear_file = Button(self.master, text="Clear files", command=self.clear_files) self.clear_file = Button(self.master, text="Clear files", command=self.clear_files)
self.clear_file.grid(row=4, column=0, rowspan=3) 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 = Button(self.master, text="Add files", command=self.open_files)
self.open_file.grid(row=4, column=1, rowspan=3) 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 = Button(self.master, text="Add folder", command=self.open_folder)
self.open_folder.grid(row=4, column=2, rowspan=3) self.open_folder.grid(row=4, column=2, sticky=W + E + N + S)
self.profile = StringVar() self.profile = StringVar()
profiles = sorted(ProfileData.ProfileLabels.iterkeys()) profiles = sorted(ProfileData.ProfileLabels.iterkeys())
self.profile.set(profiles[-1]) self.profile.set(profiles[-1])
w = apply(OptionMenu, (self.master, self.profile) + tuple(profiles)) w = apply(OptionMenu, (self.master, self.profile) + tuple(profiles))
w.grid(row=1, column=3) w.grid(row=4, column=3, sticky=W + E + N + S)
self.options = { self.options = {
'epub_only': IntVar(None, 0), 'Aepub_only': IntVar(None, 0),
'image_preprocess': IntVar(None, 1), 'Bcbz_only': IntVar(None, 0),
'rotate': IntVar(None, 0), 'Cmangastyle': IntVar(None, 0),
'cut_page_numbers': IntVar(None, 1), 'Dnopanelviewhq': IntVar(None, 0),
'mangastyle': IntVar(None, 0), 'Eimage_preprocess': IntVar(None, 0),
'image_upscale': IntVar(None, 0), 'Fforcepng': IntVar(None, 0),
'image_stretch': IntVar(None, 0), 'Gimage_gamma': DoubleVar(None, 0.0),
'black_borders': IntVar(None, 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 = { self.optionlabels = {
'epub_only': "Generate ePub only (does not call 'kindlegen')", 'Aepub_only': "Generate EPUB only",
'image_preprocess': "Apply image optimizations", 'Bcbz_only': "Generate CBZ only (skip EPUB/Mobi generation)",
'rotate': "Rotate landscape images instead of splitting them", 'Cmangastyle': "Manga mode",
'cut_page_numbers': "Cut page numbers", 'Dnopanelviewhq': "Disable high quality Panel View",
'mangastyle': "Manga-style (right-to-left reading, applies to reading and splitting)", 'Eimage_preprocess': "Disable image optimizations",
'image_upscale': "Allow image upscaling", 'Fforcepng': "Create PNG files instead of JPEG",
'image_stretch': "Stretch images", 'Gimage_gamma': "Custom gamma correction",
'black_borders': "Use black borders" '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"
} }
for key in self.options: self.optionsButtons = {}
aCheckButton = Checkbutton(self.master, text=self.optionlabels[key], variable=self.options[key]) for key in sorted(self.options):
aCheckButton.grid(column=3, sticky='w') if isinstance(self.options[key], IntVar) or isinstance(self.options[key], BooleanVar):
self.progressbar = ttk.Progressbar(orient=HORIZONTAL, length=200, mode='determinate') 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="Execute!", command=self.start_conversion, fg="red") self.submit = Button(self.master, text="CONVERT", command=self.start_conversion, fg="red", state=DISABLED)
self.submit.grid(column=3) self.submit.grid(columnspan=4, sticky=W + E + N + S)
self.progressbar.grid(column=0, 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)
self.notelabel = Label(self.master, retcode = call("kindlegen", shell=True)
text="GUI can seem frozen while converting, kindly wait until some message appears!") if retcode == 1:
self.notelabel.grid(column=0, columnspan=4, sticky=W + E + N + S) self.optionsButtons['Aepub_only'].select()
self.optionsButtons['Aepub_only']['state'] = DISABLED
def start_conversion(self): def start_conversion(self):
self.progressbar.start() self.submit['state'] = DISABLED
self.master.update()
self.convert() self.convert()
self.progressbar.stop() self.submit['state'] = NORMAL
self.master.update()
def convert(self): def convert(self):
if len(self.filelist) < 1: if len(self.filelist) < 1:
tkMessageBox.showwarning('No file selected', "You should really select some files to convert...") tkMessageBox.showwarning('No files selected!', "Please choose files to convert.")
return return
profilekey = ProfileData.ProfileLabels[self.profile.get()] profilekey = ProfileData.ProfileLabels[self.profile.get()]
argv = ["-p", profilekey] argv = ["-p", profilekey]
if self.options['image_preprocess'].get() == 0: if self.options['Bcbz_only'].get() == 1:
argv.append("--no-image-processing") argv.append("-c")
if self.options['rotate'].get() == 1: if self.options['Cmangastyle'].get() == 1:
argv.append("--rotate")
if self.options['cut_page_numbers'].get() == 0:
argv.append("--no-cut-page-numbers")
if self.options['mangastyle'].get() == 1:
argv.append("-m") argv.append("-m")
if self.options['image_upscale'].get() == 1: if self.options['Dnopanelviewhq'].get() == 1:
argv.append("--upscale-images") argv.append("--nopanelviewhq")
if self.options['image_stretch'].get() == 1: if self.options['Eimage_preprocess'].get() == 1:
argv.append("--stretch-images") argv.append("--noprocessing")
if self.options['black_borders'].get() == 1: if self.options['Fforcepng'].get() == 1:
argv.append("--black-borders") 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 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: for entry in self.filelist:
self.progress_overall['value'] = filenum
self.progress_file['value'] = 1
self.master.update() self.master.update()
filenum += 1
subargv = list(argv) subargv = list(argv)
try: try:
subargv.append(entry) subargv.append(entry)
epub_path = comic2ebook.main(subargv) epub_path = comic2ebook.main(subargv)
except Exception, err: self.progress_file['value'] = 2
tkMessageBox.showerror('Error comic2ebook', "Error on file %s:\n%s" % (subargv[-1], str(err))) 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 errors = True
continue continue
if self.options['epub_only'] == 1: if self.options['Aepub_only'].get() == 0 and self.options['Bcbz_only'].get() == 0:
continue
try: 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) retcode = call("kindlegen \"" + epub_path + "\"", shell=True)
if retcode < 0: if retcode < 0:
print >>sys.stderr, "Child was terminated by signal", -retcode print >>sys.stderr, "Child was terminated by signal", -retcode
else: else:
print >>sys.stderr, "Child returned", retcode print >>sys.stderr, "Child returned", retcode
self.progress_file['value'] = 3
self.master.update()
except OSError as e: except OSError as e:
tkMessageBox.showerror('Error kindlegen', "Error on file %s:\n%s" % (epub_path, e)) tkMessageBox.showerror('KindleGen Error', "Error on file %s:\n%s" % (epub_path, e))
errors = True errors = True
continue continue
mobifile = epub_path.replace('.epub', '.mobi') mobifile = epub_path.replace('.epub', '.mobi')
@@ -172,20 +235,23 @@ class MainWindow:
shutil.move(mobifile, mobifile + '_tostrip') shutil.move(mobifile, mobifile + '_tostrip')
kindlestrip.main((mobifile + '_tostrip', mobifile)) kindlestrip.main((mobifile + '_tostrip', mobifile))
os.remove(mobifile + '_tostrip') os.remove(mobifile + '_tostrip')
self.progress_file['value'] = 4
self.master.update()
except Exception, err: except Exception, err:
tkMessageBox.showerror('Error', "Error on file %s:\n%s" % (mobifile, str(err))) tkMessageBox.showerror('KindleStrip Error', "Error on file %s:\n%s" % (mobifile, str(err)))
errors = True errors = True
continue continue
if errors:
tkMessageBox.showinfo(
"Done",
"Conversion finished (some errors have been reported)"
)
else: else:
tkMessageBox.showinfo( self.progress_file['value'] = 4
"Done", self.master.update()
"Conversion successfully done!" 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): def remove_readonly(self, fn, path):
if fn is os.rmdir: if fn is os.rmdir:
@@ -195,8 +261,7 @@ class MainWindow:
os.chmod(path, stat.S_IWRITE) os.chmod(path, stat.S_IWRITE)
os.remove(path) os.remove(path)
def __init__(self, master, title): def __init__(self, master):
self.filelist = [] self.filelist = []
self.master = master self.master = master
self.master.title(title)
self.initialize() self.initialize()

View File

@@ -20,7 +20,7 @@ __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os import os
from PIL import Image, ImageOps, ImageDraw, ImageStat from PIL import Image, ImageOps, ImageStat
class ImageFlags: class ImageFlags:
@@ -77,20 +77,22 @@ class ProfileData:
] ]
Profiles = { Profiles = {
'K1': ("Kindle", (600, 800), Palette4), 'K1': ("Kindle 1", (600, 800), Palette4, 1.8, (900, 1200)),
'K2': ("Kindle 2", (600, 800), Palette15), 'K2': ("Kindle 2", (600, 800), Palette15, 1.8, (900, 1200)),
'K3': ("Kindle 3/Keyboard", (600, 800), Palette16), 'K3': ("Kindle Keyboard", (600, 800), Palette16, 1.8, (900, 1200)),
'K4': ("Kindle 4/NT/Touch", (600, 800), Palette16), 'K4NT': ("Kindle Non-Touch", (600, 800), Palette16, 1.8, (900, 1200)),
'KHD': ("Kindle Paperwhite", (758, 1024), Palette16), 'K4T': ("Kindle Touch", (600, 800), Palette16, 1.8, (900, 1200)),
'KDX': ("Kindle DX", (824, 1200), Palette15), 'KHD': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)),
'KDXG': ("Kindle DXG", (824, 1200), Palette16) 'KDX': ("Kindle DX", (824, 1200), Palette15, 1.8, (1236, 1800)),
'KDXG': ("Kindle DXG", (824, 1200), Palette16, 1.8, (1236, 1800))
} }
ProfileLabels = { ProfileLabels = {
"Kindle": 'K1', "Kindle 1": 'K1',
"Kindle 2": 'K2', "Kindle 2": 'K2',
"Kindle 3/Keyboard": 'K3', "Kindle 3/Keyboard": 'K3',
"Kindle 4/NT/Touch": 'K4', "Kindle 4/Non-Touch": 'K4NT',
"Kindle 4/Touch": 'K4T',
"Kindle Paperwhite": 'KHD', "Kindle Paperwhite": 'KHD',
"Kindle DX": 'KDX', "Kindle DX": 'KDX',
"Kindle DXG": 'KDXG' "Kindle DXG": 'KDXG'
@@ -101,7 +103,7 @@ class ComicPage:
def __init__(self, source, device): def __init__(self, source, device):
try: try:
self.profile = device self.profile = device
self.profile_label, self.size, self.palette = ProfileData.Profiles[device] self.profile_label, self.size, self.palette, self.gamma, self.panelviewsize = ProfileData.Profiles[device]
except KeyError: except KeyError:
raise RuntimeError('Unexpected output device %s' % device) raise RuntimeError('Unexpected output device %s' % device)
try: try:
@@ -111,18 +113,29 @@ class ComicPage:
raise RuntimeError('Cannot read image file %s' % source) raise RuntimeError('Cannot read image file %s' % source)
self.image = self.image.convert('RGB') self.image = self.image.convert('RGB')
def saveToDir(self, targetdir): def saveToDir(self, targetdir, forcepng):
filename = os.path.basename(self.origFileName) filename = os.path.basename(self.origFileName)
try: try:
self.image = self.image.convert('L') # convert to grayscale self.image = self.image.convert('L') # convert to grayscale
self.image.save(os.path.join(targetdir, filename), "JPEG") 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: except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e)) raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
def optimizeImage(self): def optimizeImage(self, gamma):
if gamma < 0.1:
gamma = self.gamma
if gamma == 1.0:
self.image = ImageOps.autocontrast(self.image) self.image = ImageOps.autocontrast(self.image)
else:
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: 255 * (a / 255.) ** gamma))
def quantizeImage(self): 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 colors = len(self.palette) / 3
if colors < 256: if colors < 256:
self.palette += self.palette[:3] * (256 - colors) self.palette += self.palette[:3] * (256 - colors)
@@ -130,46 +143,48 @@ class ComicPage:
palImg.putpalette(self.palette) palImg.putpalette(self.palette)
self.image = self.image.quantize(palette=palImg) self.image = self.image.quantize(palette=palImg)
def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False): def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False,
landscapeMode=False, noPanelViewHQ=False):
method = Image.ANTIALIAS method = Image.ANTIALIAS
if black_borders: if black_borders:
fill = 'black' fill = 'black'
else: else:
fill = 'white' 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 self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
if not upscale: if not upscale:
if isSplit and (self.profile == 'K4' or self.profile == 'KHD'):
borderw = (self.size[0] - self.image.size[0])
borderh = (self.size[1] - self.image.size[1]) / 2
self.image = ImageOps.expand(self.image, border=(0, borderh), fill=fill)
tempImg = Image.new(self.image.mode, (self.image.size[0] + borderw, self.image.size[1]), fill)
if toRight:
tempImg.paste(self.image, (borderw, 0))
else:
tempImg.paste(self.image, (0, 0))
self.image = tempImg
else:
borderw = (self.size[0] - self.image.size[0]) / 2 borderw = (self.size[0] - self.image.size[0]) / 2
borderh = (self.size[1] - self.image.size[1]) / 2 borderh = (self.size[1] - self.image.size[1]) / 2
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill) self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill)
return self.image return self.image
else: else:
method = Image.NEAREST method = Image.BILINEAR
if stretch: # if stretching call directly resize() without other considerations. if stretch: # if stretching call directly resize() without other considerations.
self.image = self.image.resize(self.size, method) self.image = self.image.resize(size, method)
return self.image return self.image
ratioDev = float(self.size[0]) / float(self.size[1]) ratioDev = float(self.size[0]) / float(self.size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: 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] diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
if isSplit and (self.profile == 'K4' or self.profile == 'KHD'):
diff = 2
self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill) self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill)
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev: elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1] 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.expand(self.image, border=(0, diff / 2), fill=fill)
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5)) self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
return self.image return self.image
def splitPage(self, targetdir, righttoleft=False, rotate=False): def splitPage(self, targetdir, righttoleft=False, rotate=False):
@@ -209,29 +224,6 @@ class ComicPage:
else: else:
return None return None
def frameImage(self):
foreground = tuple(self.palette[:3])
background = tuple(self.palette[-3:])
widthDev, heightDev = self.size
widthImg, heightImg = self.image.size
pastePt = (
max(0, (widthDev - widthImg) / 2),
max(0, (heightDev - heightImg) / 2)
)
corner1 = (
pastePt[0] - 1,
pastePt[1] - 1
)
corner2 = (
pastePt[0] + widthImg + 1,
pastePt[1] + heightImg + 1
)
imageBg = Image.new(self.image.mode, self.size, background)
imageBg.paste(self.image, pastePt)
draw = ImageDraw.Draw(imageBg)
draw.rectangle([corner1, corner2], outline=foreground)
self.image = imageBg
def cutPageNumber(self): def cutPageNumber(self):
widthImg, heightImg = self.image.size widthImg, heightImg = self.image.size
delta = 2 delta = 2
@@ -324,37 +316,60 @@ class ComicPage:
# print "New size: %sx%s"%(self.image.size[0],self.image.size[1]) # print "New size: %sx%s"%(self.image.size[0],self.image.size[1])
return self.image return self.image
def addProgressbar(self, file_number, files_totalnumber, size, howoften): # def addProgressbar(self, file_number, files_totalnumber, size, howoften):
if file_number // howoften != float(file_number) / howoften: # if file_number // howoften != float(file_number) / howoften:
return self.image # return self.image
white = (255, 255, 255) # white = (255, 255, 255)
black = (0, 0, 0) # black = (0, 0, 0)
widthDev, heightDev = size # widthDev, heightDev = size
widthImg, heightImg = self.image.size # widthImg, heightImg = self.image.size
pastePt = ( # pastePt = (
max(0, (widthDev - widthImg) / 2), # max(0, (widthDev - widthImg) / 2),
max(0, (heightDev - heightImg) / 2) # max(0, (heightDev - heightImg) / 2)
) # )
imageBg = Image.new('RGB', size, white) # imageBg = Image.new('RGB', size, white)
imageBg.paste(self.image, pastePt) # imageBg.paste(self.image, pastePt)
self.image = imageBg # self.image = imageBg
widthImg, heightImg = self.image.size # widthImg, heightImg = self.image.size
draw = ImageDraw.Draw(self.image) # draw = ImageDraw.Draw(self.image)
#Black rectangle # #Black rectangle
draw.rectangle([(0, heightImg - 3), (widthImg, heightImg)], outline=black, fill=black) # draw.rectangle([(0, heightImg - 3), (widthImg, heightImg)], outline=black, fill=black)
#White rectangle # #White rectangle
draw.rectangle([(widthImg * file_number / files_totalnumber, heightImg - 3), (widthImg - 1, heightImg)], # draw.rectangle([(widthImg * file_number / files_totalnumber, heightImg - 3), (widthImg - 1, heightImg)],
outline=black, fill=white) # outline=black, fill=white)
#Making notches # #Making notches
for i in range(1, 10): # for i in range(1, 10):
if i <= (10 * file_number / files_totalnumber): # if i <= (10 * file_number / files_totalnumber):
notch_colour = white # White # notch_colour = white # White
else: # else:
notch_colour = black # Black # notch_colour = black # Black
draw.line([(widthImg * float(i) / 10, heightImg - 3), (widthImg * float(i) / 10, heightImg)], # draw.line([(widthImg * float(i) / 10, heightImg - 3), (widthImg * float(i) / 10, heightImg)],
fill=notch_colour) # fill=notch_colour)
#The 50% # #The 50%
if i == 5: # if i == 5:
draw.rectangle([(widthImg / 2 - 1, heightImg - 5), (widthImg / 2 + 1, heightImg)], # draw.rectangle([(widthImg / 2 - 1, heightImg - 5), (widthImg / 2 + 1, heightImg)],
outline=black, fill=notch_colour) # outline=black, fill=notch_colour)
return self.image # 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

View File

@@ -70,3 +70,4 @@ class PdfJpgExtract:
njpg += 1 njpg += 1
i = iend i = iend
return self.path

View File

@@ -1,6 +1,6 @@
# rarfile.py # rarfile.py
# #
# Copyright (c) 2005-2012 Marko Kreen <markokr@gmail.com> # Copyright (c) 2005-2013 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
@@ -17,7 +17,7 @@
r"""RAR archive reader. r"""RAR archive reader.
This is Python module for Rar archive reading. The interface This is Python module for Rar archive reading. The interface
is made as zipfile like as possible. is made as :mod:`zipfile`-like as possible.
Basic logic: Basic logic:
- Parse archive structure with Python. - Parse archive structure with Python.
@@ -34,7 +34,17 @@ Example::
for f in rf.infolist(): for f in rf.infolist():
print f.filename, f.file_size print f.filename, f.file_size
if f.filename == 'README': if f.filename == 'README':
print rf.read(f) print(rf.read(f))
Archive files can also be accessed via file-like object returned
by :meth:`RarFile.open`::
import rarfile
with rarfile.RarFile('archive.rar') as rf:
with rf.open('README') as f:
for ln in f:
print(ln.strip())
There are few module-level parameters to tune behaviour, There are few module-level parameters to tune behaviour,
here they are with defaults, and reason to change it:: here they are with defaults, and reason to change it::
@@ -64,7 +74,7 @@ For more details, refer to source.
""" """
__version__ = '2.5' __version__ = '2.6'
# export only interesting items # export only interesting items
__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile'] __all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
@@ -73,7 +83,7 @@ __all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
## Imports and compat - support both Python 2.x and 3.x ## Imports and compat - support both Python 2.x and 3.x
## ##
import sys, os, struct import sys, os, struct, errno
from struct import pack, unpack from struct import pack, unpack
from binascii import crc32 from binascii import crc32
from tempfile import mkstemp from tempfile import mkstemp
@@ -148,45 +158,45 @@ except ImportError:
## Module configuration. Can be tuned after importing. ## Module configuration. Can be tuned after importing.
## ##
# default fallback charset #: default fallback charset
DEFAULT_CHARSET = "windows-1252" DEFAULT_CHARSET = "windows-1252"
# list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed #: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed
TRY_ENCODINGS = ('utf8', 'utf-16le') TRY_ENCODINGS = ('utf8', 'utf-16le')
# 'unrar', 'rar' or full path to either one #: 'unrar', 'rar' or full path to either one
UNRAR_TOOL = "unrar" UNRAR_TOOL = "unrar"
# Command line args to use for opening file for reading. #: Command line args to use for opening file for reading.
OPEN_ARGS = ('p', '-inul') OPEN_ARGS = ('p', '-inul')
# Command line args to use for extracting file to disk. #: Command line args to use for extracting file to disk.
EXTRACT_ARGS = ('x', '-y', '-idq') EXTRACT_ARGS = ('x', '-y', '-idq')
# args for testrar() #: args for testrar()
TEST_ARGS = ('t', '-idq') TEST_ARGS = ('t', '-idq')
# 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 = 1
# 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
# whether to parse file/archive comments. #: whether to parse file/archive comments.
NEED_COMMENTS = 1 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 #: When RAR is corrupt, stopping on bad header is better
# On unknown/misparsed RAR headers reporting is better #: On unknown/misparsed RAR headers reporting is better
REPORT_BAD_HEADER = 0 REPORT_BAD_HEADER = 0
# Convert RAR time tuple into datetime() object #: Convert RAR time tuple into datetime() object
USE_DATETIME = 0 USE_DATETIME = 0
# Separator for path name components. RAR internally uses '\\'. #: Separator for path name components. RAR internally uses '\\'.
# Use '/' to be similar with zipfile. #: Use '/' to be similar with zipfile.
PATH_SEP = '\\' PATH_SEP = '\\'
## ##
@@ -320,6 +330,8 @@ class RarMemoryError(RarExecError):
"""Memory error""" """Memory error"""
class RarCreateError(RarExecError): class RarCreateError(RarExecError):
"""Create error""" """Create error"""
class RarNoFilesError(RarExecError):
"""No files that match pattern were found"""
class RarUserBreak(RarExecError): class RarUserBreak(RarExecError):
"""User stop""" """User stop"""
class RarUnknownError(RarExecError): class RarUnknownError(RarExecError):
@@ -335,49 +347,57 @@ def is_rarfile(fn):
class RarInfo(object): class RarInfo(object):
'''An entry in rar archive. r'''An entry in rar archive.
@ivar filename: :mod:`zipfile`-compatible fields:
filename
File name with relative path. File name with relative path.
Default path separator is '/', to change set rarfile.PATH_SEP. Default path separator is '\\', to change set rarfile.PATH_SEP.
Always unicode string. Always unicode string.
@ivar date_time: date_time
Modification time, tuple of (year, month, day, hour, minute, second). Modification time, tuple of (year, month, day, hour, minute, second).
Or datetime() object if USE_DATETIME is set. Or datetime() object if USE_DATETIME is set.
@ivar file_size: file_size
Uncompressed size. Uncompressed size.
@ivar compress_size: compress_size
Compressed size. Compressed size.
@ivar compress_type: CRC
Compression method: 0x30 - 0x35.
@ivar extract_version:
Minimal Rar version needed for decompressing.
@ivar host_os:
Host OS type, one of RAR_OS_* constants.
@ivar mode:
File attributes. May be either dos-style or unix-style, depending on host_os.
@ivar CRC:
CRC-32 of uncompressed file, unsigned int. CRC-32 of uncompressed file, unsigned int.
@ivar volume: comment
File comment. Byte string or None. Use UNICODE_COMMENTS
to get automatic decoding to unicode.
volume
Volume nr, starting from 0. Volume nr, starting from 0.
@ivar volume_file:
Volume file name, where file starts.
@ivar type:
One of RAR_BLOCK_* types. Only entries with type==RAR_BLOCK_FILE are shown in .infolist().
@ivar flags:
For files, RAR_FILE_* bits.
@ivar comment:
File comment (unicode string or None).
@ivar mtime: RAR-specific fields:
compress_type
Compression method: 0x30 - 0x35.
extract_version
Minimal Rar version needed for decompressing.
host_os
Host OS type, one of RAR_OS_* constants.
mode
File attributes. May be either dos-style or unix-style, depending on host_os.
volume_file
Volume file name, where file starts.
mtime
Optional time field: Modification time, with float seconds. Optional time field: Modification time, with float seconds.
Same as .date_time but with more precision. Same as .date_time but with more precision.
@ivar ctime: ctime
Optional time field: creation time, with float seconds. Optional time field: creation time, with float seconds.
@ivar atime: atime
Optional time field: last access time, with float seconds. Optional time field: last access time, with float seconds.
@ivar arctime: arctime
Optional time field: archival time, with float seconds. Optional time field: archival time, with float seconds.
Internal fields:
type
One of RAR_BLOCK_* types. Only entries with type==RAR_BLOCK_FILE are shown in .infolist().
flags
For files, RAR_FILE_* bits.
''' '''
__slots__ = ( __slots__ = (
@@ -431,19 +451,27 @@ class RarInfo(object):
class RarFile(object): class RarFile(object):
'''Parse RAR structure, provide access to files in archive. '''Parse RAR structure, provide access to files in archive.
@ivar comment:
Archive comment (unicode string or None).
''' '''
#: Archive comment. Byte string or None. Use UNICODE_COMMENTS
#: to get automatic decoding to unicode.
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):
"""Open and parse a RAR archive. """Open and parse a RAR archive.
@param rarfile: archive file name Parameters:
@param mode: only 'r' is supported.
@param charset: fallback charset to use, if filenames are not already Unicode-enabled. rarfile
@param info_callback: debug callback, gets to see all archive entries. archive file name
@param crc_check: set to False to disable CRC checks mode
only 'r' is supported.
charset
fallback charset to use, if filenames are not already Unicode-enabled.
info_callback
debug callback, gets to see all archive entries.
crc_check
set to False to disable CRC checks
""" """
self.rarfile = rarfile self.rarfile = rarfile
self.comment = None self.comment = None
@@ -455,6 +483,7 @@ class RarFile(object):
self._needs_password = False self._needs_password = False
self._password = None self._password = None
self._crc_check = crc_check self._crc_check = crc_check
self._vol_list = []
self._main = None self._main = None
@@ -487,6 +516,14 @@ class RarFile(object):
'''Return RarInfo objects for all files/directories in archive.''' '''Return RarInfo objects for all files/directories in archive.'''
return self._info_list return self._info_list
def volumelist(self):
'''Returns filenames of archive volumes.
In case of single-volume archive, the list contains
just the name of main archive file.
'''
return self._vol_list
def getinfo(self, fname): def getinfo(self, fname):
'''Return RarInfo for file.''' '''Return RarInfo for file.'''
@@ -508,7 +545,8 @@ class RarFile(object):
raise NoRarEntry("No such file: "+fname) raise NoRarEntry("No such file: "+fname)
def open(self, fname, mode = 'r', psw = None): def open(self, fname, mode = 'r', psw = None):
'''Return open file object, where the data can be read. '''Returns file-like object (:class:`RarExtFile`),
from where the data can be read.
The object implements io.RawIOBase interface, so it can The object implements io.RawIOBase interface, so it can
be further wrapped with io.BufferedReader and io.TextIOWrapper. be further wrapped with io.BufferedReader and io.TextIOWrapper.
@@ -520,9 +558,14 @@ class RarFile(object):
uncompressed files, on compressed files the seeking is implemented uncompressed files, on compressed files the seeking is implemented
by reading ahead and/or restarting the decompression. by reading ahead and/or restarting the decompression.
@param fname: file name or RarInfo instance. Parameters:
@param mode: must be 'r'
@param psw: password to use for extracting. fname
file name or RarInfo instance.
mode
must be 'r'
psw
password to use for extracting.
''' '''
if mode != 'r': if mode != 'r':
@@ -569,8 +612,12 @@ class RarFile(object):
For longer files using .open() may be better idea. For longer files using .open() may be better idea.
@param fname: filename or RarInfo instance Parameters:
@param psw: password to use for extracting.
fname
filename or RarInfo instance
psw
password to use for extracting.
""" """
f = self.open(fname, 'r', psw) f = self.open(fname, 'r', psw)
@@ -591,9 +638,14 @@ class RarFile(object):
def extract(self, member, path=None, pwd=None): def extract(self, member, path=None, pwd=None):
"""Extract single file into current directory. """Extract single file into current directory.
@param member: filename or RarInfo instance Parameters:
@param path: optional destination path
@param pwd: optional password to use member
filename or RarInfo instance
path
optional destination path
pwd
optional password to use
""" """
if isinstance(member, RarInfo): if isinstance(member, RarInfo):
fname = member.filename fname = member.filename
@@ -604,9 +656,14 @@ class RarFile(object):
def extractall(self, path=None, members=None, pwd=None): def extractall(self, path=None, members=None, pwd=None):
"""Extract all files into current directory. """Extract all files into current directory.
@param path: optional destination path Parameters:
@param members: optional filename or RarInfo instance list to extract
@param pwd: optional password to use path
optional destination path
members
optional filename or RarInfo instance list to extract
pwd
optional password to use
""" """
fnlist = [] fnlist = []
if members is not None: if members is not None:
@@ -691,6 +748,7 @@ class RarFile(object):
more_vols = 0 more_vols = 0
endarc = 0 endarc = 0
volfile = self.rarfile volfile = self.rarfile
self._vol_list = [self.rarfile]
while 1: while 1:
if endarc: if endarc:
h = None # don't read past ENDARC h = None # don't read past ENDARC
@@ -705,6 +763,7 @@ class RarFile(object):
self._fd = fd self._fd = fd
more_vols = 0 more_vols = 0
endarc = 0 endarc = 0
self._vol_list.append(volfile)
continue continue
break break
h.volume = volume h.volume = volume
@@ -1208,7 +1267,7 @@ class UnicodeFilename:
class RarExtFile(RawIOBase): class RarExtFile(RawIOBase):
"""Base class for 'file-like' object that RarFile.open() returns. """Base class for file-like object that :meth:`RarFile.open` returns.
Provides public methods and common crc checking. Provides public methods and common crc checking.
@@ -1216,13 +1275,15 @@ class RarExtFile(RawIOBase):
- no short reads - .read() and .readinfo() read as much as requested. - no short reads - .read() and .readinfo() read as much as requested.
- no internal buffer, use io.BufferedReader for that. - no internal buffer, use io.BufferedReader for that.
@ivar name: If :mod:`io` module is available (Python 2.6+, 3.x), then this calls
filename of the archive entry. will inherit from :class:`io.RawIOBase` class. This makes line-based
access available: :meth:`RarExtFile.readline` and ``for ln in f``.
""" """
def __init__(self, rf, inf): #: Filename of the archive entry
"""Fill common fields""" name = None
def __init__(self, rf, inf):
RawIOBase.__init__(self) RawIOBase.__init__(self)
# standard io.* properties # standard io.* properties
@@ -1323,7 +1384,13 @@ class RarExtFile(RawIOBase):
return self.inf.file_size - self.remain return self.inf.file_size - self.remain
def seek(self, ofs, whence = 0): def seek(self, ofs, whence = 0):
"""Seek in data.""" """Seek in data.
On uncompressed files, the seeking works by actual
seeks so it's fast. On compresses files its slow
- forward seeking happends by reading ahead,
backwards by re-opening and decompressing from the start.
"""
# disable crc check when seeking # disable crc check when seeking
self.crc_check = 0 self.crc_check = 0
@@ -1372,8 +1439,17 @@ class RarExtFile(RawIOBase):
"""Returns True""" """Returns True"""
return True return True
def writable(self):
"""Returns False.
Writing is not supported."""
return False
def seekable(self): def seekable(self):
"""Returns True""" """Returns True.
Seeking is supported, although it's slow on compressed files.
"""
return True return True
def readall(self): def readall(self):
@@ -1757,8 +1833,15 @@ def custom_popen(cmd):
creationflags = 0x08000000 # CREATE_NO_WINDOW creationflags = 0x08000000 # CREATE_NO_WINDOW
# run command # run command
p = Popen(cmd, bufsize = 0, stdout = PIPE, stdin = PIPE, stderr = STDOUT, try:
p = Popen(cmd, bufsize = 0,
stdout = PIPE, stdin = PIPE, stderr = STDOUT,
creationflags = creationflags) creationflags = creationflags)
except OSError:
ex = sys.exc_info()[1]
if ex.errno == errno.ENOENT:
raise RarExecError("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
raise
return p return p
def check_returncode(p, out): def check_returncode(p, out):
@@ -1772,7 +1855,7 @@ def check_returncode(p, out):
errmap = [None, errmap = [None,
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError, RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
RarWriteError, RarOpenError, RarUserError, RarMemoryError, RarWriteError, RarOpenError, RarUserError, RarMemoryError,
RarCreateError] # codes from rar.txt RarCreateError, RarNoFilesError] # codes from rar.txt
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:

View File

@@ -1,65 +0,0 @@
<?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>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>*</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>****</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>droplet</string>
<key>CFBundleGetInfoString</key>
<string>KindleComicConverter 2.0, Written 2012 by Ciro Mattia Gonano</string>
<key>CFBundleIconFile</key>
<string>droplet</string>
<key>CFBundleIdentifier</key>
<string>com.github.ciromattia.kcc</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>KindleComicConverter 1.20</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>dplt</string>
<key>LSMinimumSystemVersionByArchitecture</key>
<dict>
<key>x86_64</key>
<string>10.6</string>
</dict>
<key>LSRequiresCarbon</key>
<true/>
<key>WindowState</key>
<dict>
<key>dividerCollapsed</key>
<true/>
<key>eventLogLevel</key>
<integer>-1</integer>
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>568</real>
<key>savedFrame</key>
<string>144 338 889 690 0 0 1680 1028 </string>
<key>selectedTabView</key>
<string>event log</string>
</dict>
</dict>
</plist>

Binary file not shown.

View File

@@ -1,22 +0,0 @@
{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340
{\fonttbl\f0\fnil\fcharset0 Verdana;}
{\colortbl;\red255\green255\blue255;\red76\green78\blue78;}
\pard\tx576\tx1152\tx1728\tx2304\tx2880\tx3456\tx4032\tx4608\tx5184\tx5760\tx6337\tx6913\tx7489\tx8065\tx8641\tx9217\tx9793\tx10369\tx10945\tx11521\tx12097\tx12674\tx13250\tx13826\tx14402\tx14978\tx15554\tx16130\tx16706\tx17282\tx17858\tx18435\tx19011\tx19587\tx20163\tx20739\tx21315\tx21891\tx22467\tx23043\tx23619\tx24195\tx24772\tx25348\tx25924\tx26500\tx27076\tx27652\tx28228\tx28804\tx29380\tx29956\tx30532\tx31109\tx31685\tx32261\tx32837\tx33413\tx33989\tx34565\tx35141\tx35717\tx36293\tx36870\tx37446\tx38022\tx38598\tx39174\tx39750\tx40326\tx40902\tx41478\tx42054\tx42630\tx43207\tx43783\tx44359\tx44935\tx45511\tx46087\tx46663\tx47239\tx47815\tx48391\tx48967\tx49544\tx50120\tx50696\tx51272\tx51848\tx52424\tx53000\tx53576\tx54152\tx54728\tx55305\tx55881\tx56457\tx57033\tx57609\li785\fi-786\pardirnatural
\f0\fs24 \cf2 \CocoaLigature0 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.\
\
This script heavily relies on KindleStrip (C) by Paul Durrant and released in public domain (http://www.mobileread.com/forums/showthread.php?t=96903)\
Also, you need to have kindlegen v2.7 (with KF8 support) which is downloadable from Amazon website.\
\
Changelog:\
1.0: first release\
1.10: add CBZ/CBR support to comic2ebook.py\
1.11: add CBZ/CBR support to KindleComicConverter\
1.2: added image page splitting and optimizations\
\
Todo:\
- bundle a script to manipulate images (to get rid of Mangle/E-nki/whatsoever)}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

View File

@@ -1,33 +1,36 @@
""" """
py2app/py2exe build script for MyApplication. py2app/cx_Freeze build script for KCC.
Will automatically ensure that all build prerequisites are available Will automatically ensure that all build prerequisites are available via ez_setup
via ez_setup
Usage (Mac OS X): Usage (Mac OS X):
python setup.py py2app python setup.py py2app
Usage (Windows): Usage (Windows):
python setup.py py2exe python setup.py build
""" """
import ez_setup from ez_setup import use_setuptools
ez_setup.use_setuptools() use_setuptools()
import sys import sys
NAME = "KindleComicConverter"
VERSION = "2.9"
MAIN = "kcc.py"
includefiles = ['README.md', 'MANIFEST.in', 'LICENSE.txt', 'comic2ebook.ico', 'comic2ebook.icns']
includes = []
excludes = []
if sys.platform == "darwin":
from setuptools import setup from setuptools import setup
NAME = 'KindleComicConverter'
VERSION = "2.6"
mainscript = 'kcc.py'
if sys.platform == 'darwin':
extra_options = dict( extra_options = dict(
setup_requires=['py2app'], setup_requires=['py2app'],
app=[mainscript], app=[MAIN],
options=dict( options=dict(
py2app=dict( py2app=dict(
argv_emulation=True, argv_emulation=True,
iconfile='resources/comic2ebook.icns', iconfile='comic2ebook.icns',
plist=dict( plist=dict(
CFBundleName=NAME, CFBundleName=NAME,
CFBundleShortVersionString=VERSION, CFBundleShortVersionString=VERSION,
@@ -43,13 +46,17 @@ elif sys.platform == 'win32':
from cx_Freeze import setup, Executable from cx_Freeze import setup, Executable
base = "Win32GUI" base = "Win32GUI"
extra_options = dict( extra_options = dict(
executables=[Executable("kcc.py", base=base)] 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: else:
extra_options = dict( extra_options = dict(
# Normally unix-like platforms will use "setup.py install" scripts=[MAIN],
# and install the main script as such
scripts=[mainscript],
) )
setup( setup(
@@ -71,12 +78,10 @@ setup(
'Intended Audience :: End Users/Desktop', 'Intended Audience :: End Users/Desktop',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 2.7',
'Topic :: Multimedia :: Graphics :: Graphics Conversion', 'Topic :: Multimedia :: Graphics :: Graphics Conversion',
'Topic :: Utilities' 'Topic :: Utilities'
], ],
packages=['kcc'], packages=['kcc'],
# make sure to add custom_fixers to the MANIFEST.in
include_package_data=True,
**extra_options **extra_options
) )

22
setup_console.py Normal file
View File

@@ -0,0 +1,22 @@
"""
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)]
)

View File

@@ -1,86 +0,0 @@
"""
py2app/py2exe build script for MyApplication.
Will automatically ensure that all build prerequisites are available
via ez_setup
Usage (Mac OS X):
python setup_cx.py bdist_dmg
Usage (Windows):
python setup_cx.py bdist_msi
"""
import sys
from cx_Freeze import setup, Executable
NAME='KindleComicConverter'
VERSION="2.4"
mainscript = 'kcc.py'
base = None
if sys.platform == 'darwin':
extra_options = dict(
bundle-iconfile='resources/comic2ebook.icns',
volume-label=NAME + " " + VERSION
app=[mainscript],
options=dict(
py2app=dict(
argv_emulation=True,
iconfile='resources/comic2ebook.icns',
plist=dict(
CFBundleName = NAME,
CFBundleShortVersionString = VERSION,
CFBundleGetInfoString = NAME + " " + VERSION + ", written 2012-2013 by Ciro Mattia Gonano",
CFBundleExecutable = NAME,
CFBundleIdentifier = 'com.github.ciromattia.kcc',
CFBundleSignature = 'dplt'
)
)
)
)
elif sys.platform == 'win32':
base = "Win32GUI"
extra_options = dict(
upgrade-code = 'KindleComicConverter',
executables = [Executable("kcc.py", base=base)]
)
else:
extra_options = dict(
# Normally unix-like platforms will use "setup.py install"
# and install the main script as such
scripts=[mainscript],
)
options = dict(
)
setup(
name=NAME,
version=VERSION,
author="Ciro Mattia Gonano",
author_email="ciromattia@gmail.com",
description=("A tool to convert comics (CBR/CBZ/PDFs/image folders) to Mobipocket."),
license = "ISC License (ISCL)",
keywords = "kindle comic mobipocket mobi cbz cbr manga",
url = "http://github.com/ciromattia/kcc",
classifiers=[
'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 :: 3',
'Topic :: Multimedia :: Graphics :: Graphics Conversion',
'Topic :: Utilities'
],
# make sure to add custom_fixers to the MANIFEST.in
include_package_data=True,
executables = [Executable("kcc.py", base=base)]),
**extra_options
)