diff --git a/mangle/book.py b/mangle/book.py index 2079f8a..fe8edfa 100644 --- a/mangle/book.py +++ b/mangle/book.py @@ -30,27 +30,36 @@ 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) +# cf: See http://www.codinghorror.com/blog/archives/001018.html 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_)] + l = [] + for s in re.split(r'(\d+)', string_): + # QString do not have isdigit, so convert it if need + if isinstance(s, QtCore.QString): + s = unicode(s) + if s.isdigit(): + l.append(int(s)) + else: + l.append(s.lower()) + return l class Book(object): - DefaultDevice = 'Kindle Paperwhite' + DefaultDevice = 'Kindle Paperwhite' DefaultOutputFormat = 'CBZ only' - DefaultOverwrite = True - DefaultImageFlags = ImageFlags.Orient | ImageFlags.Resize | ImageFlags.Quantize + DefaultOverwrite = True + DefaultImageFlags = ImageFlags.Orient | ImageFlags.Resize | ImageFlags.Quantize def __init__(self): - self.images = [] - self.filename = None - self.modified = False - self.title = None - self.titleSet = False - self.device = Book.DefaultDevice - self.overwrite = Book.DefaultOverwrite - self.imageFlags = Book.DefaultImageFlags + self.images = [] + self.filename = None + self.modified = False + self.title = None + self.titleSet = False + self.device = Book.DefaultDevice + self.overwrite = Book.DefaultOverwrite + self.imageFlags = Book.DefaultImageFlags self.outputFormat = Book.DefaultOutputFormat @@ -101,14 +110,14 @@ class Book(object): if root.tagName() != 'book': raise RuntimeError('Unexpected book format in file %s' % filename) - self.title = root.attribute('title', 'Untitled') - self.overwrite = root.attribute('overwrite', 'true' if Book.DefaultOverwrite else 'false') == 'true' - self.device = root.attribute('device', Book.DefaultDevice) + self.title = root.attribute('title', 'Untitled') + self.overwrite = root.attribute('overwrite', 'true' if Book.DefaultOverwrite else 'false') == 'true' + self.device = root.attribute('device', Book.DefaultDevice) self.outputFormat = root.attribute('outputFormat', Book.DefaultOutputFormat) - self.imageFlags = int(root.attribute('imageFlags', str(Book.DefaultImageFlags))) - self.filename = filename - self.modified = False - self.images = [] + self.imageFlags = int(root.attribute('imageFlags', str(Book.DefaultImageFlags))) + self.filename = filename + self.modified = False + self.images = [] items = root.elementsByTagName('image') if items is None: diff --git a/mangle/cbz.py b/mangle/cbz.py index dc23583..e9c2bbc 100644 --- a/mangle/cbz.py +++ b/mangle/cbz.py @@ -21,9 +21,9 @@ from zipfile import ZipFile, ZIP_STORED class Archive(object): def __init__(self, path): outputDirectory = os.path.dirname(path) - outputFileName = '%s.cbz' % os.path.basename(path) - outputPath = os.path.join(outputDirectory, outputFileName) - self.zipfile = ZipFile(outputPath, 'w', ZIP_STORED) + outputFileName = '%s.cbz' % os.path.basename(path) + outputPath = os.path.join(outputDirectory, outputFileName) + self.zipfile = ZipFile(outputPath, 'w', ZIP_STORED) def addFile(self, filename): diff --git a/mangle/convert.py b/mangle/convert.py index 326f465..6366daa 100644 --- a/mangle/convert.py +++ b/mangle/convert.py @@ -16,10 +16,10 @@ import os import shutil - from PyQt4 import QtGui, QtCore -from image import ImageFlags + +from image import ImageFlags import cbz import image import pdfimage @@ -29,7 +29,7 @@ class DialogConvert(QtGui.QProgressDialog): def __init__(self, parent, book, directory): QtGui.QProgressDialog.__init__(self) - self.book = book + self.book = book self.bookPath = os.path.join(unicode(directory), unicode(self.book.title)) self.timer = None @@ -121,7 +121,6 @@ class DialogConvert(QtGui.QProgressDialog): archive = self.archive pdf = self.pdf - # Maybe the user ask for a split, but the picture is not a large one, so skip # it but only for this picture if (flags & ImageFlags.Split) or (flags & ImageFlags.SplitInverse): @@ -132,7 +131,7 @@ class DialogConvert(QtGui.QProgressDialog): flags &= ~f # For right page (if requested in options and need for this image) - if(flags & ImageFlags.Split): + if (flags & ImageFlags.Split): # New path based on modified index target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 0)) self.convertAndSave(source, target, device, flags ^ ImageFlags.Split | ImageFlags.SplitRight, archive, pdf) @@ -140,7 +139,7 @@ class DialogConvert(QtGui.QProgressDialog): target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 1)) # For right page (if requested), but in inverted mode - if(flags & ImageFlags.SplitInverse): + if (flags & 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) diff --git a/mangle/image.py b/mangle/image.py index 67358e1..421bec3 100644 --- a/mangle/image.py +++ b/mangle/image.py @@ -16,20 +16,20 @@ import os -from PIL import Image, ImageDraw - +from PIL import Image, ImageDraw, ImageStat, ImageChops class ImageFlags: - Orient = 1 << 0 - Resize = 1 << 1 - Frame = 1 << 2 - Quantize = 1 << 3 - Stretch = 1 << 4 - Split = 1 << 5 # split right then left - SplitRight = 1 << 6 # split only the right page - SplitLeft = 1 << 7 # split only the left page + Orient = 1 << 0 + Resize = 1 << 1 + Frame = 1 << 2 + Quantize = 1 << 3 + Stretch = 1 << 4 + 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 + AutoCrop = 1 << 9 # split left then right page class KindleData: @@ -179,6 +179,15 @@ def orientImage(image, size): return image +# We will auto crop the image, by removing just white part around the image +# by inverting colors, and asking a bounder box ^^ +@protect_bad_image +def autoCropImage(image): + x0, y0, xend, yend = ImageChops.invert(image).getbbox() + image = image.crop((x0, y0, xend, yend)) + return image + + def frameImage(image, foreground, background, size): widthDev, heightDev = size widthImg, heightImg = image.size @@ -240,6 +249,9 @@ def convertImage(source, target, device, flags): raise RuntimeError('Unexpected output device %s' % device) # Load image from source path image = loadImage(source) + + + # Format according to palette image = formatImage(image) # Apply flag transforms @@ -257,6 +269,10 @@ def convertImage(source, target, device, flags): if (flags & ImageFlags.SplitInverse): image = splitRight(image) + # Auto crop the image, but before manage size and co, clean the source so + if flags & ImageFlags.AutoCrop: + image = autoCropImage(image) + if flags & ImageFlags.Orient: image = orientImage(image, size) if flags & ImageFlags.Resize: @@ -265,7 +281,10 @@ def convertImage(source, target, device, flags): image = stretchImage(image, size) if flags & ImageFlags.Frame: image = frameImage(image, tuple(palette[:3]), tuple(palette[-3:]), size) + + + if flags & ImageFlags.Quantize: image = quantizeImage(image, palette) - - saveImage(image, target) + + saveImage(image, target) \ No newline at end of file diff --git a/mangle/options.py b/mangle/options.py index eedfe88..08a29dd 100644 --- a/mangle/options.py +++ b/mangle/options.py @@ -35,6 +35,7 @@ class DialogOptions(QtGui.QDialog): self.moveDialogToOptions() + # Get options from current book (like a loaded one) and set the dialog values def moveOptionsToDialog(self): self.lineEditTitle.setText(self.book.title or 'Untitled') self.comboBoxDevice.setCurrentIndex(max(self.comboBoxDevice.findText(self.book.device), 0)) @@ -47,12 +48,15 @@ class DialogOptions(QtGui.QDialog): self.checkboxFrame.setChecked(self.book.imageFlags & ImageFlags.Frame) + # Save parameters set on the dialogs to the book object if need def moveDialogToOptions(self): - title = self.lineEditTitle.text() - device = self.comboBoxDevice.currentText() + # First get dialog values + title = self.lineEditTitle.text() + device = self.comboBoxDevice.currentText() outputFormat = self.comboBoxFormat.currentText() - overwrite = self.checkboxOverwrite.isChecked() + overwrite = self.checkboxOverwrite.isChecked() + # Now compute flags imageFlags = 0 if self.checkboxOrient.isChecked(): imageFlags |= ImageFlags.Orient @@ -68,7 +72,12 @@ class DialogOptions(QtGui.QDialog): imageFlags |= ImageFlags.Split if self.checkboxSplitInverse.isChecked(): imageFlags |= ImageFlags.SplitInverse + if self.checkboxAutoCrop.isChecked(): + imageFlags |= ImageFlags.AutoCrop + # If we did modified a value, update the book + # and only if we did change something to not + # warn for nothing the user modified = ( self.book.title != title or self.book.device != device or @@ -78,9 +87,9 @@ class DialogOptions(QtGui.QDialog): ) if modified: - self.book.modified = True - self.book.title = title - self.book.device = device - self.book.overwrite = overwrite - self.book.imageFlags = imageFlags + self.book.modified = True + self.book.title = title + self.book.device = device + self.book.overwrite = overwrite + self.book.imageFlags = imageFlags self.book.outputFormat = outputFormat diff --git a/mangle/ui/options.ui b/mangle/ui/options.ui index 65df9bf..3619bbb 100644 --- a/mangle/ui/options.ui +++ b/mangle/ui/options.ui @@ -181,14 +181,21 @@ - + Split images into two pages (left, right) - + + + + Auto crop image (remove white around the image) + + + + Size