diff --git a/.gitignore b/.gitignore index 24db16d..14d4c97 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist kindlegen* .DS_Store Thumbs.db -UnRAR.exe +/UnRAR.exe +/7za.exe diff --git a/KCC-Linux.ui b/KCC-Linux.ui new file mode 100644 index 0000000..40d408b --- /dev/null +++ b/KCC-Linux.ui @@ -0,0 +1,812 @@ + + + KCC + + + + 0 + 0 + 420 + 380 + + + + + 420 + 380 + + + + + 420 + 380 + + + + + 9 + + + + Qt::NoFocus + + + Kindle Comic Converter + + + + :/Icon/icons/comic2ebook.png:/Icon/icons/comic2ebook.png + + + + + + + + true + + + + 1 + 254 + 421 + 61 + + + + + DejaVu Sans + 9 + + + + + 9 + + + + + + DejaVu Sans + + + + Qt::NoFocus + + + Disable image optimizations. + + + No optimisation + + + + + + + + DejaVu Sans + + + + Qt::NoFocus + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> + + + Stretch/Upscale + + + true + + + + + + + + DejaVu Sans + + + + Qt::NoFocus + + + <html><head/><body><p>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html> + + + Webtoon mode + + + + + + + + DejaVu Sans + + + + Qt::NoFocus + + + <html><head/><body><p>Create PNG files instead JPEG.</p></body></html> + + + PNG output + + + + + + + + DejaVu Sans + + + + Qt::NoFocus + + + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> + + + W/B margins + + + true + + + + + + + + DejaVu Sans + + + + Qt::NoFocus + + + <html><head/><body><p>Disable splitting and rotation.</p></body></html> + + + No split/rotate + + + + + + + + + 10 + 200 + 141 + 31 + + + + + DejaVu Sans + 8 + + + + Qt::NoFocus + + + Target device. + + + + + + 260 + 200 + 151 + 31 + + + + + DejaVu Sans + 8 + + + + Qt::NoFocus + + + Output format. + + + + + + 160 + 200 + 91 + 32 + + + + + DejaVu Sans + 9 + 75 + true + + + + Qt::NoFocus + + + Convert + + + + :/Other/icons/convert.png:/Other/icons/convert.png + + + + + + 10 + 160 + 141 + 32 + + + + + DejaVu Sans + 8 + + + + Qt::NoFocus + + + Add directory + + + + :/Other/icons/folder_new.png:/Other/icons/folder_new.png + + + + + + 260 + 160 + 151 + 32 + + + + + DejaVu Sans + 8 + + + + Qt::NoFocus + + + Add file + + + + :/Other/icons/document_new.png:/Other/icons/document_new.png + + + + + + 160 + 160 + 91 + 32 + + + + + DejaVu Sans + 8 + + + + Qt::NoFocus + + + Clear list + + + + :/Other/icons/clear.png:/Other/icons/clear.png + + + + + + 1 + 230 + 421 + 41 + + + + + DejaVu Sans + 9 + + + + + + 9 + 10 + 130 + 18 + + + + + DejaVu Sans + + + + Qt::NoFocus + + + Enable right-to-left reading. + + + Manga mode + + + + + + 282 + 10 + 135 + 18 + + + + + DejaVu Sans + + + + Qt::NoFocus + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Use it when Panel View support is not needed.</span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></span><span style=" font-family:'MS Shell Dlg 2';">- Maximum quality when zoom is not enabled.<br />- Poor quality when zoom is enabled.<br />- Lowest file size.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Not zoomed image </span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; font-style:italic;">might </span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">be a little blurry.</span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></span><span style=" font-family:'MS Shell Dlg 2';">- Medium/High quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br /></span><span style=" font-family:'MS Shell Dlg 2'; font-style:italic;">Maximum possible quality.</span><span style=" font-family:'MS Shell Dlg 2'; font-weight:600; text-decoration: underline;"><br /></span><span style=" font-family:'MS Shell Dlg 2';">- Maximum quality when zoom is not enabled.<br />- Maximum quality when zoom is enabled.<br />- Very high file size.</span></p></body></html> + + + High/Ultra quality + + + true + + + + + + 145 + 10 + 130 + 18 + + + + + DejaVu Sans + + + + Qt::NoFocus + + + <html><head/><body><p>Disable page spliting.<br/>They will be rotated instead.</p></body></html> + + + Horizontal mode + + + RotateBox + MangaBox + QualityBox + + + + + 10 + 50 + 401 + 101 + + + + + DejaVu Sans + 8 + false + + + + Qt::NoFocus + + + false + + + QAbstractItemView::NoSelection + + + + 18 + 18 + + + + + + + 10 + 10 + 195 + 32 + + + + + DejaVu Sans + 9 + + + + Qt::NoFocus + + + Basic + + + + + + 217 + 10 + 195 + 32 + + + + + DejaVu Sans + 9 + + + + Qt::NoFocus + + + Advanced + + + + + true + + + + 10 + 305 + 401 + 41 + + + + + DejaVu Sans + 9 + + + + + + 15 + 0 + 100 + 40 + + + + + DejaVu Sans + + + + <html><head/><body><p>When converting color images setting this option to 1.0 <span style=" font-weight:600;">might</span> improve readability.</p></body></html> + + + Gamma: Auto + + + + + + 110 + 10 + 275 + 22 + + + + + DejaVu Sans + + + + Qt::ClickFocus + + + <html><head/><body><p>When converting color images setting this option to 1.0 <span style=" font-weight:600;">might</span> improve readability.</p></body></html> + + + 500 + + + 5 + + + Qt::Horizontal + + + + + + + 10 + 10 + 401 + 31 + + + + + DejaVu Sans + 10 + 75 + true + + + + 0 + + + Qt::AlignJustify|Qt::AlignVCenter + + + + + + + + + 1 + 337 + 421 + 41 + + + + + DejaVu Sans + 9 + + + + + + 9 + 11 + 130 + 18 + + + + + DejaVu Sans + + + + Qt::NoFocus + + + Do not convert images to grayscale. + + + Color mode + + + + + + 105 + 0 + 295 + 40 + + + + + DejaVu Sans + + + + + + + + DejaVu Sans + + + + Resolution of target device. + + + Custom width: + + + + + + + + 0 + 0 + + + + + 40 + 16777215 + + + + + DejaVu Sans + + + + Qt::ClickFocus + + + false + + + Resolution of target device. + + + 0000; + + + 4 + + + + + + + + DejaVu Sans + + + + Resolution of target device. + + + Custom height: + + + + + + + + 0 + 0 + + + + + 40 + 16777215 + + + + + DejaVu Sans + + + + Qt::ClickFocus + + + false + + + Resolution of target device. + + + 0000; + + + 4 + + + + + + + OptionsAdvanced + DeviceBox + FormatBox + ConvertButton + DirectoryButton + FileButton + ClearButton + OptionsBasic + JobList + BasicModeButton + AdvModeButton + OptionsAdvancedGamma + OptionsExpert + ProgressBar + + + + true + + + false + + + Basic + + + + + + + + true + + + Advanced + + + + + DirectoryButton + FileButton + ConvertButton + ClearButton + + + + + + diff --git a/KCC-OSX.ui b/KCC-OSX.ui index d1e7289..2e57eb7 100644 --- a/KCC-OSX.ui +++ b/KCC-OSX.ui @@ -47,7 +47,7 @@ - 9 + 4 253 421 61 @@ -55,6 +55,7 @@ + Lucida Grande 9 @@ -63,7 +64,8 @@ - 11 + Lucida Grande + 12 @@ -81,14 +83,15 @@ - 11 + Lucida Grande + 12 Qt::NoFocus - <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html> + <html><head/><body><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span><span style=" font-size:12pt;">Images smaller than device resolution will not be resized.</span></p><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span><span style=" font-size:12pt;">Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</span></p><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span><span style=" font-size:12pt;">Images smaller than device resolution will be resized. Aspect ratio will be preserved.</span></p></body></html> Stretch/Upscale @@ -102,14 +105,15 @@ - 11 + Lucida Grande + 12 Qt::NoFocus - <html><head/><body><p><span style=" font-weight:600;">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html> + <html><head/><body><p><span style=" font-size:12pt;">Enable auto-splitting of webtoons like </span><span style=" font-size:12pt; font-style:italic;">Tower of God</span><span style=" font-size:12pt;"> or </span><span style=" font-size:12pt; font-style:italic;">Noblesse</span><span style=" font-size:12pt;">.<br/>Pages with a low width, high height and vertical panel flow.</span></p></body></html> Webtoon mode @@ -120,14 +124,15 @@ - 11 + Lucida Grande + 12 Qt::NoFocus - <html><head/><body><p><span style=" font-size:12pt;">Create PNG files instead JPEG.<br/></span><span style=" font-size:12pt; font-weight:600;">Only for non-Kindle devices!</span></p></body></html> + <html><head/><body><p><span style=" font-size:12pt;">Create PNG files instead JPEG.</span></p></body></html> PNG output @@ -138,17 +143,21 @@ - 11 + Lucida Grande + 12 Qt::NoFocus - <html><head/><body><p><span style=" font-size:12pt;">Fill space around images with black color.</span></p></body></html> + <html><head/><body><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span><span style=" font-size:12pt;">Color of margins fill will be detected automatically.</span></p><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span><span style=" font-size:12pt;">Margins will be filled with white color.</span></p><p><span style=" font-size:12pt; font-weight:600; text-decoration: underline;">Checked - Black<br/></span><span style=" font-size:12pt;">Margins will be filled with black color.</span></p></body></html> - Black borders + W/B margins + + + true @@ -156,7 +165,8 @@ - 11 + Lucida Grande + 12 @@ -183,6 +193,7 @@ + Lucida Grande 11 @@ -190,7 +201,7 @@ Qt::NoFocus - Target device. + <html><head/><body><p><span style=" font-size:12pt;">Target device.</span></p></body></html> @@ -204,6 +215,7 @@ + Lucida Grande 11 @@ -211,7 +223,7 @@ Qt::NoFocus - Output format. + <html><head/><body><p><span style=" font-size:12pt;">Output format.</span></p></body></html> @@ -225,6 +237,7 @@ + Lucida Grande 11 75 true @@ -252,6 +265,7 @@ + Lucida Grande 11 @@ -277,6 +291,7 @@ + Lucida Grande 11 @@ -302,6 +317,7 @@ + Lucida Grande 11 @@ -319,7 +335,7 @@ - 10 + 5 233 421 41 @@ -327,7 +343,8 @@ - 9 + Lucida Grande + 12 @@ -341,7 +358,8 @@ - 11 + Lucida Grande + 12 @@ -359,20 +377,21 @@ 282 10 - 130 + 135 18 - 11 + Lucida Grande + 12 Qt::NoFocus - <html><head/><body><p><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-style:italic;">Use it when Panel View support is not needed.</span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600; text-decoration: underline;"><br/></span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt;">- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</span></p><p><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-style:italic;">Not zoomed image </span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600; font-style:italic;">might </span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-style:italic;">be a little blurry.</span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600; text-decoration: underline;"><br/></span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt;">- Medium/High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</span></p><p><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-style:italic;">Maximum possible quality.</span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600; text-decoration: underline;"><br/></span><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt;">- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</span></p></body></html> + <html><head/><body><p><span style="font-size:12pt; font-weight:600; text-decoration: underline;">Unchecked - Normal quality mode<br/></span><span style="font-size:12pt; font-style:italic;">Use it when Panel View support is not needed.</span><span style="font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style="font-size:12pt;">- Maximum quality when zoom is not enabled.<br/>- Poor quality when zoom is enabled.<br/>- Lowest file size.</span></p><p><span style="font-size:12pt; font-weight:600; text-decoration: underline;">Indeterminate - High quality mode<br/></span><span style="font-size:12pt; font-style:italic;">Not zoomed image </span><span style="font-size:12pt; font-weight:600; font-style:italic;">might </span><span style="font-size:12pt; font-style:italic;">be a little blurry.</span><span style="font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style="font-size:12pt;">- Medium/High quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.</span></p><p><span style="font-size:12pt; font-weight:600; text-decoration: underline;">Checked - Ultra quality mode<br/></span><span style="font-size:12pt; font-style:italic;">Maximum possible quality.</span><span style="font-size:12pt; font-weight:600; text-decoration: underline;"><br/></span><span style="font-size:12pt;">- Maximum quality when zoom is not enabled.<br/>- Maximum quality when zoom is enabled.<br/>- Very high file size.</span></p></body></html> High/Ultra quality @@ -392,7 +411,8 @@ - 11 + Lucida Grande + 12 @@ -420,6 +440,7 @@ + Lucida Grande 11 @@ -444,6 +465,7 @@ + Lucida Grande 12 50 false @@ -467,6 +489,7 @@ + Lucida Grande 12 50 false @@ -485,7 +508,7 @@ - 10 + 5 303 401 41 @@ -493,6 +516,7 @@ + Lucida Grande 9 @@ -507,13 +531,14 @@ + Lucida Grande 12 50 false - <html><head/><body><p><span style=" font-size:12pt;">When converting color images setting this option to 1.0 MIGHT improve readability.</span></p></body></html> + <html><head/><body><p><span style=" font-size:12pt;">When converting color images setting this option to 1.0 </span><span style=" font-size:12pt; font-weight:600;">might</span><span style=" font-size:12pt;"> improve readability.</span></p></body></html> Gamma: Auto @@ -524,10 +549,15 @@ 110 10 - 280 + 290 22 + + + Lucida Grande + + Qt::ClickFocus @@ -556,6 +586,7 @@ + Lucida Grande 10 75 true @@ -577,7 +608,7 @@ - 10 + 5 335 421 41 @@ -585,6 +616,7 @@ + Lucida Grande 9 @@ -599,7 +631,8 @@ - 11 + Lucida Grande + 12 @@ -615,17 +648,23 @@ - 90 + 95 0 315 40 + + + Lucida Grande + + + Lucida Grande 12 50 false @@ -655,6 +694,7 @@ + Lucida Grande 12 @@ -679,6 +719,7 @@ + Lucida Grande 12 50 false @@ -708,6 +749,7 @@ + Lucida Grande 12 diff --git a/KCC.ui b/KCC.ui index 5387341..9d90feb 100644 --- a/KCC.ui +++ b/KCC.ui @@ -97,7 +97,7 @@ Qt::NoFocus - <html><head/><body><p><span style=" font-weight:600;">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html> + <html><head/><body><p>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html> Webtoon mode @@ -110,7 +110,7 @@ Qt::NoFocus - <html><head/><body><p>Create PNG files instead JPEG.<br/><span style=" font-weight:600;">Only for non-Kindle devices!</span></p></body></html> + <html><head/><body><p>Create PNG files instead JPEG.</p></body></html> PNG output @@ -123,10 +123,13 @@ Qt::NoFocus - Fill space around images with black color. + <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html> - Black borders + W/B margins + + + true diff --git a/README.md b/README.md index 57dbb5f..795ebd2 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,13 @@ You can find the latest released binary at the following links: - Folders - CBZ, ZIP - CBR, RAR *(With `unrar` executable)* +- CB7, 7Z *(With `7za` executable)* - PDF *(Extracting only contained JPG images)* ## OPTIONAL REQUIREMENTS - [KindleGen](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211) v2.9+ in a directory reachable by your _PATH_ or in _KCC_ directory *(For .mobi generation)* - [UnRAR](http://www.rarlab.com/download.htm) *(For CBR/RAR support)* +- [7za](http://www.7-zip.org/download.html) *(For 7z/CB7 support)* ### 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. @@ -52,7 +54,7 @@ You can find the latest released binary at the following links: * Check our [wiki](https://github.com/ciromattia/kcc/wiki/Other-devices) for a list of tested Non-Kindle E-Readers. * The first image found will be set as the comic's cover. * All files/directories will be added to EPUB in alphabetical order. -* Output MOBI file should be uploaded via USB. Other methods (e.g. via Calibre) might corrupt it. +* Output MOBI file should be uploaded via USB. Other methods might corrupt it. ### GUI @@ -67,12 +69,10 @@ Usage: comic2ebook.py [options] comic_file|comic_folder Options: MAIN: -p PROFILE, --profile=PROFILE - Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8, KFA) [Default=KHD] + Device profile (Choose one among K1, K2, K345, KDX, KDXG, KHD, KF, KFHD, KFHD8, KFHDX, KFHDX8, KFA) [Default=KHD] -q QUALITY, --quality=QUALITY Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0] -m, --manga-style Manga style (Right-to-left reading and splitting) - - EXPERIMENTAL: -w, --webtoon Webtoon processing mode OUTPUT SETTINGS: @@ -84,7 +84,8 @@ Options: --batchsplit Split output into multiple files PROCESSING: - --blackborders Use black borders instead of white ones + --blackborders Disable autodetection and force black borders + --whiteborders Disable autodetection and force white borders --forcecolor Don't convert images to grayscale --forcepng Create PNG files instead JPEG (For non-Kindle devices) --gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto] @@ -129,22 +130,20 @@ This script born as a cross-platform alternative to `KindleComicParser` by **Dc5 The app relies and includes the following scripts/binaries: - - `KindleStrip` script © 2010-2012 by **Paul Durrant** and released in public domain -([forum thread](http://www.mobileread.com/forums/showthread.php?t=96903)) - - `rarfile.py` script © 2005-2011 **Marko Kreen** , released with ISC 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 - - Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License + - `KindleUnpack` script by Charles **M. Hannum, P. Durrant, K. Hendricks, S. Siebert, fandrieu, DiapDealer, nickredding**. Released with GPLv3 License. + - `rarfile.py` script © 2005-2011 **Marko Kreen** . Released with ISC 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. + - Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License. ## SAMPLE FILES CREATED BY KCC -* [Kindle Keyboard](http://kcc.vulturis.eu/Samples/Ubunchu!-K3.mobi) -* [Kindle DX](http://kcc.vulturis.eu/Samples/Ubunchu!-KDX.mobi) -* [Kindle DXG](http://kcc.vulturis.eu/Samples/Ubunchu!-KDXG.mobi) -* [Kindle Non-Touch](http://kcc.vulturis.eu/Samples/Ubunchu!-K4NT.mobi) -* [Kindle Touch](http://kcc.vulturis.eu/Samples/Ubunchu!-K4T.mobi) * [Kindle Paperwhite](http://kcc.vulturis.eu/Samples/Ubunchu!-KPW.mobi) +* [Kindle](http://kcc.vulturis.eu/Samples/Ubunchu!-K345.mobi) +* [Kindle DX/DXG](http://kcc.vulturis.eu/Samples/Ubunchu!-KDX.mobi) * [Kindle Fire](http://kcc.vulturis.eu/Samples/Ubunchu!-KF.mobi) * [Kindle Fire HD](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHD.mobi) * [Kindle Fire HD 8.9"](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHD8.mobi) +* [Kindle Fire HDX](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHDX.mobi) +* [Kindle Fire HDX 8.9"](http://kcc.vulturis.eu/Samples/Ubunchu!-KFHDX8.mobi) ## CHANGELOG ####1.00 @@ -255,8 +254,19 @@ The app relies and includes the following scripts/binaries: ####3.2.1: * Hotfixed crash occurring on OS with Russian locale -## KNOWN ISSUES -* Removing SRCS headers sometimes fail in 32bit enviroments. Due to memory limitations. +####3.3: +* Margins are now automatically omitted in Panel View mode +* Margin color fill is now autodetected +* Created MOBI files are not longer marked as _Personal_ on newer Kindle models +* Layout of panels in Panel View mode is now automatically adjusted to content +* Fixed Kindle 2/DX/DXG profiles - no more blank pages +* All Kindle Fire profiles now support hiqh quality Panel View +* Added support of 7z/CB7 files +* Added Kindle Fire HDX profile +* Support for Virtual Panel View was removed +* Profiles for Kindle Keyboard, Touch and Non-Touch are now merged +* Windows release is now bundled with UnRAR and 7za +* Small GUI tweaks ## COPYRIGHT diff --git a/kcc.py b/kcc.py index bcab374..60a0646 100644 --- a/kcc.py +++ b/kcc.py @@ -1,7 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (c) 2012 Ciro Mattia Gonano +# Copyright (c) 2012-2013 Ciro Mattia Gonano +# Copyright (c) 2013 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the @@ -17,7 +18,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -__version__ = '3.2.1' +__version__ = '3.3' __license__ = 'ISC' __copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jastrzebski ' __docformat__ = 'restructuredtext en' @@ -32,9 +33,10 @@ except ImportError: exit(1) from kcc import KCC_gui from multiprocessing import freeze_support - -if sys.platform == 'darwin': +if sys.platform.startswith('darwin'): from kcc import KCC_ui_osx as KCC_ui +elif sys.platform.startswith('linux'): + from kcc import KCC_ui_linux as KCC_ui else: from kcc import KCC_ui diff --git a/kcc/KCC_gui.py b/kcc/KCC_gui.py index e86e93e..4b6e022 100644 --- a/kcc/KCC_gui.py +++ b/kcc/KCC_gui.py @@ -1,7 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (c) 2013 Ciro Mattia Gonano +# Copyright (c) 2012-2013 Ciro Mattia Gonano +# Copyright (c) 2013 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the @@ -17,7 +18,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -__version__ = '3.2.1' +__version__ = '3.3' __license__ = 'ISC' __copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jastrzebski ' __docformat__ = 'restructuredtext en' @@ -29,7 +30,7 @@ import traceback import urllib2 import time import comic2ebook -import kindlestrip +import kindlesplit from image import ProfileData from subprocess import call, Popen, STDOUT, PIPE from PyQt4 import QtGui, QtCore @@ -98,6 +99,10 @@ class WorkerThread(QtCore.QThread): def __init__(self, parent): QtCore.QThread.__init__(self) self.parent = parent + self.conversionAlive = False + self.errors = False + self.kindlegenErrorCode = 0 + self.kindlegenError = None def __del__(self): self.wait() @@ -111,7 +116,6 @@ class WorkerThread(QtCore.QThread): self.emit(QtCore.SIGNAL("addMessage"), 'Conversion interrupted.', 'error') self.emit(QtCore.SIGNAL("modeConvert"), True) - # noinspection PyUnboundLocalVariable def run(self): self.emit(QtCore.SIGNAL("modeConvert"), False) profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())] @@ -130,12 +134,14 @@ class WorkerThread(QtCore.QThread): argv.append("--noprocessing") if GUI.NoRotateBox.isChecked(): argv.append("--nosplitrotate") - if GUI.BorderBox.isChecked(): - argv.append("--blackborders") if GUI.UpscaleBox.checkState() == 1: argv.append("--stretch") elif GUI.UpscaleBox.checkState() == 2: argv.append("--upscale") + if GUI.BorderBox.checkState() == 1: + argv.append("--whiteborders") + elif GUI.BorderBox.checkState() == 2: + argv.append("--blackborders") if GUI.NoDitheringBox.isChecked(): argv.append("--forcepng") if GUI.WebtoonBox.isChecked(): @@ -209,7 +215,8 @@ class WorkerThread(QtCore.QThread): try: self.kindlegenErrorCode = 0 if os.path.getsize(item) < 367001600: - output = Popen('kindlegen -locale en "' + item + '"', stdout=PIPE, stderr=STDOUT, shell=True) + output = Popen('kindlegen -locale en "' + item + '"', stdout=PIPE, stderr=STDOUT, + shell=True) for line in output.stdout: # ERROR: Generic error if "Error(" in line: @@ -242,23 +249,27 @@ class WorkerThread(QtCore.QThread): True) else: self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file... Done!', 'info', True) - self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header...', 'info') + self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI file...', 'info') os.remove(item) mobiPath = item.replace('.epub', '.mobi') - shutil.move(mobiPath, mobiPath + '_tostrip') + shutil.move(mobiPath, mobiPath + '_toclean') try: - kindlestrip.main((mobiPath + '_tostrip', mobiPath)) + if profile in ['K345', 'KHD', 'KF', 'KFHD', 'KFHD8', 'KFHDX', 'KFHDX8', 'KFA']: + newKindle = True + else: + newKindle = False + mobisplit = kindlesplit.mobi_split(mobiPath + '_toclean', newKindle) + open(mobiPath, 'wb').write(mobisplit.getResult()) except Exception: self.errors = True if not self.errors: - os.remove(mobiPath + '_tostrip') - self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header... Done!', 'info', True) + os.remove(mobiPath + '_toclean') + self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI file... Done!', 'info', True) else: - shutil.move(mobiPath + '_tostrip', mobiPath) + os.remove(mobiPath + '_toclean') + os.remove(mobiPath) self.emit(QtCore.SIGNAL("addMessage"), - 'KindleStrip failed to remove SRCS header!', 'warning') - self.emit(QtCore.SIGNAL("addMessage"), - 'MOBI file will work correctly but it will be highly oversized.', 'warning') + 'KindleUnpack failed to clean MOBI file!', 'error') else: epubSize = (os.path.getsize(item))/1024/1024 os.remove(item) @@ -318,11 +329,19 @@ class Ui_KCC(object): self.needClean = False GUI.JobList.clear() if self.UnRAR: - fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath, - '*.cbz *.cbr *.zip *.rar *.pdf') + if self.sevenza: + fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath, + '*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf') + else: + fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath, + '*.cbz *.cbr *.zip *.rar *.pdf') else: - fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath, - '*.cbz *.zip *.pdf') + if self.sevenza: + fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath, + '*.cbz *.cb7 *.zip *.7z *.pdf') + else: + fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath, + '*.cbz *.zip *.pdf') # Lame UTF-8 security measure for fname in fnames: try: @@ -438,13 +457,13 @@ class Ui_KCC(object): GUI.NoRotateBox.setChecked(True) GUI.QualityBox.setEnabled(False) GUI.QualityBox.setChecked(False) - GUI.BorderBox.setEnabled(False) - GUI.BorderBox.setChecked(False) + GUI.MangaBox.setEnabled(False) + GUI.MangaBox.setChecked(False) self.addMessage('If images are color setting Gamma to 1.0 is recommended.', 'info') else: GUI.NoRotateBox.setEnabled(True) GUI.QualityBox.setEnabled(True) - GUI.BorderBox.setEnabled(True) + GUI.MangaBox.setEnabled(True) def toggleNoSplitRotate(self, value): if value: @@ -454,13 +473,13 @@ class Ui_KCC(object): GUI.RotateBox.setEnabled(True) def changeDevice(self, value): - if value == 12: + if value == 9: GUI.BasicModeButton.setEnabled(False) GUI.AdvModeButton.setEnabled(False) self.addMessage('' 'List of supported Non-Kindle devices', 'info') self.modeExpert() - elif value == 11: + elif value == 8: GUI.BasicModeButton.setEnabled(False) GUI.AdvModeButton.setEnabled(False) self.modeExpert(True) @@ -468,11 +487,17 @@ class Ui_KCC(object): GUI.BasicModeButton.setEnabled(True) GUI.AdvModeButton.setEnabled(True) self.modeBasic() - if value in [0, 1, 5, 6, 7, 8, 9, 12]: + if value in [9, 11, 12, 13, 14]: GUI.QualityBox.setCheckState(0) GUI.QualityBox.setEnabled(False) else: - GUI.QualityBox.setEnabled(True) + if not GUI.WebtoonBox.isChecked(): + GUI.QualityBox.setEnabled(True) + if value in [3, 4, 5, 6, 8, 15]: + GUI.NoDitheringBox.setCheckState(0) + GUI.NoDitheringBox.setEnabled(False) + else: + GUI.NoDitheringBox.setEnabled(True) def stripTags(self, html): s = HTMLStripper() @@ -544,10 +569,12 @@ class Ui_KCC(object): event.ignore() if not GUI.ConvertButton.isEnabled(): event.ignore() + self.settings.setValue('settingsVersion', __version__) self.settings.setValue('lastPath', self.lastPath) self.settings.setValue('lastDevice', GUI.DeviceBox.currentIndex()) self.settings.setValue('currentFormat', GUI.FormatBox.currentIndex()) self.settings.setValue('currentMode', self.currentMode) + self.settings.setValue('firstStart', False) self.settings.setValue('options', QtCore.QVariant({'MangaBox': GUI.MangaBox.checkState(), 'RotateBox': GUI.RotateBox.checkState(), 'QualityBox': GUI.QualityBox.checkState(), @@ -567,22 +594,33 @@ class Ui_KCC(object): global GUI, MainWindow GUI = UI MainWindow = KCC - profiles = sorted(ProfileData.ProfileLabels.iterkeys()) + # User settings will be reverted to default ones if were created in one of the following versions + # Empty string cover all versions before this system was implemented + purgeSettingsVersions = [''] self.icons = Icons() self.settings = QtCore.QSettings('KindleComicConverter', 'KindleComicConverter') + self.settingsVersion = self.settings.value('settingsVersion', '', type=str) + if self.settingsVersion in purgeSettingsVersions: + QtCore.QSettings.clear(self.settings) + self.settingsVersion = self.settings.value('settingsVersion', '', type=str) self.lastPath = self.settings.value('lastPath', '', type=str) - self.lastDevice = self.settings.value('lastDevice', 10, type=int) + self.lastDevice = self.settings.value('lastDevice', 0, type=int) self.currentMode = self.settings.value('currentMode', 1, type=int) self.currentFormat = self.settings.value('currentFormat', 0, type=int) + self.firstStart = self.settings.value('firstStart', True, type=bool) self.options = self.settings.value('options', QtCore.QVariant({'GammaSlider': 0})) self.options = self.options.toPyObject() self.worker = WorkerThread(self) self.versionCheck = VersionThread(self) self.conversionAlive = False self.needClean = True + self.GammaValue = 1.0 self.addMessage('Welcome!', 'info') self.addMessage('Remember: All options have additional informations in tooltips.', 'info') + if self.firstStart: + self.addMessage('Since you are using KCC for first time please see few ' + 'important tips.', 'info') if call('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) == 0: self.KindleGen = True formats = ['MOBI', 'EPUB', 'CBZ'] @@ -608,6 +646,13 @@ class Ui_KCC(object): self.UnRAR = False self.addMessage('Cannot find UnRAR!' ' Processing of CBR/RAR files will be disabled.', 'warning') + sevenzaExitCode = call('7za', stdout=PIPE, stderr=STDOUT, shell=True) + if sevenzaExitCode == 0 or sevenzaExitCode == 7: + self.sevenza = True + else: + self.sevenza = False + self.addMessage('Cannot find 7za!' + ' Processing of CB7/7Z files will be disabled.', 'warning') GUI.BasicModeButton.clicked.connect(self.modeBasic) GUI.AdvModeButton.clicked.connect(self.modeAdvanced) @@ -627,12 +672,18 @@ class Ui_KCC(object): KCC.connect(self.versionCheck, QtCore.SIGNAL("addMessage"), self.addMessage) KCC.closeEvent = self.saveSettings - for profile in profiles: - if profile != "Other": - GUI.DeviceBox.addItem(self.icons.deviceKindle, profile) - else: + for profile in ProfileData.ProfileLabelsGUI: + if profile == "Other": GUI.DeviceBox.addItem(self.icons.deviceOther, profile) - GUI.DeviceBox.setCurrentIndex(self.lastDevice) + elif profile == "Separator": + GUI.DeviceBox.insertSeparator(GUI.DeviceBox.count()+1) + else: + GUI.DeviceBox.addItem(self.icons.deviceKindle, profile) + if self.lastDevice > GUI.DeviceBox.count(): + GUI.DeviceBox.setCurrentIndex(0) + self.lastDevice = 0 + else: + GUI.DeviceBox.setCurrentIndex(self.lastDevice) for f in formats: GUI.FormatBox.addItem(eval('self.icons.' + f + 'Format'), f) @@ -650,8 +701,6 @@ class Ui_KCC(object): elif str(option) == "GammaSlider": GUI.GammaSlider.setValue(int(self.options[option])) self.changeGamma(int(self.options[option])) - elif str(option) == "StretchBox" or str(option) == "WebstripBox": - pass else: eval('GUI.' + str(option)).setCheckState(self.options[option]) if self.currentMode == 1: diff --git a/kcc/KCC_ui.py b/kcc/KCC_ui.py index 757b2a8..a997ead 100644 --- a/kcc/KCC_ui.py +++ b/kcc/KCC_ui.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'KCC.ui' # -# Created: Wed Aug 14 08:39:46 2013 -# by: PyQt4 UI code generator 4.10.2 +# Created: Wed Sep 18 12:12:45 2013 +# by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -68,6 +68,7 @@ class Ui_KCC(object): self.gridLayout.addWidget(self.NoDitheringBox, 3, 2, 1, 1) self.BorderBox = QtGui.QCheckBox(self.OptionsAdvanced) self.BorderBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.BorderBox.setTristate(True) self.BorderBox.setObjectName(_fromUtf8("BorderBox")) self.gridLayout.addWidget(self.BorderBox, 3, 0, 1, 1) self.NoRotateBox = QtGui.QCheckBox(self.OptionsAdvanced) @@ -266,12 +267,12 @@ class Ui_KCC(object): self.ProcessingBox.setText(_translate("KCC", "No optimisation", None)) self.UpscaleBox.setToolTip(_translate("KCC", "

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None)) - self.WebtoonBox.setToolTip(_translate("KCC", "

EXPERIMENTAL!
Enable auto-splitting of webtoons like Tower of God or Noblesse.
Pages with a low width, high height and vertical panel flow.

", None)) + self.WebtoonBox.setToolTip(_translate("KCC", "

Enable auto-splitting of webtoons like Tower of God or Noblesse.
Pages with a low width, high height and vertical panel flow.

", None)) self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None)) - self.NoDitheringBox.setToolTip(_translate("KCC", "

Create PNG files instead JPEG.
Only for non-Kindle devices!

", None)) + self.NoDitheringBox.setToolTip(_translate("KCC", "

Create PNG files instead JPEG.

", None)) self.NoDitheringBox.setText(_translate("KCC", "PNG output", None)) - self.BorderBox.setToolTip(_translate("KCC", "Fill space around images with black color.", None)) - self.BorderBox.setText(_translate("KCC", "Black borders", None)) + self.BorderBox.setToolTip(_translate("KCC", "

Unchecked - Autodetection
Color of margins fill will be detected automatically.

Indeterminate - White
Margins will be filled with white color.

Checked - Black
Margins will be filled with black color.

", None)) + self.BorderBox.setText(_translate("KCC", "W/B margins", None)) self.NoRotateBox.setToolTip(_translate("KCC", "

Disable splitting and rotation.

", None)) self.NoRotateBox.setText(_translate("KCC", "No split/rotate", None)) self.DeviceBox.setToolTip(_translate("KCC", "Target device.", None)) diff --git a/kcc/KCC_ui_linux.py b/kcc/KCC_ui_linux.py new file mode 100644 index 0000000..416bb16 --- /dev/null +++ b/kcc/KCC_ui_linux.py @@ -0,0 +1,383 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'KCC-Linux.ui' +# +# Created: Fri Sep 20 10:25:30 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_KCC(object): + def setupUi(self, KCC): + KCC.setObjectName(_fromUtf8("KCC")) + KCC.resize(420, 380) + KCC.setMinimumSize(QtCore.QSize(420, 380)) + KCC.setMaximumSize(QtCore.QSize(420, 380)) + font = QtGui.QFont() + font.setPointSize(9) + KCC.setFont(font) + KCC.setFocusPolicy(QtCore.Qt.NoFocus) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/Icon/icons/comic2ebook.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + KCC.setWindowIcon(icon) + KCC.setLocale(QtCore.QLocale(QtCore.QLocale.C, QtCore.QLocale.AnyCountry)) + self.Form = QtGui.QWidget(KCC) + self.Form.setObjectName(_fromUtf8("Form")) + self.OptionsAdvanced = QtGui.QFrame(self.Form) + self.OptionsAdvanced.setEnabled(True) + self.OptionsAdvanced.setGeometry(QtCore.QRect(1, 254, 421, 61)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(9) + self.OptionsAdvanced.setFont(font) + self.OptionsAdvanced.setObjectName(_fromUtf8("OptionsAdvanced")) + self.gridLayout = QtGui.QGridLayout(self.OptionsAdvanced) + self.gridLayout.setContentsMargins(9, -1, -1, -1) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.ProcessingBox = QtGui.QCheckBox(self.OptionsAdvanced) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.ProcessingBox.setFont(font) + self.ProcessingBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.ProcessingBox.setObjectName(_fromUtf8("ProcessingBox")) + self.gridLayout.addWidget(self.ProcessingBox, 1, 0, 1, 1) + self.UpscaleBox = QtGui.QCheckBox(self.OptionsAdvanced) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.UpscaleBox.setFont(font) + self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.UpscaleBox.setTristate(True) + self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox")) + self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1) + self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.WebtoonBox.setFont(font) + self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox")) + self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1) + self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.NoDitheringBox.setFont(font) + self.NoDitheringBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.NoDitheringBox.setObjectName(_fromUtf8("NoDitheringBox")) + self.gridLayout.addWidget(self.NoDitheringBox, 3, 2, 1, 1) + self.BorderBox = QtGui.QCheckBox(self.OptionsAdvanced) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.BorderBox.setFont(font) + self.BorderBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.BorderBox.setTristate(True) + self.BorderBox.setObjectName(_fromUtf8("BorderBox")) + self.gridLayout.addWidget(self.BorderBox, 3, 0, 1, 1) + self.NoRotateBox = QtGui.QCheckBox(self.OptionsAdvanced) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.NoRotateBox.setFont(font) + self.NoRotateBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.NoRotateBox.setObjectName(_fromUtf8("NoRotateBox")) + self.gridLayout.addWidget(self.NoRotateBox, 1, 2, 1, 1) + self.DeviceBox = QtGui.QComboBox(self.Form) + self.DeviceBox.setGeometry(QtCore.QRect(10, 200, 141, 31)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(8) + self.DeviceBox.setFont(font) + self.DeviceBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.DeviceBox.setObjectName(_fromUtf8("DeviceBox")) + self.FormatBox = QtGui.QComboBox(self.Form) + self.FormatBox.setGeometry(QtCore.QRect(260, 200, 151, 31)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(8) + self.FormatBox.setFont(font) + self.FormatBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.FormatBox.setObjectName(_fromUtf8("FormatBox")) + self.ConvertButton = QtGui.QPushButton(self.Form) + self.ConvertButton.setGeometry(QtCore.QRect(160, 200, 91, 32)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(9) + font.setBold(True) + font.setWeight(75) + self.ConvertButton.setFont(font) + self.ConvertButton.setFocusPolicy(QtCore.Qt.NoFocus) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/Other/icons/convert.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.ConvertButton.setIcon(icon1) + self.ConvertButton.setObjectName(_fromUtf8("ConvertButton")) + self.DirectoryButton = QtGui.QPushButton(self.Form) + self.DirectoryButton.setGeometry(QtCore.QRect(10, 160, 141, 32)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(8) + self.DirectoryButton.setFont(font) + self.DirectoryButton.setFocusPolicy(QtCore.Qt.NoFocus) + icon2 = QtGui.QIcon() + icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/Other/icons/folder_new.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.DirectoryButton.setIcon(icon2) + self.DirectoryButton.setObjectName(_fromUtf8("DirectoryButton")) + self.FileButton = QtGui.QPushButton(self.Form) + self.FileButton.setGeometry(QtCore.QRect(260, 160, 151, 32)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(8) + self.FileButton.setFont(font) + self.FileButton.setFocusPolicy(QtCore.Qt.NoFocus) + icon3 = QtGui.QIcon() + icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/Other/icons/document_new.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.FileButton.setIcon(icon3) + self.FileButton.setObjectName(_fromUtf8("FileButton")) + self.ClearButton = QtGui.QPushButton(self.Form) + self.ClearButton.setGeometry(QtCore.QRect(160, 160, 91, 32)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(8) + self.ClearButton.setFont(font) + self.ClearButton.setFocusPolicy(QtCore.Qt.NoFocus) + icon4 = QtGui.QIcon() + icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/Other/icons/clear.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.ClearButton.setIcon(icon4) + self.ClearButton.setObjectName(_fromUtf8("ClearButton")) + self.OptionsBasic = QtGui.QFrame(self.Form) + self.OptionsBasic.setGeometry(QtCore.QRect(1, 230, 421, 41)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(9) + self.OptionsBasic.setFont(font) + self.OptionsBasic.setObjectName(_fromUtf8("OptionsBasic")) + self.MangaBox = QtGui.QCheckBox(self.OptionsBasic) + self.MangaBox.setGeometry(QtCore.QRect(9, 10, 130, 18)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.MangaBox.setFont(font) + self.MangaBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.MangaBox.setObjectName(_fromUtf8("MangaBox")) + self.QualityBox = QtGui.QCheckBox(self.OptionsBasic) + self.QualityBox.setGeometry(QtCore.QRect(282, 10, 135, 18)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.QualityBox.setFont(font) + self.QualityBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.QualityBox.setTristate(True) + self.QualityBox.setObjectName(_fromUtf8("QualityBox")) + self.RotateBox = QtGui.QCheckBox(self.OptionsBasic) + self.RotateBox.setGeometry(QtCore.QRect(145, 10, 130, 18)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.RotateBox.setFont(font) + self.RotateBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.RotateBox.setObjectName(_fromUtf8("RotateBox")) + self.JobList = QtGui.QListWidget(self.Form) + self.JobList.setGeometry(QtCore.QRect(10, 50, 401, 101)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(8) + font.setItalic(False) + self.JobList.setFont(font) + self.JobList.setFocusPolicy(QtCore.Qt.NoFocus) + self.JobList.setProperty("showDropIndicator", False) + self.JobList.setSelectionMode(QtGui.QAbstractItemView.NoSelection) + self.JobList.setIconSize(QtCore.QSize(18, 18)) + self.JobList.setObjectName(_fromUtf8("JobList")) + self.BasicModeButton = QtGui.QPushButton(self.Form) + self.BasicModeButton.setGeometry(QtCore.QRect(10, 10, 195, 32)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(9) + self.BasicModeButton.setFont(font) + self.BasicModeButton.setFocusPolicy(QtCore.Qt.NoFocus) + self.BasicModeButton.setObjectName(_fromUtf8("BasicModeButton")) + self.AdvModeButton = QtGui.QPushButton(self.Form) + self.AdvModeButton.setGeometry(QtCore.QRect(217, 10, 195, 32)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(9) + self.AdvModeButton.setFont(font) + self.AdvModeButton.setFocusPolicy(QtCore.Qt.NoFocus) + self.AdvModeButton.setObjectName(_fromUtf8("AdvModeButton")) + self.OptionsAdvancedGamma = QtGui.QFrame(self.Form) + self.OptionsAdvancedGamma.setEnabled(True) + self.OptionsAdvancedGamma.setGeometry(QtCore.QRect(10, 305, 401, 41)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(9) + self.OptionsAdvancedGamma.setFont(font) + self.OptionsAdvancedGamma.setObjectName(_fromUtf8("OptionsAdvancedGamma")) + self.GammaLabel = QtGui.QLabel(self.OptionsAdvancedGamma) + self.GammaLabel.setGeometry(QtCore.QRect(15, 0, 100, 40)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.GammaLabel.setFont(font) + self.GammaLabel.setObjectName(_fromUtf8("GammaLabel")) + self.GammaSlider = QtGui.QSlider(self.OptionsAdvancedGamma) + self.GammaSlider.setGeometry(QtCore.QRect(110, 10, 275, 22)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.GammaSlider.setFont(font) + self.GammaSlider.setFocusPolicy(QtCore.Qt.ClickFocus) + self.GammaSlider.setMaximum(500) + self.GammaSlider.setSingleStep(5) + self.GammaSlider.setOrientation(QtCore.Qt.Horizontal) + self.GammaSlider.setObjectName(_fromUtf8("GammaSlider")) + self.ProgressBar = QtGui.QProgressBar(self.Form) + self.ProgressBar.setGeometry(QtCore.QRect(10, 10, 401, 31)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(10) + font.setBold(True) + font.setWeight(75) + self.ProgressBar.setFont(font) + self.ProgressBar.setProperty("value", 0) + self.ProgressBar.setAlignment(QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) + self.ProgressBar.setFormat(_fromUtf8("")) + self.ProgressBar.setObjectName(_fromUtf8("ProgressBar")) + self.OptionsExpert = QtGui.QFrame(self.Form) + self.OptionsExpert.setGeometry(QtCore.QRect(1, 337, 421, 41)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + font.setPointSize(9) + self.OptionsExpert.setFont(font) + self.OptionsExpert.setObjectName(_fromUtf8("OptionsExpert")) + self.ColorBox = QtGui.QCheckBox(self.OptionsExpert) + self.ColorBox.setGeometry(QtCore.QRect(9, 11, 130, 18)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.ColorBox.setFont(font) + self.ColorBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.ColorBox.setObjectName(_fromUtf8("ColorBox")) + self.OptionsExpertInternal = QtGui.QFrame(self.OptionsExpert) + self.OptionsExpertInternal.setGeometry(QtCore.QRect(105, 0, 295, 40)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.OptionsExpertInternal.setFont(font) + self.OptionsExpertInternal.setObjectName(_fromUtf8("OptionsExpertInternal")) + self.gridLayout_2 = QtGui.QGridLayout(self.OptionsExpertInternal) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.wLabel = QtGui.QLabel(self.OptionsExpertInternal) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.wLabel.setFont(font) + self.wLabel.setObjectName(_fromUtf8("wLabel")) + self.gridLayout_2.addWidget(self.wLabel, 0, 0, 1, 1) + self.customWidth = QtGui.QLineEdit(self.OptionsExpertInternal) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.customWidth.sizePolicy().hasHeightForWidth()) + self.customWidth.setSizePolicy(sizePolicy) + self.customWidth.setMaximumSize(QtCore.QSize(40, 16777215)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.customWidth.setFont(font) + self.customWidth.setFocusPolicy(QtCore.Qt.ClickFocus) + self.customWidth.setAcceptDrops(False) + self.customWidth.setMaxLength(4) + self.customWidth.setObjectName(_fromUtf8("customWidth")) + self.gridLayout_2.addWidget(self.customWidth, 0, 1, 1, 1) + self.hLabel = QtGui.QLabel(self.OptionsExpertInternal) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.hLabel.setFont(font) + self.hLabel.setObjectName(_fromUtf8("hLabel")) + self.gridLayout_2.addWidget(self.hLabel, 0, 2, 1, 1) + self.customHeight = QtGui.QLineEdit(self.OptionsExpertInternal) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.customHeight.sizePolicy().hasHeightForWidth()) + self.customHeight.setSizePolicy(sizePolicy) + self.customHeight.setMaximumSize(QtCore.QSize(40, 16777215)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("DejaVu Sans")) + self.customHeight.setFont(font) + self.customHeight.setFocusPolicy(QtCore.Qt.ClickFocus) + self.customHeight.setAcceptDrops(False) + self.customHeight.setMaxLength(4) + self.customHeight.setObjectName(_fromUtf8("customHeight")) + self.gridLayout_2.addWidget(self.customHeight, 0, 3, 1, 1) + KCC.setCentralWidget(self.Form) + self.ActionBasic = QtGui.QAction(KCC) + self.ActionBasic.setCheckable(True) + self.ActionBasic.setChecked(False) + font = QtGui.QFont() + self.ActionBasic.setFont(font) + self.ActionBasic.setObjectName(_fromUtf8("ActionBasic")) + self.ActionAdvanced = QtGui.QAction(KCC) + self.ActionAdvanced.setCheckable(True) + self.ActionAdvanced.setObjectName(_fromUtf8("ActionAdvanced")) + + self.retranslateUi(KCC) + QtCore.QMetaObject.connectSlotsByName(KCC) + KCC.setTabOrder(self.DirectoryButton, self.FileButton) + KCC.setTabOrder(self.FileButton, self.ConvertButton) + KCC.setTabOrder(self.ConvertButton, self.ClearButton) + + def retranslateUi(self, KCC): + KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None)) + self.ProcessingBox.setToolTip(_translate("KCC", "Disable image optimizations.", None)) + self.ProcessingBox.setText(_translate("KCC", "No optimisation", None)) + self.UpscaleBox.setToolTip(_translate("KCC", "

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) + self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None)) + self.WebtoonBox.setToolTip(_translate("KCC", "

Enable auto-splitting of webtoons like Tower of God or Noblesse.
Pages with a low width, high height and vertical panel flow.

", None)) + self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None)) + self.NoDitheringBox.setToolTip(_translate("KCC", "

Create PNG files instead JPEG.

", None)) + self.NoDitheringBox.setText(_translate("KCC", "PNG output", None)) + self.BorderBox.setToolTip(_translate("KCC", "

Unchecked - Autodetection
Color of margins fill will be detected automatically.

Indeterminate - White
Margins will be filled with white color.

Checked - Black
Margins will be filled with black color.

", None)) + self.BorderBox.setText(_translate("KCC", "W/B margins", None)) + self.NoRotateBox.setToolTip(_translate("KCC", "

Disable splitting and rotation.

", None)) + self.NoRotateBox.setText(_translate("KCC", "No split/rotate", None)) + self.DeviceBox.setToolTip(_translate("KCC", "Target device.", None)) + self.FormatBox.setToolTip(_translate("KCC", "Output format.", None)) + self.ConvertButton.setText(_translate("KCC", "Convert", None)) + self.DirectoryButton.setText(_translate("KCC", "Add directory", None)) + self.FileButton.setText(_translate("KCC", "Add file", None)) + self.ClearButton.setText(_translate("KCC", "Clear list", None)) + self.MangaBox.setToolTip(_translate("KCC", "Enable right-to-left reading.", None)) + self.MangaBox.setText(_translate("KCC", "Manga mode", None)) + self.QualityBox.setToolTip(_translate("KCC", "\n" +"\n" +"

Unchecked - Normal quality mode
Use it when Panel View support is not needed.
- Maximum quality when zoom is not enabled.
- Poor quality when zoom is enabled.
- Lowest file size.

\n" +"

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

\n" +"

Checked - Ultra quality mode
Maximum possible quality.
- Maximum quality when zoom is not enabled.
- Maximum quality when zoom is enabled.
- Very high file size.

", None)) + self.QualityBox.setText(_translate("KCC", "High/Ultra quality", None)) + self.RotateBox.setToolTip(_translate("KCC", "

Disable page spliting.
They will be rotated instead.

", None)) + self.RotateBox.setText(_translate("KCC", "Horizontal mode", None)) + self.BasicModeButton.setText(_translate("KCC", "Basic", None)) + self.AdvModeButton.setText(_translate("KCC", "Advanced", None)) + self.GammaLabel.setToolTip(_translate("KCC", "

When converting color images setting this option to 1.0 might improve readability.

", None)) + self.GammaLabel.setText(_translate("KCC", "Gamma: Auto", None)) + self.GammaSlider.setToolTip(_translate("KCC", "

When converting color images setting this option to 1.0 might improve readability.

", None)) + self.ColorBox.setToolTip(_translate("KCC", "Do not convert images to grayscale.", None)) + self.ColorBox.setText(_translate("KCC", "Color mode", None)) + self.wLabel.setToolTip(_translate("KCC", "Resolution of target device.", None)) + self.wLabel.setText(_translate("KCC", "Custom width: ", None)) + self.customWidth.setToolTip(_translate("KCC", "Resolution of target device.", None)) + self.customWidth.setInputMask(_translate("KCC", "0000; ", None)) + self.hLabel.setToolTip(_translate("KCC", "Resolution of target device.", None)) + self.hLabel.setText(_translate("KCC", "Custom height: ", None)) + self.customHeight.setToolTip(_translate("KCC", "Resolution of target device.", None)) + self.customHeight.setInputMask(_translate("KCC", "0000; ", None)) + self.ActionBasic.setText(_translate("KCC", "Basic", None)) + self.ActionAdvanced.setText(_translate("KCC", "Advanced", None)) + +import KCC_rc diff --git a/kcc/KCC_ui_osx.py b/kcc/KCC_ui_osx.py index 997a35b..5813d40 100644 --- a/kcc/KCC_ui_osx.py +++ b/kcc/KCC_ui_osx.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'KCC-OSX.ui' # -# Created: Wed Aug 14 08:39:45 2013 +# Created: Fri Sep 20 10:57:31 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -41,8 +41,9 @@ class Ui_KCC(object): self.Form.setObjectName(_fromUtf8("Form")) self.OptionsAdvanced = QtGui.QFrame(self.Form) self.OptionsAdvanced.setEnabled(True) - self.OptionsAdvanced.setGeometry(QtCore.QRect(9, 253, 421, 61)) + self.OptionsAdvanced.setGeometry(QtCore.QRect(4, 253, 421, 61)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(9) self.OptionsAdvanced.setFont(font) self.OptionsAdvanced.setObjectName(_fromUtf8("OptionsAdvanced")) @@ -50,14 +51,16 @@ class Ui_KCC(object): self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.ProcessingBox = QtGui.QCheckBox(self.OptionsAdvanced) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.ProcessingBox.setFont(font) self.ProcessingBox.setFocusPolicy(QtCore.Qt.NoFocus) self.ProcessingBox.setObjectName(_fromUtf8("ProcessingBox")) self.gridLayout.addWidget(self.ProcessingBox, 1, 0, 1, 1) self.UpscaleBox = QtGui.QCheckBox(self.OptionsAdvanced) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.UpscaleBox.setFont(font) self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus) self.UpscaleBox.setTristate(True) @@ -65,28 +68,33 @@ class Ui_KCC(object): self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1) self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.WebtoonBox.setFont(font) self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus) self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox")) self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1) self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.NoDitheringBox.setFont(font) self.NoDitheringBox.setFocusPolicy(QtCore.Qt.NoFocus) self.NoDitheringBox.setObjectName(_fromUtf8("NoDitheringBox")) self.gridLayout.addWidget(self.NoDitheringBox, 3, 2, 1, 1) self.BorderBox = QtGui.QCheckBox(self.OptionsAdvanced) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.BorderBox.setFont(font) self.BorderBox.setFocusPolicy(QtCore.Qt.NoFocus) + self.BorderBox.setTristate(True) self.BorderBox.setObjectName(_fromUtf8("BorderBox")) self.gridLayout.addWidget(self.BorderBox, 3, 0, 1, 1) self.NoRotateBox = QtGui.QCheckBox(self.OptionsAdvanced) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.NoRotateBox.setFont(font) self.NoRotateBox.setFocusPolicy(QtCore.Qt.NoFocus) self.NoRotateBox.setObjectName(_fromUtf8("NoRotateBox")) @@ -94,6 +102,7 @@ class Ui_KCC(object): self.DeviceBox = QtGui.QComboBox(self.Form) self.DeviceBox.setGeometry(QtCore.QRect(8, 200, 151, 34)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(11) self.DeviceBox.setFont(font) self.DeviceBox.setFocusPolicy(QtCore.Qt.NoFocus) @@ -101,6 +110,7 @@ class Ui_KCC(object): self.FormatBox = QtGui.QComboBox(self.Form) self.FormatBox.setGeometry(QtCore.QRect(262, 200, 152, 34)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(11) self.FormatBox.setFont(font) self.FormatBox.setFocusPolicy(QtCore.Qt.NoFocus) @@ -108,6 +118,7 @@ class Ui_KCC(object): self.ConvertButton = QtGui.QPushButton(self.Form) self.ConvertButton.setGeometry(QtCore.QRect(160, 200, 101, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(11) font.setBold(True) font.setWeight(75) @@ -120,6 +131,7 @@ class Ui_KCC(object): self.DirectoryButton = QtGui.QPushButton(self.Form) self.DirectoryButton.setGeometry(QtCore.QRect(5, 160, 156, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(11) self.DirectoryButton.setFont(font) self.DirectoryButton.setFocusPolicy(QtCore.Qt.NoFocus) @@ -130,6 +142,7 @@ class Ui_KCC(object): self.FileButton = QtGui.QPushButton(self.Form) self.FileButton.setGeometry(QtCore.QRect(260, 160, 157, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(11) self.FileButton.setFont(font) self.FileButton.setFocusPolicy(QtCore.Qt.NoFocus) @@ -140,6 +153,7 @@ class Ui_KCC(object): self.ClearButton = QtGui.QPushButton(self.Form) self.ClearButton.setGeometry(QtCore.QRect(160, 160, 101, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(11) self.ClearButton.setFont(font) self.ClearButton.setFocusPolicy(QtCore.Qt.NoFocus) @@ -148,22 +162,25 @@ class Ui_KCC(object): self.ClearButton.setIcon(icon4) self.ClearButton.setObjectName(_fromUtf8("ClearButton")) self.OptionsBasic = QtGui.QFrame(self.Form) - self.OptionsBasic.setGeometry(QtCore.QRect(10, 233, 421, 41)) + self.OptionsBasic.setGeometry(QtCore.QRect(5, 233, 421, 41)) font = QtGui.QFont() - font.setPointSize(9) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.OptionsBasic.setFont(font) self.OptionsBasic.setObjectName(_fromUtf8("OptionsBasic")) self.MangaBox = QtGui.QCheckBox(self.OptionsBasic) self.MangaBox.setGeometry(QtCore.QRect(9, 10, 130, 18)) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.MangaBox.setFont(font) self.MangaBox.setFocusPolicy(QtCore.Qt.NoFocus) self.MangaBox.setObjectName(_fromUtf8("MangaBox")) self.QualityBox = QtGui.QCheckBox(self.OptionsBasic) - self.QualityBox.setGeometry(QtCore.QRect(282, 10, 130, 18)) + self.QualityBox.setGeometry(QtCore.QRect(282, 10, 135, 18)) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.QualityBox.setFont(font) self.QualityBox.setFocusPolicy(QtCore.Qt.NoFocus) self.QualityBox.setTristate(True) @@ -171,13 +188,15 @@ class Ui_KCC(object): self.RotateBox = QtGui.QCheckBox(self.OptionsBasic) self.RotateBox.setGeometry(QtCore.QRect(145, 10, 130, 18)) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.RotateBox.setFont(font) self.RotateBox.setFocusPolicy(QtCore.Qt.NoFocus) self.RotateBox.setObjectName(_fromUtf8("RotateBox")) self.JobList = QtGui.QListWidget(self.Form) self.JobList.setGeometry(QtCore.QRect(10, 50, 401, 101)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(11) self.JobList.setFont(font) self.JobList.setFocusPolicy(QtCore.Qt.NoFocus) @@ -187,6 +206,7 @@ class Ui_KCC(object): self.BasicModeButton = QtGui.QPushButton(self.Form) self.BasicModeButton.setGeometry(QtCore.QRect(5, 10, 210, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(12) font.setBold(False) font.setWeight(50) @@ -196,6 +216,7 @@ class Ui_KCC(object): self.AdvModeButton = QtGui.QPushButton(self.Form) self.AdvModeButton.setGeometry(QtCore.QRect(207, 10, 210, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(12) font.setBold(False) font.setWeight(50) @@ -204,21 +225,26 @@ class Ui_KCC(object): self.AdvModeButton.setObjectName(_fromUtf8("AdvModeButton")) self.OptionsAdvancedGamma = QtGui.QFrame(self.Form) self.OptionsAdvancedGamma.setEnabled(True) - self.OptionsAdvancedGamma.setGeometry(QtCore.QRect(10, 303, 401, 41)) + self.OptionsAdvancedGamma.setGeometry(QtCore.QRect(5, 303, 401, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(9) self.OptionsAdvancedGamma.setFont(font) self.OptionsAdvancedGamma.setObjectName(_fromUtf8("OptionsAdvancedGamma")) self.GammaLabel = QtGui.QLabel(self.OptionsAdvancedGamma) self.GammaLabel.setGeometry(QtCore.QRect(20, 0, 100, 40)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(12) font.setBold(False) font.setWeight(50) self.GammaLabel.setFont(font) self.GammaLabel.setObjectName(_fromUtf8("GammaLabel")) self.GammaSlider = QtGui.QSlider(self.OptionsAdvancedGamma) - self.GammaSlider.setGeometry(QtCore.QRect(110, 10, 280, 22)) + self.GammaSlider.setGeometry(QtCore.QRect(110, 10, 290, 22)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) + self.GammaSlider.setFont(font) self.GammaSlider.setFocusPolicy(QtCore.Qt.ClickFocus) self.GammaSlider.setMaximum(500) self.GammaSlider.setSingleStep(5) @@ -227,6 +253,7 @@ class Ui_KCC(object): self.ProgressBar = QtGui.QProgressBar(self.Form) self.ProgressBar.setGeometry(QtCore.QRect(10, 10, 401, 35)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(10) font.setBold(True) font.setWeight(75) @@ -237,25 +264,31 @@ class Ui_KCC(object): self.ProgressBar.setFormat(_fromUtf8("")) self.ProgressBar.setObjectName(_fromUtf8("ProgressBar")) self.OptionsExpert = QtGui.QFrame(self.Form) - self.OptionsExpert.setGeometry(QtCore.QRect(10, 335, 421, 41)) + self.OptionsExpert.setGeometry(QtCore.QRect(5, 335, 421, 41)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(9) self.OptionsExpert.setFont(font) self.OptionsExpert.setObjectName(_fromUtf8("OptionsExpert")) self.ColorBox = QtGui.QCheckBox(self.OptionsExpert) self.ColorBox.setGeometry(QtCore.QRect(9, 11, 130, 18)) font = QtGui.QFont() - font.setPointSize(11) + font.setFamily(_fromUtf8("Lucida Grande")) + font.setPointSize(12) self.ColorBox.setFont(font) self.ColorBox.setFocusPolicy(QtCore.Qt.NoFocus) self.ColorBox.setObjectName(_fromUtf8("ColorBox")) self.OptionsExpertInternal = QtGui.QFrame(self.OptionsExpert) - self.OptionsExpertInternal.setGeometry(QtCore.QRect(90, 0, 315, 40)) + self.OptionsExpertInternal.setGeometry(QtCore.QRect(95, 0, 315, 40)) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) + self.OptionsExpertInternal.setFont(font) self.OptionsExpertInternal.setObjectName(_fromUtf8("OptionsExpertInternal")) self.gridLayout_2 = QtGui.QGridLayout(self.OptionsExpertInternal) self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) self.wLabel = QtGui.QLabel(self.OptionsExpertInternal) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(12) font.setBold(False) font.setWeight(50) @@ -270,6 +303,7 @@ class Ui_KCC(object): self.customWidth.setSizePolicy(sizePolicy) self.customWidth.setMaximumSize(QtCore.QSize(45, 16777215)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(12) self.customWidth.setFont(font) self.customWidth.setFocusPolicy(QtCore.Qt.ClickFocus) @@ -279,6 +313,7 @@ class Ui_KCC(object): self.gridLayout_2.addWidget(self.customWidth, 0, 1, 1, 1) self.hLabel = QtGui.QLabel(self.OptionsExpertInternal) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(12) font.setBold(False) font.setWeight(50) @@ -293,6 +328,7 @@ class Ui_KCC(object): self.customHeight.setSizePolicy(sizePolicy) self.customHeight.setMaximumSize(QtCore.QSize(45, 16777215)) font = QtGui.QFont() + font.setFamily(_fromUtf8("Lucida Grande")) font.setPointSize(12) self.customHeight.setFont(font) self.customHeight.setFocusPolicy(QtCore.Qt.ClickFocus) @@ -322,31 +358,31 @@ class Ui_KCC(object): KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None)) self.ProcessingBox.setToolTip(_translate("KCC", "

Disable image optimizations.

", None)) self.ProcessingBox.setText(_translate("KCC", "No optimisation", None)) - self.UpscaleBox.setToolTip(_translate("KCC", "

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) + self.UpscaleBox.setToolTip(_translate("KCC", "

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None)) - self.WebtoonBox.setToolTip(_translate("KCC", "

EXPERIMENTAL!
Enable auto-splitting of webtoons like Tower of God or Noblesse.
Pages with a low width, high height and vertical panel flow.

", None)) + self.WebtoonBox.setToolTip(_translate("KCC", "

Enable auto-splitting of webtoons like Tower of God or Noblesse.
Pages with a low width, high height and vertical panel flow.

", None)) self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None)) - self.NoDitheringBox.setToolTip(_translate("KCC", "

Create PNG files instead JPEG.
Only for non-Kindle devices!

", None)) + self.NoDitheringBox.setToolTip(_translate("KCC", "

Create PNG files instead JPEG.

", None)) self.NoDitheringBox.setText(_translate("KCC", "PNG output", None)) - self.BorderBox.setToolTip(_translate("KCC", "

Fill space around images with black color.

", None)) - self.BorderBox.setText(_translate("KCC", "Black borders", None)) + self.BorderBox.setToolTip(_translate("KCC", "

Unchecked - Autodetection
Color of margins fill will be detected automatically.

Indeterminate - White
Margins will be filled with white color.

Checked - Black
Margins will be filled with black color.

", None)) + self.BorderBox.setText(_translate("KCC", "W/B margins", None)) self.NoRotateBox.setToolTip(_translate("KCC", "

Disable splitting and rotation.

", None)) self.NoRotateBox.setText(_translate("KCC", "No split/rotate", None)) - self.DeviceBox.setToolTip(_translate("KCC", "Target device.", None)) - self.FormatBox.setToolTip(_translate("KCC", "Output format.", None)) + self.DeviceBox.setToolTip(_translate("KCC", "

Target device.

", None)) + self.FormatBox.setToolTip(_translate("KCC", "

Output format.

", None)) self.ConvertButton.setText(_translate("KCC", "Convert", None)) self.DirectoryButton.setText(_translate("KCC", "Add directory", None)) self.FileButton.setText(_translate("KCC", "Add file", None)) self.ClearButton.setText(_translate("KCC", "Clear list", None)) self.MangaBox.setToolTip(_translate("KCC", "

Enable right-to-left reading.

", None)) self.MangaBox.setText(_translate("KCC", "Manga mode", None)) - self.QualityBox.setToolTip(_translate("KCC", "

Unchecked - Normal quality mode
Use it when Panel View support is not needed.
- Maximum quality when zoom is not enabled.
- Poor quality when zoom is enabled.
- Lowest file size.

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

Checked - Ultra quality mode
Maximum possible quality.
- Maximum quality when zoom is not enabled.
- Maximum quality when zoom is enabled.
- Very high file size.

", None)) + self.QualityBox.setToolTip(_translate("KCC", "

Unchecked - Normal quality mode
Use it when Panel View support is not needed.
- Maximum quality when zoom is not enabled.
- Poor quality when zoom is enabled.
- Lowest file size.

Indeterminate - High quality mode
Not zoomed image might be a little blurry.
- Medium/High quality when zoom is not enabled.
- Maximum quality when zoom is enabled.

Checked - Ultra quality mode
Maximum possible quality.
- Maximum quality when zoom is not enabled.
- Maximum quality when zoom is enabled.
- Very high file size.

", None)) self.QualityBox.setText(_translate("KCC", "High/Ultra quality", None)) self.RotateBox.setToolTip(_translate("KCC", "

Disable page spliting.
They will be rotated instead.

", None)) self.RotateBox.setText(_translate("KCC", "Horizontal mode", None)) self.BasicModeButton.setText(_translate("KCC", "Basic", None)) self.AdvModeButton.setText(_translate("KCC", "Advanced", None)) - self.GammaLabel.setToolTip(_translate("KCC", "

When converting color images setting this option to 1.0 MIGHT improve readability.

", None)) + self.GammaLabel.setToolTip(_translate("KCC", "

When converting color images setting this option to 1.0 might improve readability.

", None)) self.GammaLabel.setText(_translate("KCC", "Gamma: Auto", None)) self.GammaSlider.setToolTip(_translate("KCC", "

When converting color images setting this option to 1.0 might improve readability.

", None)) self.ColorBox.setToolTip(_translate("KCC", "

Do not convert images to grayscale.

", None)) diff --git a/kcc/__init__.py b/kcc/__init__.py index f9bbb7e..9e6b234 100644 --- a/kcc/__init__.py +++ b/kcc/__init__.py @@ -1,4 +1,4 @@ -__version__ = '3.2.1' +__version__ = '3.3' __license__ = 'ISC' __copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jastrzebski ' __docformat__ = 'restructuredtext en' \ No newline at end of file diff --git a/kcc/cbxarchive.py b/kcc/cbxarchive.py index d4b8a86..226e77a 100644 --- a/kcc/cbxarchive.py +++ b/kcc/cbxarchive.py @@ -1,4 +1,5 @@ -# Copyright (c) 2012 Ciro Mattia Gonano +# Copyright (c) 2012-2013 Ciro Mattia Gonano +# Copyright (c) 2013 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the @@ -21,6 +22,7 @@ __docformat__ = 'restructuredtext en' import os import zipfile import rarfile +from subprocess import Popen, STDOUT, PIPE # noinspection PyBroadException @@ -31,6 +33,8 @@ class CBxArchive: self.compressor = 'zip' elif rarfile.is_rarfile(origFileName): self.compressor = 'rar' + elif origFileName.endswith('.7z') or origFileName.endswith('.cb7'): + self.compressor = '7z' else: self.compressor = None @@ -67,12 +71,24 @@ class CBxArchive: filelist.append(f) cbrFile.extractall(targetdir, filelist) + def extractCB7(self, targetdir): + output = Popen('7za x "' + self.origFileName + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -o"' + targetdir + + '"', stdout=PIPE, stderr=STDOUT, shell=True) + extracted = False + for line in output.stdout: + if "Everything is Ok" in line: + extracted = True + if not extracted: + raise OSError + def extract(self, targetdir): print "\n" + targetdir + "\n" if self.compressor == 'rar': self.extractCBR(targetdir) elif self.compressor == 'zip': self.extractCBZ(targetdir) + elif self.compressor == '7z': + self.extractCB7(targetdir) adir = os.listdir(targetdir) if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])): import shutil diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py index d56e5e7..cb0204a 100755 --- a/kcc/comic2ebook.py +++ b/kcc/comic2ebook.py @@ -1,7 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (c) 2012 Ciro Mattia Gonano +# Copyright (c) 2012-2013 Ciro Mattia Gonano +# Copyright (c) 2013 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the @@ -17,7 +18,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # -__version__ = '3.2.1' +__version__ = '3.3' __license__ = 'ISC' __copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jastrzebski ' __docformat__ = 'restructuredtext en' @@ -44,11 +45,18 @@ import pdfjpgextract def buildHTML(path, imgfile): filename = getImageFileName(imgfile) if filename is not None: - # All files marked with this sufix need horizontal Panel View. - if "_kccrotated" in str(filename): - rotate = True + if "_kccrot" in str(filename): + rotatedPage = True else: - rotate = False + rotatedPage = False + if "_kccnh" in str(filename): + noHorizontalPV = True + else: + noHorizontalPV = False + if "_kccnv" in str(filename): + noVerticalPV = True + else: + noVerticalPV = False htmlpath = '' postfix = '' backref = 1 @@ -79,88 +87,82 @@ def buildHTML(path, imgfile): imgfile, "\" class=\"singlePage\"/>\n" ]) if options.panelview: - if rotate: - if options.righttoleft: - f.writelines(["
\n", - "
\n", - "
\n", - "
\n" - ]) + if not noHorizontalPV and not noVerticalPV: + if rotatedPage: + if options.righttoleft: + order = [1, 3, 2, 4] + else: + order = [2, 4, 1, 3] else: - f.writelines(["
\n", - "
\n", - "
\n", - "
\n" - ]) + if options.righttoleft: + order = [2, 1, 4, 3] + else: + order = [1, 2, 3, 4] + boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"] + elif noHorizontalPV and not noVerticalPV: + if rotatedPage: + if options.righttoleft: + order = [2, 1] + else: + order = [1, 2] + else: + order = [1, 2] + boxes = ["BoxT", "BoxB"] + elif not noHorizontalPV and noVerticalPV: + if rotatedPage: + order = [1, 2] + else: + if options.righttoleft: + order = [2, 1] + else: + order = [1, 2] + boxes = ["BoxL", "BoxR"] else: - if options.righttoleft: - f.writelines(["
\n", - "
\n", - "
\n", - "
\n" - ]) - else: - f.writelines(["
\n", - "
\n", - "
\n", - "
\n" - ]) + order = [1] + boxes = ["BoxC"] + for i in range(0, len(boxes)): + f.writelines(["
\n"]) if options.quality == 2: imgfilepv = string.split(imgfile, ".") + imgfilepv[0] = imgfilepv[0].split("_kccx")[0].replace("_kccnh", "").replace("_kccnv", "") imgfilepv[0] += "_kcchq" imgfilepv = string.join(imgfilepv, ".") else: imgfilepv = imgfile - f.writelines(["
\"",
\n", - "
\"",
\n", - "
\"",
\n", - "
\"",
\n" - ]) + xy = string.split(filename[0], "_kccx")[1] + x = string.split(xy, "_kccy")[0].lstrip("0") + y = string.split(xy, "_kccy")[1].lstrip("0") + if x != "": + x = "-" + str(float(x)/100) + "%" + else: + x = "0%" + if y != "": + y = "-" + str(float(y)/100) + "%" + else: + y = "0%" + boxStyles = {"BoxTL": "left:" + x + ";top:" + y + ";", + "BoxTR": "right:" + x + ";top:" + y + ";", + "BoxBL": "left:" + x + ";bottom:" + y + ";", + "BoxBR": "right:" + x + ";bottom:" + y + ";", + "BoxT": "left:-25%;top:" + y + ";", + "BoxB": "left:-25%;bottom:" + y + ";", + "BoxL": "left:" + x + ";top:-25%;", + "BoxR": "right:" + x + ";top:-25%;", + "BoxC": "right:-25%;top:-25%;" + } + for box in boxes: + f.writelines(["
\""
\n", + ]) f.writelines(["\n\n"]) f.close() return path, imgfile -def buildBlankHTML(path): - f = open(os.path.join(path, 'blank.html'), "w") - f.writelines(["\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - ""]) - f.close() - return path - - def buildNCX(dstdir, title, chapters): from uuid import uuid4 options.uuid = str(uuid4()) @@ -173,16 +175,18 @@ def buildNCX(dstdir, title, chapters): "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", + "\n", "\n", "", title, "\n", "" ]) for chapter in chapters: folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') - title = os.path.basename(folder) + if os.path.basename(folder) != "Text": + title = os.path.basename(folder) filename = getImageFileName(os.path.join(folder, chapter[1])) f.write("" + title + "\n") @@ -197,38 +201,33 @@ def buildOPF(dstdir, title, filelist, cover=None): imgres = str(deviceres[0]) + "x" + str(deviceres[1]) if options.righttoleft: writingmode = "horizontal-rl" - facing = "right" - facing1 = "right" - facing2 = "left" else: writingmode = "horizontal-lr" - facing = "left" - facing1 = "left" - facing2 = "right" f = open(opffile, "w") f.writelines(["\n", - "\n", - "\n", + "\n", + "\n", "", title, "\n", "en-US\n", "", options.uuid, "\n", + "KCC\n", + "\n", "\n", + "\n", "\n", "\n", + "\n", "\n", "\n", "\n" - ]) - if options.landscapemode: - f.writelines(["\n", - "\n"]) - else: - f.writelines(["\n", - "\n"]) - f.writelines(["\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", + "\n", "\n\n\n"]) if cover is not None: @@ -253,44 +252,10 @@ def buildOPF(dstdir, title, filelist, cover=None): mt = 'image/jpeg' f.write("\n") - if options.landscapemode and splitCount > 0: - splitCountUsed = 1 - while splitCountUsed <= splitCount: - f.write("\n") - splitCountUsed += 1 f.write("\n") f.write("\n\n") - splitCountUsed = 1 for entry in reflist: - if "_kcca" in str(entry): - # noinspection PyRedundantParentheses - if ((options.righttoleft and facing == 'left') or (not options.righttoleft and facing == 'right')) and\ - options.landscapemode: - f.write("\n") - splitCountUsed += 1 - if options.landscapemode: - f.write("\n") - else: - f.write("\n") - elif "_kccb" in str(entry): - if options.landscapemode: - f.write("\n") - else: - f.write("\n") - if options.righttoleft: - facing = "right" - else: - facing = "left" - else: - if options.landscapemode: - f.write("\n") - else: - f.write("\n") - if facing == 'right': - facing = 'left' - else: - facing = 'right' + f.write("\n") f.write("\n\n\n\n") f.close() os.mkdir(os.path.join(dstdir, 'META-INF')) @@ -322,24 +287,22 @@ def getImageFileName(imgfile): return filename -def applyImgOptimization(img, isSplit, toRight, options, overrideQuality=5): - if not options.webtoon: +def applyImgOptimization(img, opt, overrideQuality=5): + img.getImageFill(opt.webtoon) + if not opt.webtoon: img.cropWhiteSpace(10.0) - if options.cutpagenumbers and not options.webtoon: + if opt.cutpagenumbers and not opt.webtoon: img.cutPageNumber() - img.optimizeImage(options.gamma) + img.optimizeImage(opt.gamma) if overrideQuality != 5: - img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight, - options.landscapemode, overrideQuality) + img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, overrideQuality) else: - img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight, - options.landscapemode, options.quality) - if options.forcepng and not options.forcecolor: + img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, opt.quality) + if opt.forcepng and not opt.forcecolor: img.quantizeImage() def dirImgProcess(path): - global options, splitCount work = [] pagenumber = 0 pagenumbermodifier = 0 @@ -378,7 +341,6 @@ def dirImgProcess(path): splitpages.sort() for page in splitpages: if (page + pagenumbermodifier) % 2 == 0: - splitCount += 1 pagenumbermodifier += 1 pagenumbermodifier += 1 else: @@ -386,9 +348,9 @@ def dirImgProcess(path): raise UserWarning("Source directory is empty.") -def fileImgProcess_init(queue, options): +def fileImgProcess_init(queue, opt): fileImgProcess.queue = queue - fileImgProcess.options = options + fileImgProcess.options = opt # noinspection PyUnresolvedReferences @@ -396,59 +358,53 @@ def fileImgProcess(work): afile = work[0] dirpath = work[1] pagenumber = work[2] - options = fileImgProcess.options + opt = fileImgProcess.options output = None - if options.verbose: - print "Optimizing " + afile + " for " + options.profile + if opt.verbose: + print "Optimizing " + afile + " for " + opt.profile else: print ".", fileImgProcess.queue.put(".") - img = image.ComicPage(os.path.join(dirpath, afile), options.profileData) - if options.quality == 2: + img = image.ComicPage(os.path.join(dirpath, afile), opt.profileData) + if opt.quality == 2: wipe = False else: wipe = True - if options.nosplitrotate: + if opt.nosplitrotate: split = None else: - split = img.splitPage(dirpath, options.righttoleft, options.rotate) - if split is not None and split is not "R": - if options.verbose: + split = img.splitPage(dirpath, opt.righttoleft, opt.rotate) + if split is not None: + if opt.verbose: print "Splitted " + afile - if options.righttoleft: - toRight1 = False - toRight2 = True - else: - toRight1 = True - toRight2 = False output = pagenumber - img0 = image.ComicPage(split[0], options.profileData) - applyImgOptimization(img0, True, toRight1, options) - img0.saveToDir(dirpath, options.forcepng, options.forcecolor, wipe) - img1 = image.ComicPage(split[1], options.profileData) - applyImgOptimization(img1, True, toRight2, options) - img1.saveToDir(dirpath, options.forcepng, options.forcecolor, wipe) - if options.quality == 2: - img3 = image.ComicPage(split[0], options.profileData) - applyImgOptimization(img3, True, toRight1, options, 0) - img3.saveToDir(dirpath, options.forcepng, options.forcecolor, True) - img4 = image.ComicPage(split[1], options.profileData) - applyImgOptimization(img4, True, toRight2, options, 0) - img4.saveToDir(dirpath, options.forcepng, options.forcecolor, True) + img0 = image.ComicPage(split[0], opt.profileData) + applyImgOptimization(img0, opt) + img0.saveToDir(dirpath, opt.forcepng, opt.forcecolor, wipe) + img1 = image.ComicPage(split[1], opt.profileData) + applyImgOptimization(img1, opt) + img1.saveToDir(dirpath, opt.forcepng, opt.forcecolor, wipe) + if opt.quality == 2: + img3 = image.ComicPage(split[0], opt.profileData) + applyImgOptimization(img3, opt, 0) + img3.saveToDir(dirpath, opt.forcepng, opt.forcecolor, True) + img4 = image.ComicPage(split[1], opt.profileData) + applyImgOptimization(img4, opt, 0) + img4.saveToDir(dirpath, opt.forcepng, opt.forcecolor, True) else: - applyImgOptimization(img, False, False, options) - img.saveToDir(dirpath, options.forcepng, options.forcecolor, wipe, split) - if options.quality == 2: - img2 = image.ComicPage(os.path.join(dirpath, afile), options.profileData) - if split == "R": + applyImgOptimization(img, opt) + img.saveToDir(dirpath, opt.forcepng, opt.forcecolor, wipe) + if opt.quality == 2: + img2 = image.ComicPage(os.path.join(dirpath, afile), opt.profileData) + if img.rotated: img2.image = img2.image.rotate(90) - applyImgOptimization(img2, False, False, options, 0) - img2.saveToDir(dirpath, options.forcepng, options.forcecolor, True, split) + img2.rotated = True + applyImgOptimization(img2, opt, 0) + img2.saveToDir(dirpath, opt.forcepng, opt.forcecolor, True) return output def genEpubStruct(path): - global options filelist = [] chapterlist = [] cover = None @@ -508,6 +464,36 @@ def genEpubStruct(path): "height: ", str(panelviewsize[1]), "px;\n", "width: ", str(panelviewsize[0]), "px;\n", "}\n", + "#Generic-Panel {\n", + "top: 0;\n", + "height: 100%;\n", + "width: 100%;\n", + "}\n", + "#BoxC {\n", + "top: 0;\n", + "height: 100%;\n", + "width: 100%;\n", + "}\n", + "#BoxT {\n", + "top: 0;\n", + "height: 50%;\n", + "width: 100%;\n", + "}\n", + "#BoxB {\n", + "bottom: 0;\n", + "height: 50%;\n", + "width: 100%;\n", + "}\n", + "#BoxL {\n", + "left: 0;\n", + "height: 100%;\n", + "width: 50%;\n", + "}\n", + "#BoxR {\n", + "right: 0;\n", + "height: 100%;\n", + "width: 50%;\n", + "}\n", "#BoxTL {\n", "top: 0;\n", "left: 0;\n", @@ -531,47 +517,7 @@ def genEpubStruct(path): "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')): @@ -588,13 +534,11 @@ def genEpubStruct(path): 'cover' + getImageFileName(filelist[-1][1])[1]) copyfile(os.path.join(filelist[-1][0], filelist[-1][1]), cover) buildNCX(path, options.title, chapterlist) - # ensure we're sorting files alphabetically + # Ensure we're sorting files alphabetically convert = lambda text: int(text) if text.isdigit() else text alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] filelist.sort(key=lambda name: (alphanum_key(name[0].lower()), alphanum_key(name[1].lower()))) buildOPF(path, options.title, filelist, cover) - if options.landscapemode and splitCount > 0: - filelist.append(buildBlankHTML(os.path.join(path, 'OEBPS', 'Text'))) def getWorkFolder(afile): @@ -625,8 +569,7 @@ def getWorkFolder(afile): path = cbx.extract(workdir) except OSError: rmtree(workdir) - print 'Unrar not found, please download from ' + \ - 'http://www.rarlab.com/download.htm and put into your PATH.' + print 'UnRAR/7za not found or file failed to extract!' sys.exit(21) else: rmtree(workdir) @@ -637,9 +580,9 @@ def getWorkFolder(afile): def slugify(value): - # Normalizes string, converts to lowercase, removes non-alpha characters, - # and converts spaces to hyphens. + # Normalizes string, converts to lowercase, removes non-alpha characters and converts spaces to hyphens. import unicodedata + #noinspection PyArgumentList value = unicodedata.normalize('NFKD', unicode(value, 'latin1')).encode('ascii', 'ignore') value = re.sub('[^\w\s\.-]', '', value).strip().lower() value = re.sub('[-\.\s]+', '-', value) @@ -689,6 +632,7 @@ def getDirectorySize(start_path='.'): return total_size +# noinspection PyUnusedLocal def createNewTome(parentPath): tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-') #tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-', parentPath) @@ -843,21 +787,22 @@ def Usage(): def main(argv=None, qtGUI=None): - global parser, options, epub_path, splitCount, GUI + global parser, options, GUI parser = OptionParser(usage="Usage: %prog [options] comic_file|comic_folder", add_help_option=False) mainOptions = OptionGroup(parser, "MAIN") - experimentalOptions = OptionGroup(parser, "EXPERIMENTAL") processingOptions = OptionGroup(parser, "PROCESSING") outputOptions = OptionGroup(parser, "OUTPUT SETTINGS") customProfileOptions = OptionGroup(parser, "CUSTOM PROFILE") otherOptions = OptionGroup(parser, "OTHER") mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KHD", - help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD," - " KFHD8, KFA) [Default=KHD]") + help="Device profile (Choose one among K1, K2, K345, KDX, KDXG, KHD, KF, KFHD, KFHD8, KFHDX," + " KFHDX8, KFA) [Default=KHD]") mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0", help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]") mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, help="Manga style (Right-to-left reading and splitting)") + mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False, + help="Webtoon processing mode"), outputOptions.add_option("-o", "--output", action="store", dest="output", default=None, help="Output generated file to specified directory or file") outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle", @@ -866,10 +811,10 @@ def main(argv=None, qtGUI=None): help="Outputs a CBZ archive and does not generate EPUB") outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False, help="Split output into multiple files"), - experimentalOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False, - help="Webtoon processing mode"), processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False, - help="Use black borders instead of white ones") + help="Disable autodetection and force black borders") + processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False, + help="Disable autodetection and force white borders") processingOptions.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False, help="Don't convert images to grayscale") processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False, @@ -897,7 +842,6 @@ def main(argv=None, qtGUI=None): otherOptions.add_option("-h", "--help", action="help", help="Show this help message and exit") parser.add_option_group(mainOptions) - parser.add_option_group(experimentalOptions) parser.add_option_group(outputOptions) parser.add_option_group(processingOptions) parser.add_option_group(customProfileOptions) @@ -920,7 +864,6 @@ def main(argv=None, qtGUI=None): comic2panel.main(['-y ' + str(options.customheight), '-i', path], qtGUI) else: comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', path], qtGUI) - splitCount = 0 if options.imgproc: print "\nProcessing images..." if GUI: @@ -984,45 +927,29 @@ def getOutputFilename(srcpath, wantedname, ext, tomeNumber): def checkOptions(): global options - # Webtoon mode mandatory options - if options.webtoon: - options.nosplitrotate = True - options.black_borders = False - options.quality = 0 - # Landscape mode is only supported by Kindle Touch and Paperwhite. - if options.profile == 'K4T' or options.profile == 'KHD': - options.landscapemode = True - else: - options.landscapemode = False - # Older Kindle don't support Virtual Panel View. We providing them configuration that will fake that feature. - # Ultra quality mode require Real Panel View. Landscape mode don't work correcly without Virtual Panel View. - if options.profile == 'K3' or options.profile == 'K4NT' or options.quality == 2: - # Real Panel View - options.panelview = True - options.landscapemode = False - else: - # Virtual Panel View - options.panelview = False + options.panelview = True + options.bordersColor = None + if options.white_borders: + options.bordersColor = "white" + if options.black_borders: + options.bordersColor = "black" # Disabling grayscale conversion for Kindle Fire family. - if options.profile == 'KF' or options.profile == 'KFHD' or options.profile == 'KFHD8' or options.forcecolor: + if options.profile == 'KF' or options.profile == 'KFHD' or options.profile == 'KFHD8' or options.profile == 'KFHDX'\ + or options.profile == 'KFHDX8' or options.forcecolor: options.forcecolor = True else: options.forcecolor = False - # Mixing vertical and horizontal pages require real Panel View. - # Landscape mode don't work correcly without Virtual Panel View. - if options.rotate: - options.panelview = True - options.landscapemode = False # Older Kindle don't need higher resolution files due lack of Panel View. - # Kindle Fire family have very high resolution. Bigger images are not needed. - if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX' or options.profile == 'KDXG'\ - or options.profile == 'KF' or options.profile == 'KFHD' or options.profile == 'KFHD8': + if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX' or options.profile == 'KDXG': options.quality = 0 - if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX' or options.profile == 'KDXG': - options.panelview = False - # Disable all Kindle features + options.panelview = False + # Webtoon mode mandatory options + if options.webtoon: + options.nosplitrotate = True + options.quality = 0 + options.panelview = False + # Disable all Kindle features for other e-readers if options.profile == 'OTHER': - options.landscapemode = False options.panelview = False options.quality = 0 # Kindle for Android profile require target resolution. diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py index 57a39eb..637ea28 100644 --- a/kcc/comic2panel.py +++ b/kcc/comic2panel.py @@ -1,7 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (c) 2012 Ciro Mattia Gonano +# Copyright (c) 2012-2013 Ciro Mattia Gonano +# Copyright (c) 2013 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the @@ -17,7 +18,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # -__version__ = '3.2.1' +__version__ = '3.3' __license__ = 'ISC' __copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jastrzebski ' __docformat__ = 'restructuredtext en' @@ -28,7 +29,7 @@ from shutil import rmtree, copytree, move from optparse import OptionParser, OptionGroup from multiprocessing import Pool, Queue, freeze_support try: - # noinspection PyUnresolvedReferences,PyPackageRequirements + # noinspection PyUnresolvedReferences from PIL import Image, ImageStat except ImportError: print "ERROR: Pillow is not installed!" @@ -53,48 +54,9 @@ def getImageFileName(imgfile): return filename -def getImageHistogram(image): - histogram = image.histogram() - RBGW = [] - for i in range(256): - RBGW.append(histogram[i] + histogram[256 + i] + histogram[512 + i]) - white = 0 - black = 0 - for i in range(245, 256): - white += RBGW[i] - for i in range(11): - black += RBGW[i] - if white > black: - return False - else: - return True - - -def getImageFill(image): - imageSize = image.size - imageT = image.crop((0, 0, imageSize[0], 1)) - imageB = image.crop((0, imageSize[1]-1, imageSize[0], imageSize[1])) - fill = 0 - fill += getImageHistogram(imageT) - fill += getImageHistogram(imageB) - if fill == 2: - return 'KCCFB' - elif fill == 0: - return 'KCCFW' - else: - imageL = image.crop((0, 0, 1, imageSize[1])) - imageR = image.crop((imageSize[0]-1, 0, imageSize[0], imageSize[1])) - fill += getImageHistogram(imageL) - fill += getImageHistogram(imageR) - if fill >= 2: - return 'KCCFB' - else: - return 'KCCFW' - - -def sanitizePanelSize(panel, options): +def sanitizePanelSize(panel, opt): newPanels = [] - if panel[2] > 8 * options.height: + if panel[2] > 8 * opt.height: diff = (panel[2] / 8) newPanels.append([panel[0], panel[1] - diff*7, diff]) newPanels.append([panel[1] - diff*7, panel[1] - diff*6, diff]) @@ -104,13 +66,13 @@ def sanitizePanelSize(panel, options): newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff]) newPanels.append([panel[1] - diff*2, panel[1] - diff, diff]) newPanels.append([panel[1] - diff, panel[1], diff]) - elif panel[2] > 4 * options.height: + elif panel[2] > 4 * opt.height: diff = (panel[2] / 4) newPanels.append([panel[0], panel[1] - diff*3, diff]) newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff]) newPanels.append([panel[1] - diff*2, panel[1] - diff, diff]) newPanels.append([panel[1] - diff, panel[1], diff]) - elif panel[2] > 2 * options.height: + elif panel[2] > 2 * opt.height: newPanels.append([panel[0], panel[1] - (panel[2] / 2), (panel[2] / 2)]) newPanels.append([panel[1] - (panel[2] / 2), panel[1], (panel[2] / 2)]) else: @@ -118,17 +80,17 @@ def sanitizePanelSize(panel, options): return newPanels -def splitImage_init(queue, options): +def splitImage_init(queue, opt): splitImage.queue = queue - splitImage.options = options + splitImage.options = opt # noinspection PyUnresolvedReferences def splitImage(work): path = work[0] name = work[1] - options = splitImage.options - # Harcoded options + opt = splitImage.options + # Harcoded opttions threshold = 1.0 delta = 15 print ".", @@ -137,7 +99,7 @@ def splitImage(work): filePath = os.path.join(path, name) # Detect corrupted files try: - image = Image.open(filePath) + Image.open(filePath) except IOError: raise RuntimeError('Cannot read image file %s' % filePath) try: @@ -153,8 +115,8 @@ def splitImage(work): image = Image.open(filePath) image = image.convert('RGB') widthImg, heightImg = image.size - if heightImg > options.height: - if options.debug: + if heightImg > opt.height: + if opt.debug: from PIL import ImageDraw debugImage = Image.open(filePath) draw = ImageDraw.Draw(debugImage) @@ -176,23 +138,23 @@ def splitImage(work): if y1 + delta >= heightImg: y1 = heightImg - 1 y2Temp = y1 - if options.debug: + if opt.debug: draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0)) draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0)) panelHeight = y2Temp - y1Temp if panelHeight > delta: # Panels that can't be cut nicely will be forcefully splitted - panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], options) + panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], opt) for panel in panelsCleaned: panels.append(panel) - if options.debug: + if opt.debug: # noinspection PyUnboundLocalVariable debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG') # Create virtual pages pages = [] currentPage = [] - pageLeft = options.height + pageLeft = opt.height panelNumber = 0 for panel in panels: if pageLeft - panel[2] > 0: @@ -202,7 +164,7 @@ def splitImage(work): else: if len(currentPage) > 0: pages.append(currentPage) - pageLeft = options.height - panel[2] + pageLeft = opt.height - panel[2] currentPage = [panelNumber] panelNumber += 1 if len(currentPage) > 0: @@ -222,7 +184,7 @@ def splitImage(work): newPage.paste(panelImg, (0, targetHeight)) targetHeight += panels[panel][2] newPage.save(os.path.join(path, fileExpanded[0] + '-' + - str(pageNumber) + '-' + getImageFill(newPage) + '.png'), 'PNG') + str(pageNumber) + '.png'), 'PNG') pageNumber += 1 os.remove(filePath) diff --git a/kcc/image.py b/kcc/image.py index 9958f3d..97c97b8 100755 --- a/kcc/image.py +++ b/kcc/image.py @@ -1,6 +1,7 @@ # Copyright (C) 2010 Alex Yatskov # Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov # Copyright (C) 2012-2013 Ciro Mattia Gonano +# Copyright (C) 2013 Pawel Jastrzebski # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,7 +22,7 @@ __docformat__ = 'restructuredtext en' import os try: - # noinspection PyUnresolvedReferences,PyPackageRequirements + # noinspection PyUnresolvedReferences from PIL import Image, ImageOps, ImageStat, ImageChops except ImportError: print "ERROR: Pillow is not installed!" @@ -29,6 +30,9 @@ except ImportError: class ProfileData: + def __init__(self): + pass + Palette4 = [ 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, @@ -73,38 +77,60 @@ class ProfileData: 0xff, 0xff, 0xff, ] + PalleteNull = [ + ] + Profiles = { - 'K1': ("Kindle 1", (600, 800), Palette4, 1.8, (900, 1200)), - 'K2': ("Kindle 2", (600, 800), Palette15, 1.8, (900, 1200)), - 'K3': ("Kindle Keyboard", (600, 800), Palette16, 1.8, (900, 1200)), - 'K4NT': ("Kindle Non-Touch", (600, 800), Palette16, 1.8, (900, 1200)), - 'K4T': ("Kindle Touch", (600, 800), Palette16, 1.8, (900, 1200)), + 'K1': ("Kindle 1", (600, 670), Palette4, 1.8, (900, 1005)), + 'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)), + 'K345': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)), 'KHD': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)), - 'KDX': ("Kindle DX", (824, 1200), Palette15, 1.8, (1236, 1800)), - 'KDXG': ("Kindle DXG", (824, 1200), Palette16, 1.8, (1236, 1800)), - 'KF': ("Kindle Fire", (600, 1024), Palette16, 1.0, (900, 1536)), - 'KFHD': ("Kindle Fire HD 7\"", (800, 1280), Palette16, 1.0, (1200, 1920)), - 'KFHD8': ("Kindle Fire HD 8.9\"", (1200, 1920), Palette16, 1.0, (1800, 2880)), - 'KFA': ("Kindle for Android", (0, 0), Palette16, 1.0, (0, 0)), + 'KDX': ("Kindle DX", (824, 1000), Palette15, 1.8, (1236, 1500)), + 'KDXG': ("Kindle DXG", (824, 1000), Palette16, 1.8, (1236, 1500)), + 'KF': ("Kindle Fire", (600, 1024), PalleteNull, 1.0, (900, 1536)), + 'KFHD': ("K. Fire HD 7\"", (800, 1280), PalleteNull, 1.0, (1200, 1920)), + 'KFHD8': ("K. Fire HD 8.9\"", (1200, 1920), PalleteNull, 1.0, (1800, 2880)), + 'KFHDX': ("K. Fire HDX 7\"", (1200, 1920), PalleteNull, 1.0, (1800, 2880)), + 'KFHDX8': ("K. Fire HDX 8.9\"", (1600, 2560), PalleteNull, 1.0, (2400, 3840)), + 'KFA': ("Kindle for Android", (0, 0), PalleteNull, 1.0, (0, 0)), 'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)), } ProfileLabels = { "Kindle 1": 'K1', "Kindle 2": 'K2', - "Kindle 3/Keyboard": 'K3', - "Kindle 4/Non-Touch": 'K4NT', - "Kindle 4/Touch": 'K4T', + "Kindle": 'K345', "Kindle Paperwhite": 'KHD', "Kindle DX": 'KDX', "Kindle DXG": 'KDXG', "Kindle Fire": 'KF', - "Kindle Fire HD 7\"": 'KFHD', - "Kindle Fire HD 8.9\"": 'KFHD8', + "K. Fire HD 7\"": 'KFHD', + "K. Fire HD 8.9\"": 'KFHD8', + "K. Fire HDX 7\"": 'KFHDX', + "K. Fire HDX 8.9\"": 'KFHDX8', "Kindle for Android": 'KFA', "Other": 'OTHER' } + ProfileLabelsGUI = [ + "Kindle Paperwhite", + "Kindle", + "Separator", + "K. Fire HD 7\"", + "K. Fire HD 8.9\"", + "K. Fire HDX 7\"", + "K. Fire HDX 8.9\"", + "Separator", + "Kindle for Android", + "Other", + "Separator", + "Kindle 1", + "Kindle 2", + "Kindle DX", + "Kindle DXG", + "Kindle Fire" + ] + class ComicPage: def __init__(self, source, device): @@ -133,19 +159,29 @@ class ComicPage: raise RuntimeError('Image file %s is corrupted' % source) self.image = Image.open(source) self.image = self.image.convert('RGB') + self.rotated = None + self.border = None + self.noHPV = None + self.noVPV = None + self.fill = None - def saveToDir(self, targetdir, forcepng, color, wipe, suffix=None): + def saveToDir(self, targetdir, forcepng, color, wipe): try: + suffix = "" if not color: self.image = self.image.convert('L') # convert to grayscale - if suffix == "R": - suffix = "_kccrotated" - else: - suffix = "" + if self.rotated: + suffix += "_kccrot" if wipe: os.remove(os.path.join(targetdir, self.filename)) else: suffix += "_kcchq" + if self.noHPV: + suffix += "_kccnh" + if self.noVPV: + suffix += "_kccnv" + if self.border: + suffix += "_kccx" + str(self.border[0]) + "_kccy" + str(self.border[1]) if forcepng: self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".png"), "PNG") else: @@ -171,72 +207,98 @@ class ComicPage: palImg.putpalette(self.palette) self.image = self.image.quantize(palette=palImg) - def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False, - landscapeMode=False, qualityMode=0): + def resizeImage(self, upscale=False, stretch=False, bordersColor=None, qualityMode=0): method = Image.ANTIALIAS - if '-KCCFW' in str(self.filename): - fill = 'white' - elif '-KCCFB' in str(self.filename): - fill = 'black' + if bordersColor: + fill = bordersColor else: - if black_borders: - fill = 'black' - else: - fill = 'white' + fill = self.fill if qualityMode == 0: size = (self.size[0], self.size[1]) + generateBorder = True + elif qualityMode == 1: + size = (self.panelviewsize[0], self.panelviewsize[1]) + generateBorder = True else: size = (self.panelviewsize[0], self.panelviewsize[1]) - # Kindle Paperwhite/Touch - Force upscale of splited pages to increase readability - if isSplit and landscapeMode: - upscale = True + generateBorder = False if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]: if not upscale: borderw = (self.size[0] - self.image.size[0]) / 2 borderh = (self.size[1] - self.image.size[1]) / 2 self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill) + if generateBorder: + if (self.image.size[0]-(2*borderw))*1.5 < self.size[0]: + self.noHPV = True + if (self.image.size[1]-(2*borderh))*1.5 < self.size[1]: + self.noVPV = True + self.border = [int(round(float(borderw)/float(self.image.size[0])*100, 2)*100*1.5), + int(round(float(borderh)/float(self.image.size[1])*100, 2)*100*1.5)] return self.image else: 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(size, method) + if generateBorder: + if fill == 'white': + border = ImageOps.invert(self.image).getbbox() + else: + border = self.image.getbbox() + if border is not None: + if (border[2]-border[0])*1.5 < self.size[0]: + self.noHPV = True + if (border[3]-border[1])*1.5 < self.size[1]: + self.noVPV = True + self.border = [int(round(float(border[0])/float(self.image.size[0])*100, 2)*100*1.5), + int(round(float(border[1])/float(self.image.size[1])*100, 2)*100*1.5)] + else: + self.border = [0, 0] + self.noHPV = True + self.noVPV = True return self.image ratioDev = float(self.size[0]) / float(self.size[1]) if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: - if isSplit and landscapeMode: - diff = int(self.image.size[1] * ratioDev) - self.image.size[0] - self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill) - tempImg = Image.new(self.image.mode, (self.image.size[0] + diff, self.image.size[1]), fill) - if toRight: - tempImg.paste(self.image, (diff, 0)) - else: - tempImg.paste(self.image, (0, 0)) - self.image = tempImg - else: - diff = int(self.image.size[1] * ratioDev) - self.image.size[0] - self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill) + diff = int(self.image.size[1] * ratioDev) - self.image.size[0] + self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill) elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev: diff = int(self.image.size[0] / ratioDev) - self.image.size[1] self.image = ImageOps.expand(self.image, border=(0, diff / 2), fill=fill) self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5)) + if generateBorder: + if fill == 'white': + border = ImageOps.invert(self.image).getbbox() + else: + border = self.image.getbbox() + if border is not None: + if (border[2]-border[0])*1.5 < self.size[0]: + self.noHPV = True + if (border[3]-border[1])*1.5 < self.size[1]: + self.noVPV = True + self.border = [int(round(float(border[0])/float(self.image.size[0])*100, 2)*100*1.5), + int(round(float(border[1])/float(self.image.size[1])*100, 2)*100*1.5)] + else: + self.border = [0, 0] + self.noHPV = True + self.noVPV = True return self.image def splitPage(self, targetdir, righttoleft=False, rotate=False): width, height = self.image.size dstwidth, dstheight = self.size - #print "Image is %d x %d" % (width,height) - # only split if origin is not oriented the same as target + # Only split if origin is not oriented the same as target if (width > height) != (dstwidth > dstheight): if rotate: self.image = self.image.rotate(90) - return "R" + self.rotated = True + return None else: + self.rotated = False if width > height: - # source is landscape, so split by the width + # Source is landscape, so split by the width leftbox = (0, 0, width / 2, height) rightbox = (width / 2, 0, width, height) else: - # source is portrait and target is landscape, so split by the height + # Source is portrait and target is landscape, so split by the height leftbox = (0, 0, width, height / 2) rightbox = (0, height / 2, width, height) filename = os.path.splitext(self.filename) @@ -256,6 +318,7 @@ class ComicPage: raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e)) return fileone, filetwo else: + self.rotated = False return None def cutPageNumber(self): @@ -351,4 +414,48 @@ class ComicPage: # print "Right crop: %s"%diff self.image = self.image.crop((0, 0, widthImg - diff, heightImg)) # print "New size: %sx%s"%(self.image.size[0],self.image.size[1]) - return self.image \ No newline at end of file + return self.image + + def getImageHistogram(self, image): + histogram = image.histogram() + RBGW = [] + pixelCount = 0 + for i in range(256): + pixelCount += histogram[i] + histogram[256 + i] + histogram[512 + i] + RBGW.append(histogram[i] + histogram[256 + i] + histogram[512 + i]) + white = 0 + black = 0 + for i in range(245, 256): + white += RBGW[i] + for i in range(11): + black += RBGW[i] + if black > white and black > pixelCount*0.5: + return True + else: + return False + + def getImageFill(self, isWebToon): + fill = 0 + if isWebToon or self.rotated: + fill += self.getImageHistogram(self.image.crop((0, 0, self.image.size[0], 5))) + fill += self.getImageHistogram(self.image.crop((0, self.image.size[1]-5, self.image.size[0], + self.image.size[1]))) + else: + fill += self.getImageHistogram(self.image.crop((0, 0, 5, self.image.size[1]))) + fill += self.getImageHistogram(self.image.crop((self.image.size[0]-5, 0, self.image.size[0], + self.image.size[1]))) + if fill == 2: + self.fill = 'black' + elif fill == 0: + self.fill = 'white' + else: + fill = 0 + fill += self.getImageHistogram(self.image.crop((0, 0, 5, 5))) + fill += self.getImageHistogram(self.image.crop((self.image.size[0]-5, 0, self.image.size[0], 5))) + fill += self.getImageHistogram(self.image.crop((0, self.image.size[1]-5, 5, self.image.size[1]))) + fill += self.getImageHistogram(self.image.crop((self.image.size[0]-5, self.image.size[1]-5, + self.image.size[0], self.image.size[1]))) + if fill > 1: + self.fill = 'black' + else: + self.fill = 'white' \ No newline at end of file diff --git a/kcc/kindlesplit.py b/kcc/kindlesplit.py new file mode 100644 index 0000000..c8ae74d --- /dev/null +++ b/kcc/kindlesplit.py @@ -0,0 +1,384 @@ +# Based on initial version of KindleUnpack. Copyright (C) 2009 Charles M. Hannum +# Improvements Copyright (C) 2009-2012 P. Durrant, K. Hendricks, S. Siebert, fandrieu, DiapDealer, nickredding +# Copyright (C) 2013 Pawel Jastrzebski +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +__license__ = 'ISC' +__copyright__ = '2012-2013, Ciro Mattia Gonano , Pawel Jastrzebski ' +__docformat__ = 'restructuredtext en' + +import struct +# from uuid import uuid4 + +# important pdb header offsets +unique_id_seed = 68 +number_of_pdb_records = 76 + +# important palmdoc header offsets +book_length = 4 +book_record_count = 8 +first_pdb_record = 78 + +# important rec0 offsets +length_of_book = 4 +mobi_header_base = 16 +mobi_header_length = 20 +mobi_type = 24 +mobi_version = 36 +first_non_text = 80 +title_offset = 84 +first_image_record = 108 +first_content_index = 192 +last_content_index = 194 +kf8_last_content_index = 192 # for KF8 mobi headers +fcis_index = 200 +flis_index = 208 +srcs_index = 224 +srcs_count = 228 +primary_index = 244 +datp_index = 256 +huffoff = 112 +hufftbloff = 120 + + +def getint(datain, ofs, sz='L'): + i, = struct.unpack_from('>'+sz, datain, ofs) + return i + + +def writeint(datain, ofs, n, length='L'): + if length == 'L': + return datain[:ofs]+struct.pack('>L', n)+datain[ofs+4:] + else: + return datain[:ofs]+struct.pack('>H', n)+datain[ofs+2:] + + +def getsecaddr(datain, secno): + nsec = getint(datain, number_of_pdb_records, 'H') + assert secno >= 0 & secno < nsec, 'secno %d out of range (nsec=%d)' % (secno, nsec) + secstart = getint(datain, first_pdb_record+secno*8) + if secno == nsec-1: + secend = len(datain) + else: + secend = getint(datain, first_pdb_record+(secno+1)*8) + return secstart, secend + + +def readsection(datain, secno): + secstart, secend = getsecaddr(datain, secno) + return datain[secstart:secend] + + +def writesection(datain, secno, secdata): # overwrite, accounting for different length + dataout = deletesectionrange(datain, secno, secno) + return insertsection(dataout, secno, secdata) + + +def nullsection(datain, secno): # make it zero-length without deleting it + datalst = [] + nsec = getint(datain, number_of_pdb_records, 'H') + secstart, secend = getsecaddr(datain, secno) + zerosecstart, zerosecend = getsecaddr(datain, 0) + dif = secend-secstart + datalst.append(datain[:first_pdb_record]) + for i in range(0, secno+1): + ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8) + datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval)) + for i in range(secno+1, nsec): + ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8) + ofs -= dif + datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval)) + lpad = zerosecstart - (first_pdb_record + 8*nsec) + if lpad > 0: + datalst.append('\0' * lpad) + datalst.append(datain[zerosecstart: secstart]) + datalst.append(datain[secend:]) + dataout = "".join(datalst) + return dataout + + +def deletesectionrange(datain, firstsec, lastsec): # delete a range of sections + datalst = [] + firstsecstart, firstsecend = getsecaddr(datain, firstsec) + lastsecstart, lastsecend = getsecaddr(datain, lastsec) + zerosecstart, zerosecend = getsecaddr(datain, 0) + dif = lastsecend - firstsecstart + 8*(lastsec-firstsec+1) + nsec = getint(datain, number_of_pdb_records, 'H') + datalst.append(datain[:unique_id_seed]) + datalst.append(struct.pack('>L', 2*(nsec-(lastsec-firstsec+1))+1)) + datalst.append(datain[unique_id_seed+4:number_of_pdb_records]) + datalst.append(struct.pack('>H', nsec-(lastsec-firstsec+1))) + newstart = zerosecstart - 8*(lastsec-firstsec+1) + for i in range(0, firstsec): + ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8) + ofs -= 8 * (lastsec - firstsec + 1) + datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval)) + for i in range(lastsec+1, nsec): + ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8) + ofs -= dif + flgval = 2*(i-(lastsec-firstsec+1)) + datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval)) + lpad = newstart - (first_pdb_record + 8*(nsec - (lastsec - firstsec + 1))) + if lpad > 0: + datalst.append('\0' * lpad) + datalst.append(datain[zerosecstart:firstsecstart]) + datalst.append(datain[lastsecend:]) + dataout = "".join(datalst) + return dataout + + +def insertsection(datain, secno, secdata): # insert a new section + datalst = [] + nsec = getint(datain, number_of_pdb_records, 'H') + secstart, secend = getsecaddr(datain, secno) + zerosecstart, zerosecend = getsecaddr(datain, 0) + dif = len(secdata) + datalst.append(datain[:unique_id_seed]) + datalst.append(struct.pack('>L', 2*(nsec+1)+1)) + datalst.append(datain[unique_id_seed+4:number_of_pdb_records]) + datalst.append(struct.pack('>H', nsec+1)) + newstart = zerosecstart + 8 + for i in range(0, secno): + ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8) + ofs += 8 + datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval)) + datalst.append(struct.pack('>L', secstart + 8) + struct.pack('>L', (2*secno))) + for i in range(secno, nsec): + ofs, flgval = struct.unpack_from('>2L', datain, first_pdb_record+i*8) + ofs = ofs + dif + 8 + flgval = 2*(i+1) + datalst.append(struct.pack('>L', ofs) + struct.pack('>L', flgval)) + lpad = newstart - (first_pdb_record + 8*(nsec + 1)) + if lpad > 0: + datalst.append('\0' * lpad) + datalst.append(datain[zerosecstart:secstart]) + datalst.append(secdata) + datalst.append(datain[secstart:]) + dataout = "".join(datalst) + return dataout + + +def insertsectionrange(sectionsource, firstsec, lastsec, sectiontarget, targetsec): # insert a range of sections + dataout = sectiontarget + for idx in range(lastsec, firstsec-1, -1): + dataout = insertsection(dataout, targetsec, readsection(sectionsource, idx)) + return dataout + + +def get_exth_params(rec0): + ebase = mobi_header_base + getint(rec0, mobi_header_length) + elen = getint(rec0, ebase+4) + enum = getint(rec0, ebase+8) + return ebase, elen, enum + + +def add_exth(rec0, exth_num, exth_bytes): + ebase, elen, enum = get_exth_params(rec0) + newrecsize = 8+len(exth_bytes) + newrec0 = rec0[0:ebase+4]+struct.pack('>L', elen+newrecsize)+struct.pack('>L', enum+1) +\ + struct.pack('>L', exth_num) + struct.pack('>L', newrecsize)+exth_bytes+rec0[ebase+12:] + newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)+newrecsize) + return newrec0 + + +def read_exth(rec0, exth_num): + exth_values = [] + ebase, elen, enum = get_exth_params(rec0) + ebase += 12 + while enum > 0: + exth_id = getint(rec0, ebase) + if exth_id == exth_num: + # We might have multiple exths, so build a list. + exth_values.append(rec0[ebase+8:ebase+getint(rec0, ebase+4)]) + enum -= 1 + ebase = ebase+getint(rec0, ebase+4) + return exth_values + + +def write_exth(rec0, exth_num, exth_bytes): + ebase, elen, enum = get_exth_params(rec0) + ebase_idx = ebase+12 + enum_idx = enum + while enum_idx > 0: + exth_id = getint(rec0, ebase_idx) + if exth_id == exth_num: + dif = len(exth_bytes)+8-getint(rec0, ebase_idx+4) + newrec0 = rec0 + if dif != 0: + newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)+dif) + return newrec0[:ebase+4]+struct.pack('>L', elen+len(exth_bytes)+8-getint(rec0, ebase_idx+4)) +\ + struct.pack('>L', enum)+rec0[ebase+12:ebase_idx+4] +\ + struct.pack('>L', len(exth_bytes)+8)+exth_bytes +\ + rec0[ebase_idx+getint(rec0, ebase_idx+4):] + enum_idx -= 1 + ebase_idx = ebase_idx+getint(rec0, ebase_idx+4) + return rec0 + + +def del_exth(rec0, exth_num): + ebase, elen, enum = get_exth_params(rec0) + ebase_idx = ebase+12 + enum_idx = 0 + while enum_idx < enum: + exth_id = getint(rec0, ebase_idx) + exth_size = getint(rec0, ebase_idx+4) + if exth_id == exth_num: + newrec0 = rec0 + newrec0 = writeint(newrec0, title_offset, getint(newrec0, title_offset)-exth_size) + newrec0 = newrec0[:ebase_idx]+newrec0[ebase_idx+exth_size:] + newrec0 = newrec0[0:ebase+4]+struct.pack('>L', elen-exth_size)+struct.pack('>L', enum-1)+newrec0[ebase+12:] + return newrec0 + enum_idx += 1 + ebase_idx = ebase_idx+exth_size + return rec0 + + +class mobi_split: + def __init__(self, infile, newKindle): + try: + datain = open(infile, 'rb').read() + datain_rec0 = readsection(datain, 0) + ver = getint(datain_rec0, mobi_version) + # fake_asin = str(uuid4()) + self.combo = (ver != 8) + if not self.combo: + return + exth121 = read_exth(datain_rec0, 121) + if len(exth121) == 0: + self.combo = False + return + else: + # only pay attention to first exth121 + # (there should only be one) + datain_kf8, = struct.unpack_from('>L', exth121[0], 0) + if datain_kf8 == 0xffffffff: + self.combo = False + return + datain_kfrec0 = readsection(datain, datain_kf8) + firstimage = getint(datain_rec0, first_image_record) + lastimage = getint(datain_rec0, last_content_index, 'H') + + if not newKindle: + # create the standalone mobi7 + num_sec = getint(datain, number_of_pdb_records, 'H') + # remove BOUNDARY up to but not including ELF record + self.result_file = deletesectionrange(datain, datain_kf8-1, num_sec-2) + # check if there are SRCS records and delete them + srcs = getint(datain_rec0, srcs_index) + num_srcs = getint(datain_rec0, srcs_count) + if srcs != 0xffffffff and num_srcs > 0: + self.result_file = deletesectionrange(self.result_file, srcs, srcs+num_srcs-1) + datain_rec0 = writeint(datain_rec0, srcs_index, 0xffffffff) + datain_rec0 = writeint(datain_rec0, srcs_count, 0) + # reset the EXTH 121 KF8 Boundary meta data to 0xffffffff + datain_rec0 = write_exth(datain_rec0, 121, struct.pack('>L', 0xffffffff)) + # datain_rec0 = del_exth(datain_rec0,121) + # datain_rec0 = del_exth(datain_rec0,534) + # don't remove the EXTH 125 KF8 Count of Resources, seems to be present in mobi6 files as well + # set the EXTH 129 KF8 Masthead / Cover Image string to the null string + datain_rec0 = write_exth(datain_rec0, 129, '') + # don't remove the EXTH 131 KF8 Unidentified Count, seems to be present in mobi6 files as well + + # Make sure we have an ASIN & cdeType set... + # if len(read_exth(datain_rec0, 113)) == 0: + # datain_rec0 = add_exth(datain_rec0, 113, fake_asin) + # if len(read_exth(datain_rec0, 504)) == 0: + # datain_rec0 = add_exth(datain_rec0, 504, fake_asin) + if len(read_exth(datain_rec0, 501)) == 0: + datain_rec0 = add_exth(datain_rec0, 501, b'EBOK') + + # need to reset flags stored in 0x80-0x83 + # old mobi with exth: 0x50, mobi7 part with exth: 0x1850, mobi8 part with exth: 0x1050 + # Bit Flags + # 0x1000 = Bit 12 indicates if embedded fonts are used or not + # 0x0800 = means this Header points to *shared* images/resource/fonts ?? + # 0x0080 = unknown new flag, why is this now being set by Kindlegen 2.8? + # 0x0040 = exth exists + # 0x0010 = Not sure but this is always set so far + fval, = struct.unpack_from('>L', datain_rec0, 0x80) + # need to remove flag 0x0800 for KindlePreviewer 2.8 and unset Bit 12 for embedded fonts + fval &= 0x07FF + datain_rec0 = datain_rec0[:0x80] + struct.pack('>L', fval) + datain_rec0[0x84:] + self.result_file = writesection(self.result_file, 0, datain_rec0) + if lastimage == 0xffff: + # find the lowest of the next sections and copy up to that. + ofs_list = [(kf8_last_content_index, 'L'), (fcis_index, 'L'), (flis_index, 'L'), (datp_index, 'L'), + (hufftbloff, 'L')] + for ofs, sz in ofs_list: + n = getint(datain_kfrec0, ofs, sz) + if 0 < n < lastimage: + lastimage = n-1 + + # Try to null out FONT and RES, but leave the (empty) PDB record so image refs remain valid + for i in range(firstimage, lastimage): + imgsec = readsection(self.result_file, i) + if imgsec[0:4] in ['RESC', 'FONT']: + self.result_file = nullsection(self.result_file, i) + # mobi7 finished + else: + # create standalone mobi8 + self.result_file = deletesectionrange(datain, 0, datain_kf8-1) + target = getint(datain_kfrec0, first_image_record) + self.result_file = insertsectionrange(datain, firstimage, lastimage, self.result_file, target) + datain_kfrec0 = readsection(self.result_file, 0) + + # Only keep the correct EXTH 116 StartOffset, KG 2.5 carries over the one from the mobi7 part, + # which then points at garbage in the mobi8 part, and confuses FW 3.4 + kf8starts = read_exth(datain_kfrec0, 116) + # If we have multiple StartOffset, keep only the last one + kf8start_count = len(kf8starts) + while kf8start_count > 1: + kf8start_count -= 1 + datain_kfrec0 = del_exth(datain_kfrec0, 116) + + # update the EXTH 125 KF8 Count of Images/Fonts/Resources + datain_kfrec0 = write_exth(datain_kfrec0, 125, struct.pack('>L', lastimage-firstimage+1)) + + # Same dance for the KF8, we want an ASIN & cdeType :) + # if len(read_exth(datain_kfrec0, 113)) == 0: + # datain_kfrec0 = add_exth(datain_kfrec0, 113, fake_asin) + # if len(read_exth(datain_kfrec0, 504)) == 0: + # datain_kfrec0 = add_exth(datain_kfrec0, 504, fake_asin) + if len(read_exth(datain_kfrec0, 501)) == 0: + datain_kfrec0 = add_exth(datain_kfrec0, 501, b'EBOK') + + # need to reset flags stored in 0x80-0x83 + # old mobi with exth: 0x50, mobi7 part with exth: 0x1850, mobi8 part with exth: 0x1050 + # standalone mobi8 with exth: 0x0050 + # Bit Flags + # 0x1000 = Bit 12 indicates if embedded fonts are used or not + # 0x0800 = means this Header points to *shared* images/resource/fonts ?? + # 0x0080 = unknown new flag, why is this now being set by Kindlegen 2.8? + # 0x0040 = exth exists + # 0x0010 = Not sure but this is always set so far + fval, = struct.unpack_from('>L', datain_kfrec0, 0x80) + fval &= 0x1FFF + fval |= 0x0800 + datain_kfrec0 = datain_kfrec0[:0x80] + struct.pack('>L', fval) + datain_kfrec0[0x84:] + + # properly update other index pointers that have been shifted by the insertion of images + ofs_list = [(kf8_last_content_index, 'L'), (fcis_index, 'L'), (flis_index, 'L'), (datp_index, 'L'), + (hufftbloff, 'L')] + for ofs, sz in ofs_list: + n = getint(datain_kfrec0, ofs, sz) + if n != 0xffffffff: + datain_kfrec0 = writeint(datain_kfrec0, ofs, n+lastimage-firstimage+1, sz) + self.result_file = writesection(self.result_file, 0, datain_kfrec0) + # mobi8 finished + except Exception: + raise + + def getResult(self): + return self.result_file \ No newline at end of file diff --git a/kcc/kindlestrip.py b/kcc/kindlestrip.py deleted file mode 100755 index 4aea003..0000000 --- a/kcc/kindlestrip.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# This script strips the penultimate record from a Mobipocket file. -# This is useful because the current KindleGen add a compressed copy -# of the source files used in this record, making the ebook produced -# about twice as big as it needs to be. -# -# -# This is free and unencumbered software released into the public domain. -# -# Anyone is free to copy, modify, publish, use, compile, sell, or -# distribute this software, either in source code form or as a compiled -# binary, for any purpose, commercial or non-commercial, and by any -# means. -# -# In jurisdictions that recognize copyright laws, the author or authors -# of this software dedicate any and all copyright interest in the -# software to the public domain. We make this dedication for the benefit -# of the public at large and to the detriment of our heirs and -# successors. We intend this dedication to be an overt act of -# relinquishment in perpetuity of all present and future rights to this -# software under copyright law. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -# -# For more information, please refer to -# -# Written by Paul Durrant, 2010-2011, paul@durrant.co.uk, pdurrant on mobileread.com -# With enhancements by Kevin Hendricks, KevinH on mobileread.com -# -# Changelog -# 1.00 - Initial version -# 1.10 - Added an option to output the stripped data -# 1.20 - Added check for source files section (thanks Piquan) -# 1.30 - Added prelim Support for K8 style mobis -# 1.31 - removed the SRCS section but kept a 0 size entry for it -# 1.32 - removes the SRCS section and its entry, now updates metadata 121 if needed -# 1.33 - now uses and modifies mobiheader SRCS and CNT -# 1.34 - added credit for Kevin Hendricks -# 1.35 - fixed bug when more than one compilation (SRCS/CMET) records - -__version__ = '1.35' - -import sys -import struct -import binascii - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - - -class StripException(Exception): - pass - - -class SectionStripper: - def loadSection(self, section): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - return self.data_file[off:endoff] - - def patch(self, off, new): - self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] - - def strip(self, off, len): - self.data_file = self.data_file[:off] + self.data_file[off+len:] - - def patchSection(self, section, new, in_off = 0): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - assert off + in_off + len(new) <= endoff - self.patch(off + in_off, new) - - def updateEXTH121(self, srcs_secnum, srcs_cnt, mobiheader): - mobi_length, = struct.unpack('>L',mobiheader[0x14:0x18]) - exth_flag, = struct.unpack('>L', mobiheader[0x80:0x84]) - exth = 'NONE' - try: - if exth_flag & 0x40: - exth = mobiheader[16 + mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - # print type, size - if type == 121: - boundaryptr, =struct.unpack('>L',exth[pos+8: pos + size]) - if srcs_secnum <= boundaryptr: - boundaryptr -= srcs_cnt - prefix = mobiheader[0:16 + mobi_length + pos + 8] - suffix = mobiheader[16 + mobi_length + pos + 8 + 4:] - nval = struct.pack('>L',boundaryptr) - mobiheader = prefix + nval + suffix - pos += size - except: - pass - return mobiheader - - def __init__(self, datain): - if datain[0x3C:0x3C+8] != 'BOOKMOBI': - raise StripException("invalid file format") - self.num_sections, = struct.unpack('>H', datain[76:78]) - - # get mobiheader and check SRCS section number and count - offset0, = struct.unpack_from('>L', datain, 78) - offset1, = struct.unpack_from('>L', datain, 86) - mobiheader = datain[offset0:offset1] - srcs_secnum, srcs_cnt = struct.unpack_from('>2L', mobiheader, 0xe0) - if srcs_secnum == 0xffffffff or srcs_cnt == 0: - raise StripException("File doesn't contain the sources section.") - - print "Found SRCS section number %d, and count %d" % (srcs_secnum, srcs_cnt) - # find its offset and length - next = srcs_secnum + srcs_cnt - srcs_offset, flgval = struct.unpack_from('>2L', datain, 78+(srcs_secnum*8)) - next_offset, flgval = struct.unpack_from('>2L', datain, 78+(next*8)) - srcs_length = next_offset - srcs_offset - if datain[srcs_offset:srcs_offset+4] != 'SRCS': - raise StripException("SRCS section num does not point to SRCS.") - print " beginning at offset %0x and ending at offset %0x" % (srcs_offset, srcs_length) - - # it appears bytes 68-71 always contain (2*num_sections) + 1 - # this is not documented anyplace at all but it appears to be some sort of next - # available unique_id used to identify specific sections in the palm db - self.data_file = datain[:68] + struct.pack('>L',((self.num_sections-srcs_cnt)*2+1)) - self.data_file += datain[72:76] - - # write out the number of sections reduced by srtcs_cnt - self.data_file = self.data_file + struct.pack('>H',self.num_sections-srcs_cnt) - - # we are going to remove srcs_cnt SRCS sections so the offset of every entry in the table - # up to the srcs secnum must begin 8 bytes earlier per section removed (each table entry is 8 ) - delta = -8 * srcs_cnt - for i in xrange(srcs_secnum): - offset, flgval = struct.unpack_from('>2L', datain, 78+(i*8)) - offset += delta - self.data_file += struct.pack('>L',offset) + struct.pack('>L',flgval) - - # for every record after the srcs_cnt SRCS records we must start it - # earlier by 8*srcs_cnt + the length of the srcs sections themselves) - delta = delta - srcs_length - for i in xrange(srcs_secnum+srcs_cnt,self.num_sections): - offset, flgval = struct.unpack_from('>2L', datain, 78+(i*8)) - offset += delta - flgval = 2 * (i - srcs_cnt) - self.data_file += struct.pack('>L',offset) + struct.pack('>L',flgval) - - # now pad it out to begin right at the first offset - # typically this is 2 bytes of nulls - first_offset, flgval = struct.unpack_from('>2L', self.data_file, 78) - self.data_file += '\0' * (first_offset - len(self.data_file)) - - # now finally add on every thing up to the original src_offset - self.data_file += datain[offset0: srcs_offset] - - # and everything afterwards - self.data_file += datain[srcs_offset+srcs_length:] - - #store away the SRCS section in case the user wants it output - self.stripped_data_header = datain[srcs_offset:srcs_offset+16] - self.stripped_data = datain[srcs_offset+16:srcs_offset+srcs_length] - - # update the number of sections count - self.num_section = self.num_sections - srcs_cnt - - # update the srcs_secnum and srcs_cnt in the mobiheader - offset0, flgval0 = struct.unpack_from('>2L', self.data_file, 78) - offset1, flgval1 = struct.unpack_from('>2L', self.data_file, 86) - mobiheader = self.data_file[offset0:offset1] - mobiheader = mobiheader[:0xe0]+ struct.pack('>L', 0xffffffff) + struct.pack('>L', 0) + mobiheader[0xe8:] - - # if K8 mobi, handle metadata 121 in old mobiheader - mobiheader = self.updateEXTH121(srcs_secnum, srcs_cnt, mobiheader) - self.data_file = self.data_file[0:offset0] + mobiheader + self.data_file[offset1:] - print "done" - - def getResult(self): - return self.data_file - - def getStrippedData(self): - return self.stripped_data - - def getHeader(self): - return self.stripped_data_header - -def main(argv=None): - infile = argv[0] - outfile = argv[1] - data_file = file(infile, 'rb').read() - try: - strippedFile = SectionStripper(data_file) - file(outfile, 'wb').write(strippedFile.getResult()) - print "Header Bytes: " + binascii.b2a_hex(strippedFile.getHeader()) - if len(argv)==3: - file(argv[2], 'wb').write(strippedFile.getStrippedData()) - except StripException, e: - print "Error: %s" % e - sys.exit(1) - -if __name__ == "__main__": - sys.stdout=Unbuffered(sys.stdout) - print ('KindleStrip v%(__version__)s. ' - 'Written 2010-2012 by Paul Durrant and Kevin Hendricks.' % globals()) - if len(sys.argv)<3 or len(sys.argv)>4: - print "Strips the Sources record from Mobipocket ebooks" - print "For ebooks generated using KindleGen 1.1 and later that add the source" - print "Usage:" - print " %s " % sys.argv[0] - print " is optional." - sys.exit(1) - else: - main(sys.argv[1:]) - sys.exit(0) diff --git a/kcc/pdfjpgextract.py b/kcc/pdfjpgextract.py index dd5f067..d9dad09 100644 --- a/kcc/pdfjpgextract.py +++ b/kcc/pdfjpgextract.py @@ -1,4 +1,5 @@ -# Copyright (c) 2012 Ciro Mattia Gonano +# Copyright (c) 2012-2013 Ciro Mattia Gonano +# Copyright (c) 2013 Pawel Jastrzebski # # Based upon the code snippet by Ned Batchelder # (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html) diff --git a/other/Additional-LICENSE.txt b/other/Additional-LICENSE.txt new file mode 100644 index 0000000..891d453 --- /dev/null +++ b/other/Additional-LICENSE.txt @@ -0,0 +1,91 @@ + ****** ***** ****** UnRAR - free utility for RAR archives + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ****** ******* ****** License for use and distribution of + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ** ** ** ** ** ** FREEWARE version + ~~~~~~~~~~~~~~~~ + + The UnRAR utility is freeware. This means: + + 1. All copyrights to RAR and the utility UnRAR are exclusively + owned by the author - Alexander Roshal. + + 2. The UnRAR utility may be freely distributed. It is allowed + to distribute UnRAR inside of other software packages. + + 3. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS". + NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT + YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, + DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING + OR MISUSING THIS SOFTWARE. + + 4. Neither RAR binary code, WinRAR binary code, UnRAR source or UnRAR + binary code may be used or reverse engineered to re-create the RAR + compression algorithm, which is proprietary, without written + permission of the author. + + 5. If you don't agree with terms of the license you must remove + UnRAR files from your storage devices and cease to use the + utility. + + Thank you for your interest in RAR and UnRAR. + + + Alexander L. Roshal + + 7-Zip + ~~~~~ + License for use and distribution + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 7-Zip Copyright (C) 1999-2012 Igor Pavlov. + + Licenses for files are: + + 1) 7z.dll: GNU LGPL + unRAR restriction + 2) All other files: GNU LGPL + + The GNU LGPL + unRAR restriction means that you must follow both + GNU LGPL rules and unRAR restriction rules. + + + Note: + You can use 7-Zip on any computer, including a computer in a commercial + organization. You don't need to register or pay for 7-Zip. + + + GNU LGPL information + -------------------- + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You can receive a copy of the GNU Lesser General Public License from + http://www.gnu.org/ + + + unRAR restriction + ----------------- + + The decompression engine for RAR archives was developed using source + code of unRAR program. + All copyrights to original unRAR code are owned by Alexander Roshal. + + The license for original unRAR code has the following restriction: + + The unRAR sources cannot be used to re-create the RAR compression algorithm, + which is proprietary. Distribution of modified unRAR sources in separate form + or as a part of other software is permitted, provided that it is clearly + stated in the documentation and source comments that the code may + not be used to develop a RAR (WinRAR) compatible archiver. + + + -- + Igor Pavlov \ No newline at end of file diff --git a/setup.py b/setup.py index c17d4e6..35edd24 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ Usage (Windows): from sys import platform NAME = "KindleComicConverter" -VERSION = "3.2.1" +VERSION = "3.3" MAIN = "kcc.py" if platform == "darwin": @@ -41,7 +41,11 @@ elif platform == "win32": from cx_Freeze import setup, Executable base = "Win32GUI" extra_options = dict( - options={"build_exe": {"include_files": ['LICENSE.txt'], "compressed": True}}, + options={"build_exe": {"include_files": ['LICENSE.txt', + ['other/UnRAR.exe', 'UnRAR.exe'], + ['other/7za.exe', '7za.exe'], + ['other/Additional-LICENSE.txt', 'Additional-LICENSE.txt'] + ], "compressed": True}}, executables=[Executable(MAIN, base=base, targetName="KCC.exe",