commit
f1f94ccfba
@ -30,27 +30,36 @@ import util
|
|||||||
|
|
||||||
# Sort function use to sort files in a natural order, by lowering
|
# 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)
|
# 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_):
|
def natural_key(string_):
|
||||||
"""See http://www.codinghorror.com/blog/archives/001018.html"""
|
l = []
|
||||||
return [int(s) if s.isdigit() else s.lower() for s in re.split(r'(\d+)', string_)]
|
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):
|
class Book(object):
|
||||||
DefaultDevice = 'Kindle Paperwhite'
|
DefaultDevice = 'Kindle Paperwhite'
|
||||||
DefaultOutputFormat = 'CBZ only'
|
DefaultOutputFormat = 'CBZ only'
|
||||||
DefaultOverwrite = True
|
DefaultOverwrite = True
|
||||||
DefaultImageFlags = ImageFlags.Orient | ImageFlags.Resize | ImageFlags.Quantize
|
DefaultImageFlags = ImageFlags.Orient | ImageFlags.Resize | ImageFlags.Quantize
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.images = []
|
self.images = []
|
||||||
self.filename = None
|
self.filename = None
|
||||||
self.modified = False
|
self.modified = False
|
||||||
self.title = None
|
self.title = None
|
||||||
self.titleSet = False
|
self.titleSet = False
|
||||||
self.device = Book.DefaultDevice
|
self.device = Book.DefaultDevice
|
||||||
self.overwrite = Book.DefaultOverwrite
|
self.overwrite = Book.DefaultOverwrite
|
||||||
self.imageFlags = Book.DefaultImageFlags
|
self.imageFlags = Book.DefaultImageFlags
|
||||||
self.outputFormat = Book.DefaultOutputFormat
|
self.outputFormat = Book.DefaultOutputFormat
|
||||||
|
|
||||||
|
|
||||||
@ -101,14 +110,14 @@ class Book(object):
|
|||||||
if root.tagName() != 'book':
|
if root.tagName() != 'book':
|
||||||
raise RuntimeError('Unexpected book format in file %s' % filename)
|
raise RuntimeError('Unexpected book format in file %s' % filename)
|
||||||
|
|
||||||
self.title = root.attribute('title', 'Untitled')
|
self.title = root.attribute('title', 'Untitled')
|
||||||
self.overwrite = root.attribute('overwrite', 'true' if Book.DefaultOverwrite else 'false') == 'true'
|
self.overwrite = root.attribute('overwrite', 'true' if Book.DefaultOverwrite else 'false') == 'true'
|
||||||
self.device = root.attribute('device', Book.DefaultDevice)
|
self.device = root.attribute('device', Book.DefaultDevice)
|
||||||
self.outputFormat = root.attribute('outputFormat', Book.DefaultOutputFormat)
|
self.outputFormat = root.attribute('outputFormat', Book.DefaultOutputFormat)
|
||||||
self.imageFlags = int(root.attribute('imageFlags', str(Book.DefaultImageFlags)))
|
self.imageFlags = int(root.attribute('imageFlags', str(Book.DefaultImageFlags)))
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.modified = False
|
self.modified = False
|
||||||
self.images = []
|
self.images = []
|
||||||
|
|
||||||
items = root.elementsByTagName('image')
|
items = root.elementsByTagName('image')
|
||||||
if items is None:
|
if items is None:
|
||||||
|
@ -21,9 +21,9 @@ from zipfile import ZipFile, ZIP_STORED
|
|||||||
class Archive(object):
|
class Archive(object):
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
outputDirectory = os.path.dirname(path)
|
outputDirectory = os.path.dirname(path)
|
||||||
outputFileName = '%s.cbz' % os.path.basename(path)
|
outputFileName = '%s.cbz' % os.path.basename(path)
|
||||||
outputPath = os.path.join(outputDirectory, outputFileName)
|
outputPath = os.path.join(outputDirectory, outputFileName)
|
||||||
self.zipfile = ZipFile(outputPath, 'w', ZIP_STORED)
|
self.zipfile = ZipFile(outputPath, 'w', ZIP_STORED)
|
||||||
|
|
||||||
|
|
||||||
def addFile(self, filename):
|
def addFile(self, filename):
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from PyQt4 import QtGui, QtCore
|
from PyQt4 import QtGui, QtCore
|
||||||
from image import ImageFlags
|
|
||||||
|
|
||||||
|
|
||||||
|
from image import ImageFlags
|
||||||
import cbz
|
import cbz
|
||||||
import image
|
import image
|
||||||
import pdfimage
|
import pdfimage
|
||||||
@ -29,7 +29,7 @@ class DialogConvert(QtGui.QProgressDialog):
|
|||||||
def __init__(self, parent, book, directory):
|
def __init__(self, parent, book, directory):
|
||||||
QtGui.QProgressDialog.__init__(self)
|
QtGui.QProgressDialog.__init__(self)
|
||||||
|
|
||||||
self.book = book
|
self.book = book
|
||||||
self.bookPath = os.path.join(unicode(directory), unicode(self.book.title))
|
self.bookPath = os.path.join(unicode(directory), unicode(self.book.title))
|
||||||
|
|
||||||
self.timer = None
|
self.timer = None
|
||||||
@ -121,7 +121,6 @@ class DialogConvert(QtGui.QProgressDialog):
|
|||||||
archive = self.archive
|
archive = self.archive
|
||||||
pdf = self.pdf
|
pdf = self.pdf
|
||||||
|
|
||||||
|
|
||||||
# Maybe the user ask for a split, but the picture is not a large one, so skip
|
# Maybe the user ask for a split, but the picture is not a large one, so skip
|
||||||
# it but only for this picture
|
# it but only for this picture
|
||||||
if (flags & ImageFlags.Split) or (flags & ImageFlags.SplitInverse):
|
if (flags & ImageFlags.Split) or (flags & ImageFlags.SplitInverse):
|
||||||
@ -132,7 +131,7 @@ class DialogConvert(QtGui.QProgressDialog):
|
|||||||
flags &= ~f
|
flags &= ~f
|
||||||
|
|
||||||
# For right page (if requested in options and need for this image)
|
# 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
|
# New path based on modified index
|
||||||
target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 0))
|
target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 0))
|
||||||
self.convertAndSave(source, target, device, flags ^ ImageFlags.Split | ImageFlags.SplitRight, archive, pdf)
|
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))
|
target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 1))
|
||||||
|
|
||||||
# For right page (if requested), but in inverted mode
|
# For right page (if requested), but in inverted mode
|
||||||
if(flags & ImageFlags.SplitInverse):
|
if (flags & ImageFlags.SplitInverse):
|
||||||
# New path based on modified index
|
# New path based on modified index
|
||||||
target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 0))
|
target = os.path.join(self.bookPath, '%05d.png' % (index * 2 + 0))
|
||||||
self.convertAndSave(source, target, device, flags ^ ImageFlags.SplitInverse | ImageFlags.SplitLeft, archive, pdf)
|
self.convertAndSave(source, target, device, flags ^ ImageFlags.SplitInverse | ImageFlags.SplitLeft, archive, pdf)
|
||||||
|
@ -16,20 +16,20 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw, ImageStat, ImageChops
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ImageFlags:
|
class ImageFlags:
|
||||||
Orient = 1 << 0
|
Orient = 1 << 0
|
||||||
Resize = 1 << 1
|
Resize = 1 << 1
|
||||||
Frame = 1 << 2
|
Frame = 1 << 2
|
||||||
Quantize = 1 << 3
|
Quantize = 1 << 3
|
||||||
Stretch = 1 << 4
|
Stretch = 1 << 4
|
||||||
Split = 1 << 5 # split right then left
|
Split = 1 << 5 # split right then left
|
||||||
SplitRight = 1 << 6 # split only the right page
|
SplitRight = 1 << 6 # split only the right page
|
||||||
SplitLeft = 1 << 7 # split only the left page
|
SplitLeft = 1 << 7 # split only the left page
|
||||||
SplitInverse = 1 << 8 # split left then right page
|
SplitInverse = 1 << 8 # split left then right page
|
||||||
|
AutoCrop = 1 << 9 # split left then right page
|
||||||
|
|
||||||
|
|
||||||
class KindleData:
|
class KindleData:
|
||||||
@ -179,6 +179,15 @@ def orientImage(image, size):
|
|||||||
return image
|
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):
|
def frameImage(image, foreground, background, size):
|
||||||
widthDev, heightDev = size
|
widthDev, heightDev = size
|
||||||
widthImg, heightImg = image.size
|
widthImg, heightImg = image.size
|
||||||
@ -240,6 +249,9 @@ def convertImage(source, target, device, flags):
|
|||||||
raise RuntimeError('Unexpected output device %s' % device)
|
raise RuntimeError('Unexpected output device %s' % device)
|
||||||
# Load image from source path
|
# Load image from source path
|
||||||
image = loadImage(source)
|
image = loadImage(source)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Format according to palette
|
# Format according to palette
|
||||||
image = formatImage(image)
|
image = formatImage(image)
|
||||||
# Apply flag transforms
|
# Apply flag transforms
|
||||||
@ -257,6 +269,10 @@ def convertImage(source, target, device, flags):
|
|||||||
if (flags & ImageFlags.SplitInverse):
|
if (flags & ImageFlags.SplitInverse):
|
||||||
image = splitRight(image)
|
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:
|
if flags & ImageFlags.Orient:
|
||||||
image = orientImage(image, size)
|
image = orientImage(image, size)
|
||||||
if flags & ImageFlags.Resize:
|
if flags & ImageFlags.Resize:
|
||||||
@ -265,6 +281,9 @@ def convertImage(source, target, device, flags):
|
|||||||
image = stretchImage(image, size)
|
image = stretchImage(image, size)
|
||||||
if flags & ImageFlags.Frame:
|
if flags & ImageFlags.Frame:
|
||||||
image = frameImage(image, tuple(palette[:3]), tuple(palette[-3:]), size)
|
image = frameImage(image, tuple(palette[:3]), tuple(palette[-3:]), size)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if flags & ImageFlags.Quantize:
|
if flags & ImageFlags.Quantize:
|
||||||
image = quantizeImage(image, palette)
|
image = quantizeImage(image, palette)
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ class DialogOptions(QtGui.QDialog):
|
|||||||
self.moveDialogToOptions()
|
self.moveDialogToOptions()
|
||||||
|
|
||||||
|
|
||||||
|
# Get options from current book (like a loaded one) and set the dialog values
|
||||||
def moveOptionsToDialog(self):
|
def moveOptionsToDialog(self):
|
||||||
self.lineEditTitle.setText(self.book.title or 'Untitled')
|
self.lineEditTitle.setText(self.book.title or 'Untitled')
|
||||||
self.comboBoxDevice.setCurrentIndex(max(self.comboBoxDevice.findText(self.book.device), 0))
|
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)
|
self.checkboxFrame.setChecked(self.book.imageFlags & ImageFlags.Frame)
|
||||||
|
|
||||||
|
|
||||||
|
# Save parameters set on the dialogs to the book object if need
|
||||||
def moveDialogToOptions(self):
|
def moveDialogToOptions(self):
|
||||||
title = self.lineEditTitle.text()
|
# First get dialog values
|
||||||
device = self.comboBoxDevice.currentText()
|
title = self.lineEditTitle.text()
|
||||||
|
device = self.comboBoxDevice.currentText()
|
||||||
outputFormat = self.comboBoxFormat.currentText()
|
outputFormat = self.comboBoxFormat.currentText()
|
||||||
overwrite = self.checkboxOverwrite.isChecked()
|
overwrite = self.checkboxOverwrite.isChecked()
|
||||||
|
|
||||||
|
# Now compute flags
|
||||||
imageFlags = 0
|
imageFlags = 0
|
||||||
if self.checkboxOrient.isChecked():
|
if self.checkboxOrient.isChecked():
|
||||||
imageFlags |= ImageFlags.Orient
|
imageFlags |= ImageFlags.Orient
|
||||||
@ -68,7 +72,12 @@ class DialogOptions(QtGui.QDialog):
|
|||||||
imageFlags |= ImageFlags.Split
|
imageFlags |= ImageFlags.Split
|
||||||
if self.checkboxSplitInverse.isChecked():
|
if self.checkboxSplitInverse.isChecked():
|
||||||
imageFlags |= ImageFlags.SplitInverse
|
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 = (
|
modified = (
|
||||||
self.book.title != title or
|
self.book.title != title or
|
||||||
self.book.device != device or
|
self.book.device != device or
|
||||||
@ -78,9 +87,9 @@ class DialogOptions(QtGui.QDialog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if modified:
|
if modified:
|
||||||
self.book.modified = True
|
self.book.modified = True
|
||||||
self.book.title = title
|
self.book.title = title
|
||||||
self.book.device = device
|
self.book.device = device
|
||||||
self.book.overwrite = overwrite
|
self.book.overwrite = overwrite
|
||||||
self.book.imageFlags = imageFlags
|
self.book.imageFlags = imageFlags
|
||||||
self.book.outputFormat = outputFormat
|
self.book.outputFormat = outputFormat
|
||||||
|
@ -181,14 +181,21 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkboxSplitInverse">
|
<widget class="QCheckBox" name="checkboxSplitInverse">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Split images into two pages (left, right)</string>
|
<string>Split images into two pages (left, right)</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkboxAutoCrop">
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto crop image (remove white around the image)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Size</string>
|
<string>Size</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user