diff --git a/.gitignore b/.gitignore index d10b6b6..ae3a8a2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist *.pyo *.mngl *.cbz +*.bat diff --git a/README.md b/README.md index 5068319..42cbdda 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD ## Downloads - [Mangle for Windows](ftp://foosoft.net/releases/mangle/mangle_win.zip) - [Mangle for MacOS (Old)](ftp://foosoft.net/releases/mangle/mangle_osx.zip) @@ -8,4 +9,101 @@ - [PyQT4](http://www.riverbankcomputing.com/software/pyqt/download) - [ReportLab](https://pypi.python.org/pypi/reportlab) - [natsort](https://pypi.python.org/pypi/natsort/3.0.1) -- [py2exe](http://www.py2exe.org/) (optional, for Windows distribution only) \ No newline at end of file +- [py2exe](http://www.py2exe.org/) (optional, for Windows distribution only) +======= +# Mangle # + +Many years ago I received an [Amazon Kindle](http://en.wikipedia.org/wiki/Kindle) gift. I immediately began playing +around with it and reading about certain undocumented features that the Kindle has to offer. After a couple of hours I +discovered it to be the perfect device for reading [Manga](http://en.wikipedia.org/wiki/Manga) is almost always +grayscale, and the aspect ratio fits the Kindle's 600x800 pixel screen almost perfectly. Better yet, the Kindle's +undocumented image viewer actually keeps track of the last image you viewed and thus you are always able to return to +the page you left off on when you power on your Kindle. The device supports several popular image formats (jpeg, png, +gif, etc), and is able to dither and downscale images to fit the screen. + +However... The Kindle's image viewer does have certain shortcomings: + +* The Kindle is very picky about file format; any additional embedded data (thumbnails, comments, possibly even exif + data) can confuse it. As a result, images may not display properly or even not at all (which actually prevents you + from reading the given book, as one bad panel will prevent you from viewing subsequent images). +* The first image that you view in a Manga (until the Kindle first writes the "bookmark" file) seems to be arbitrary + even when files are named sequentially. About half the time it will correctly pick the first file in the batch, at + other times it will pick out some other image seemingly at random. +* Normally for Kindle to find your Manga scans you have to press Alt+Z on the home screen. I haven't always had luck + with it correctly identifying image directories. At other times, after finding an image directory the Kindle will + appear to hang while trying to access it (forcing you to return to the home screen). +* The Kindle image viewer has no functionality to rotate images. So if there is a horizontally large image (such as + what often happens with dual-page scans), it can be difficult to make out the text because the image is simply + scaled to fit (consequently leaving a lot of wasted space at the bottom of the screen). +* Scanlation images are oftentimes much larger than the 600x800 screen; not only does this make them take more space + on your memory card but it also slows down image loading (the Kindle has to read more data off of the slow SD card + and scale the image). Scanlations often also include color scans of covers and inserts which take up more space than + a grayscale equivalent (which is would be fine for the Kindle's limited display). +* Kindle's image viewer provides no way to sort images (to determine in which order they are shown). This can be very + problematic especially considering that scanlation groups have differing naming conventions, and as a result files + from later chapters may appear before earlier ones when you are reading your Manga (spoilers ftl). + +I was annoyed with these issues and thus Mangle was born (the program name is a mix of "Manga" and "Kindle" in case you +haven't figured it out yet; I thought it was pretty clever at the time). Fortunately you can get all the benefits of my +work without really doing anything (and it won't even cost you anything since Mangle is free, +[GPL](http://www.gnu.org/licenses/gpl-3.0.txt) software. With Mangle you can easily: + +* Sort and organize images from different directories; bulk rename feature for output to the Kindle. +* Optionally re-save images in a format Kindle will be sure to understand with no visible quality loss. +* Downsample and rotate images for optimal viewing on Kindle, convert to grayscale to save space and improve contrast. +* Automatically generate book meta-data so that your Manga is always properly detected and viewable in-order. + +Here is a recent screenshot showing off some of the export options that you can configure on a per-book basis in Mangle: + +![Mangle options dialog](http://foosoft.net/projects/mangle/img/options.png) + +You can also check out what Mangle output looks like on the Kindle on the [action +shots](http://foosoft.net/projects/mangle/action/) page. + +Mangle is cross platform, and doesn't require an install (it's a standalone executable that you can run from anywhere). +It is also "environmentally friendly" by not messing with your registry or modifying your system in any way. If you +ever want to uninstall it, just delete the executable and you're done. + +## Usage Instructions ## + +Mangle is pretty easy to use, so this won't be really in-depth. If you have any questions drop me a line though. + +1. Add images to the current book by selecting the `Book | Add | Files` or `Book | Add | Directory` menu items. +2. If certain images are not in the order you want, select them in the window, and select the `Book | Shift | Up` or + `Book | Shift | Down` menu items. +3. Configure the book title and image processing options by selecting `Book | Options`; this will be the title you see + in the Kindle home menu. +4. Create a root-level directory on your SD memory card/Kindle called `pictures` (case might matter). +5. Once you are satisfied with the your images and options select `Book | Export` and select the `pictures` directory + you just created. +6. After the export is complete your new Manga books will show up along with all your other books (if they don't for + some reason, press `Alt+Z` while on the home menu). + +## The Usual Disclaimer ## + +You probably know how this goes by now... Mess around with your Kindle at your own risk. Honestly, nothing bad is going +to happen; however if something *does* then it's your problem. + +## Running From Source ## + +Because Mangle is written in Python, a scripting language, it's trivial to get it up and running on the operating system +of your choice. First you should make sure that you have the required dependencies installed: + +* [PyQT4](http://www.riverbankcomputing.com/software/pyqt/download) +* [Python 2.7](http://www.python.org/download/releases/2.7/) +* [Python Imaging Library (PIL)](http://www.pythonware.com/products/pil/) +* [ReportLab](https://pypi.python.org/pypi/reportlab) +* [py2exe](http://www.py2exe.org/) (optional, for Windows distribution only) + +Now you can fetch the [latest version of the code](https://github.com/FooSoft/mangle/) and run the `mangle.pyw` script +to execute Mangle. + +## Downloads ## + +If you don't want to run Mangle from source, you can use the following pre-built binaries. As I don't have the means to +make MacOS X releases myself, I am providing the slightly out of date (and unsupported) package built by Rob White in +its place. Linux users should execute the Python scripts with the interpreter and libraries installed on their system. + +* [magnle_win.zip](http://dl.foosoft.net/mangle/mangle_win.zip) +* [mangle_osx.zip](http://dl.foosoft.net/mangle/mangle_osx.zip) +>>>>>>> cedfe3fc512a8f58680d8fa0618143d423c3112d diff --git a/mangle/book.py b/mangle/book.py index 1bc8c28..2079f8a 100644 --- a/mangle/book.py +++ b/mangle/book.py @@ -13,14 +13,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - +import re from os.path import basename import os.path import tempfile from zipfile import ZipFile from PyQt4 import QtGui, QtCore, QtXml, uic -from natsort import natsorted from about import DialogAbout from convert import DialogConvert @@ -29,6 +28,13 @@ from options import DialogOptions import util +# Sort function use to sort files in a natural order, by lowering +# characters, and manage multi levels of integers (tome 1/ page 1.jpg, etc etc) +def natural_key(string_): + """See http://www.codinghorror.com/blog/archives/001018.html""" + return [int(s) if s.isdigit() else s.lower() for s in re.split(r'(\d+)', string_)] + + class Book(object): DefaultDevice = 'Kindle Paperwhite' DefaultOutputFormat = 'CBZ only' @@ -367,14 +373,16 @@ class MainWindowBook(QtGui.QMainWindow): filenamesListed = [] for i in xrange(0, self.listWidgetFiles.count()): filenamesListed.append(self.listWidgetFiles.item(i).text()) - - for filename in natsorted(filenames): + + # Get files but in a natural sorted order + for filename in sorted(filenames, key=natural_key): if filename not in filenamesListed: filename = QtCore.QString(filename) self.listWidgetFiles.addItem(filename) self.book.images.append(filename) self.book.modified = True + def addImageDirs(self, directories): filenames = [] @@ -387,6 +395,7 @@ class MainWindowBook(QtGui.QMainWindow): self.addImageFiles(filenames) + def addCBZFiles(self, filenames): directories = [] tempDir = tempfile.gettempdir() diff --git a/mangle/convert.py b/mangle/convert.py index a9258fb..d9bdf8f 100644 --- a/mangle/convert.py +++ b/mangle/convert.py @@ -129,6 +129,14 @@ class DialogConvert(QtGui.QProgressDialog): # Change target once again for left page target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 1)) + # For right page (if requested), but in inverted mode + if(self.book.imageFlags & ImageFlags.SplitInverse): + # New path based on modified index + target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 0)) + self.convertAndSave(source, target, device, flags ^ ImageFlags.SplitInverse | ImageFlags.SplitLeft, archive, pdf) + # Change target once again for left page + target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 1)) + # Convert page self.convertAndSave(source, target, device, flags, archive, pdf) diff --git a/mangle/image.py b/mangle/image.py index edf2928..2ce185d 100644 --- a/mangle/image.py +++ b/mangle/image.py @@ -19,14 +19,17 @@ import os from PIL import Image, ImageDraw + class ImageFlags: Orient = 1 << 0 Resize = 1 << 1 Frame = 1 << 2 Quantize = 1 << 3 Stretch = 1 << 4 - Split = 1 << 5 - SplitRight = 1 << 6 + Split = 1 << 5 # split right then left + SplitRight = 1 << 6 # split only the right page + SplitLeft = 1 << 7 # split only the left page + SplitInverse = 1 << 8 # split left then right page class KindleData: @@ -82,22 +85,39 @@ class KindleData: 'Kindle DX': ((824, 1200), Palette15a), 'Kindle DXG': ((824, 1200), Palette15a), 'Kindle Touch': ((600, 800), Palette15a), - 'Kindle Paperwhite': ((758, 1024), Palette15b) # resolution given in manual, see http://kindle.s3.amazonaws.com/Kindle_Paperwhite_Users_Guide.pdf + 'Kindle Paperwhite': ((758, 1024), Palette15b), # resolution given in manual, see http://kindle.s3.amazonaws.com/Kindle_Paperwhite_Users_Guide.pdf + 'KoBo Aura H2o': ((1080, 1430), Palette15a), # resolution from http://www.fnac.com/Liseuse-Numerique-Kobo-by-Fnac-Kobo-Aura-H2O-Noir/a7745120/w-4 } + + +# decorate a function that use image, *** and if there +# is an exception raise by PIL (IOError) then return +# the original image because PIL cannot manage it +def protect_bad_image(func): + def func_wrapper(*args, **kwargs): + # If cannot convert (like a bogus image) return the original one + # args will be "image" and other params are after + try: + return func(*args, **kwargs) + except IOError: # Exception from PIL about bad image + return args[0] + return func_wrapper - + +@protect_bad_image def splitLeft(image): widthImg, heightImg = image.size - return image.crop((0, 0, widthImg / 2, heightImg)) +@protect_bad_image def splitRight(image): widthImg, heightImg = image.size - + return image.crop((widthImg / 2, 0, widthImg, heightImg)) +@protect_bad_image def quantizeImage(image, palette): colors = len(palette) / 3 if colors < 256: @@ -109,10 +129,14 @@ def quantizeImage(image, palette): return image.quantize(palette=palImg) +@protect_bad_image def stretchImage(image, size): widthDev, heightDev = size + return image.resize((widthDev, heightDev), Image.ANTIALIAS) + +@protect_bad_image def resizeImage(image, size): widthDev, heightDev = size widthImg, heightImg = image.size @@ -136,19 +160,21 @@ def resizeImage(image, size): return image.resize((widthImg, heightImg), Image.ANTIALIAS) +@protect_bad_image def formatImage(image): if image.mode == 'RGB': return image + return image.convert('RGB') +@protect_bad_image def orientImage(image, size): widthDev, heightDev = size widthImg, heightImg = image.size if (widthImg > heightImg) != (widthDev > heightDev): return image.rotate(90, Image.BICUBIC, True) - return image @@ -204,10 +230,20 @@ def convertImage(source, target, device, flags): # Format according to palette image = formatImage(image) # Apply flag transforms + + # Second pass of first split if flags & ImageFlags.SplitRight: image = splitRight(image) - if flags & ImageFlags.Split: + # First pass of first split option + if (flags & ImageFlags.Split): image = splitLeft(image) + # First pass of second splitting option + if flags & ImageFlags.SplitLeft: + image = splitLeft(image) + # second pass of second splitting option + if (flags & ImageFlags.SplitInverse): + image = splitRight(image) + if flags & ImageFlags.Orient: image = orientImage(image, size) if flags & ImageFlags.Resize: diff --git a/mangle/img/book.png b/mangle/img/book.png new file mode 100644 index 0000000..aee4342 Binary files /dev/null and b/mangle/img/book.png differ diff --git a/mangle/img/export_book.png b/mangle/img/export_book.png index 78525cc..f90be0a 100644 Binary files a/mangle/img/export_book.png and b/mangle/img/export_book.png differ diff --git a/mangle/options.py b/mangle/options.py index eb79f7f..eedfe88 100644 --- a/mangle/options.py +++ b/mangle/options.py @@ -66,6 +66,8 @@ class DialogOptions(QtGui.QDialog): imageFlags |= ImageFlags.Frame if self.checkboxSplit.isChecked(): imageFlags |= ImageFlags.Split + if self.checkboxSplitInverse.isChecked(): + imageFlags |= ImageFlags.SplitInverse modified = ( self.book.title != title or diff --git a/mangle/ui/book.ui b/mangle/ui/book.ui index a0121a4..8066c7c 100644 --- a/mangle/ui/book.ui +++ b/mangle/ui/book.ui @@ -180,6 +180,10 @@ + + + ../img/book.png../img/book.png + &Options... diff --git a/mangle/ui/options.ui b/mangle/ui/options.ui index 1c903ae..e5af54a 100644 --- a/mangle/ui/options.ui +++ b/mangle/ui/options.ui @@ -101,6 +101,11 @@ Kindle Paperwhite + + + KoBo Aura H2o + + @@ -170,6 +175,13 @@ Split images into two pages (right, left) + + + + + Split images into two pages (left, right) + + diff --git a/setup.py b/setup.py index 701ca74..1f774fc 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ setup( ('mangle/img', ['mangle/img/add_directory.png', 'mangle/img/add_file.png', 'mangle/img/banner_about.png', + 'mangle/img/book.png', 'mangle/img/export_book.png', 'mangle/img/file_new.png', 'mangle/img/file_open.png',