Merge remote-tracking branch 'catmanjan/master' into dev

This commit is contained in:
Alex Yatskov 2013-11-22 07:43:22 -08:00
commit be4cd7e44c
14 changed files with 180 additions and 79 deletions

10
README.md Normal file
View File

@ -0,0 +1,10 @@
## Downloads
- [Mangle Version 3.0.0](https://github.com/catmanjan/mangle/releases/download/3/mangle_3.zip) (2013-10-11)
## For Developers
- [Python 2.7](http://www.python.org/download/releases/2.7/)
- [Python Image Library (PIL)](http://www.pythonware.com/products/pil/)
- [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)

View File

@ -17,7 +17,9 @@
import sys import sys
from PyQt4 import QtGui from PyQt4 import QtGui
from mangle.book import MainWindowBook from mangle.book import MainWindowBook

View File

@ -14,12 +14,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os.path
import util
from PyQt4 import QtGui, uic from PyQt4 import QtGui, uic
import util
class DialogAbout(QtGui.QDialog): class DialogAbout(QtGui.QDialog):
def __init__(self, parent): def __init__(self, parent):
QtGui.QDialog.__init__(self, parent) QtGui.QDialog.__init__(self, parent)
uic.loadUi(util.buildResPath('ui/about.ui'), self) uic.loadUi(util.buildResPath('mangle/ui/about.ui'), self)

View File

@ -14,22 +14,24 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import os.path
from os.path import basename from os.path import basename
import util import os.path
import tempfile import tempfile
from PyQt4 import QtGui, QtCore, QtXml, uic
from zipfile import ZipFile from zipfile import ZipFile
from image import ImageFlags
from PyQt4 import QtGui, QtCore, QtXml, uic
from natsort import natsorted
from about import DialogAbout from about import DialogAbout
from options import DialogOptions
from convert import DialogConvert from convert import DialogConvert
from image import ImageFlags
from options import DialogOptions
import util
class Book(object): class Book(object):
DefaultDevice = 'Kindle 4' DefaultDevice = 'Kindle Paperwhite'
DefaultOutputFormat = 'PDF only' DefaultOutputFormat = 'CBZ only'
DefaultOverwrite = True DefaultOverwrite = True
DefaultImageFlags = ImageFlags.Orient | ImageFlags.Resize | ImageFlags.Quantize DefaultImageFlags = ImageFlags.Orient | ImageFlags.Resize | ImageFlags.Quantize
@ -39,6 +41,7 @@ class Book(object):
self.filename = None self.filename = None
self.modified = False self.modified = False
self.title = None self.title = None
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
@ -115,7 +118,7 @@ class MainWindowBook(QtGui.QMainWindow):
def __init__(self, filename=None): def __init__(self, filename=None):
QtGui.QMainWindow.__init__(self) QtGui.QMainWindow.__init__(self)
uic.loadUi(util.buildResPath('ui/book.ui'), self) uic.loadUi(util.buildResPath('mangle/ui/book.ui'), self)
self.listWidgetFiles.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.listWidgetFiles.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.actionFileNew.triggered.connect(self.onFileNew) self.actionFileNew.triggered.connect(self.onFileNew)
self.actionFileOpen.triggered.connect(self.onFileOpen) self.actionFileOpen.triggered.connect(self.onFileOpen)
@ -221,6 +224,7 @@ class MainWindowBook(QtGui.QMainWindow):
def onBookAddDirectory(self): def onBookAddDirectory(self):
directory = QtGui.QFileDialog.getExistingDirectory(self, 'Select an image directory to add') directory = QtGui.QFileDialog.getExistingDirectory(self, 'Select an image directory to add')
if not directory.isNull(): if not directory.isNull():
self.book.title = os.path.basename(os.path.normpath(unicode(directory)))
self.addImageDirs([directory]) self.addImageDirs([directory])
@ -238,7 +242,8 @@ class MainWindowBook(QtGui.QMainWindow):
def onBookOptions(self): def onBookOptions(self):
dialog = DialogOptions(self, self.book) dialog = DialogOptions(self, self.book)
dialog.exec_() if dialog.exec_() == QtGui.QDialog.Accepted:
self.book.titleSet = True
def onBookExport(self): def onBookExport(self):
@ -246,10 +251,12 @@ class MainWindowBook(QtGui.QMainWindow):
QtGui.QMessageBox.warning(self, 'Mangle', 'This book has no images to export') QtGui.QMessageBox.warning(self, 'Mangle', 'This book has no images to export')
return return
if self.book.title is None: if not self.book.titleSet: # if self.book.title is None:
dialog = DialogOptions(self, self.book) dialog = DialogOptions(self, self.book)
if dialog.exec_() == QtGui.QDialog.Rejected: if dialog.exec_() == QtGui.QDialog.Rejected:
return return
else:
self.book.titleSet = True
directory = QtGui.QFileDialog.getExistingDirectory(self, 'Select a directory to export book to') directory = QtGui.QFileDialog.getExistingDirectory(self, 'Select a directory to export book to')
if not directory.isNull(): if not directory.isNull():
@ -259,7 +266,7 @@ class MainWindowBook(QtGui.QMainWindow):
def onHelpHomepage(self): def onHelpHomepage(self):
services = QtGui.QDesktopServices() services = QtGui.QDesktopServices()
services.openUrl(QtCore.QUrl('http://foosoft.net/mangle')) services.openUrl(QtCore.QUrl('https://github.com/catmanjan/mangle'))
def onHelpAbout(self): def onHelpAbout(self):
@ -357,13 +364,11 @@ class MainWindowBook(QtGui.QMainWindow):
def addImageFiles(self, filenames): def addImageFiles(self, filenames):
filenames.sort()
filenamesListed = [] filenamesListed = []
for i in xrange(0, self.listWidgetFiles.count()): for i in xrange(0, self.listWidgetFiles.count()):
filenamesListed.append(self.listWidgetFiles.item(i).text()) filenamesListed.append(self.listWidgetFiles.item(i).text())
for filename in filenames: for filename in natsorted(filenames):
if filename not in filenamesListed: if filename not in filenamesListed:
filename = QtCore.QString(filename) filename = QtCore.QString(filename)
self.listWidgetFiles.addItem(filename) self.listWidgetFiles.addItem(filename)
@ -374,7 +379,7 @@ class MainWindowBook(QtGui.QMainWindow):
filenames = [] filenames = []
for directory in directories: for directory in directories:
for root, subdirs, subfiles in os.walk(unicode(directory)): for root, _, subfiles in os.walk(unicode(directory)):
for filename in subfiles: for filename in subfiles:
path = os.path.join(root, filename) path = os.path.join(root, filename)
if self.isImageFile(path): if self.isImageFile(path):
@ -398,16 +403,15 @@ class MainWindowBook(QtGui.QMainWindow):
for f in cbzFile.namelist(): for f in cbzFile.namelist():
if f.endswith('/'): if f.endswith('/'):
try: try:
os.makedirs(path+f) os.makedirs(path + f)
except: except:
pass #the dir exists so we are going to extract the images only. pass # the dir exists so we are going to extract the images only.
else: else:
cbzFile.extract(f, path) cbzFile.extract(f, path)
#Add the directories if os.path.isdir(unicode(path)): # Add the directories
if os.path.isdir(unicode(path)):
directories.append(path) directories.append(path)
#Add the files
self.addImageDirs(directories) self.addImageDirs(directories) # Add the files
def isImageFile(self, filename): def isImageFile(self, filename):

View File

@ -14,10 +14,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os, shutil import os
import shutil
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
import image from image import ImageFlags
import cbz import cbz
import image
import pdfimage import pdfimage
@ -56,7 +60,7 @@ class DialogConvert(QtGui.QProgressDialog):
# Close the archive if we created a CBZ file # Close the archive if we created a CBZ file
if self.archive is not None: if self.archive is not None:
self.archive.close() self.archive.close()
#Close and generate the PDF File # Close and generate the PDF File
if self.pdf is not None: if self.pdf is not None:
self.pdf.close() self.pdf.close()
@ -65,6 +69,14 @@ class DialogConvert(QtGui.QProgressDialog):
shutil.rmtree(self.bookPath) shutil.rmtree(self.bookPath)
def convertAndSave(self, source, target, device, flags, archive, pdf):
image.convertImage(source, target, device, flags)
if archive is not None:
archive.addFile(target)
if pdf is not None:
pdf.addImage(target)
def onTimer(self): def onTimer(self):
index = self.value() index = self.value()
target = os.path.join(self.bookPath, '%05d.png' % index) target = os.path.join(self.bookPath, '%05d.png' % index)
@ -104,11 +116,22 @@ class DialogConvert(QtGui.QProgressDialog):
try: try:
if self.book.overwrite or not os.path.isfile(target): if self.book.overwrite or not os.path.isfile(target):
image.convertImage(source, target, str(self.book.device), self.book.imageFlags) device = str(self.book.device)
if self.archive is not None: flags = self.book.imageFlags
self.archive.addFile(target) archive = self.archive
if self.pdf is not None: pdf = self.pdf
self.pdf.addImage(target)
# For right page (if requested)
if(self.book.imageFlags & 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)
# 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)
except RuntimeError, error: except RuntimeError, error:
result = QtGui.QMessageBox.critical( result = QtGui.QMessageBox.critical(
self, self,

View File

@ -14,6 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
@ -23,6 +25,8 @@ class ImageFlags:
Frame = 1 << 2 Frame = 1 << 2
Quantize = 1 << 3 Quantize = 1 << 3
Stretch = 1 << 4 Stretch = 1 << 4
Split = 1 << 5
SplitRight = 1 << 6
class KindleData: class KindleData:
@ -74,11 +78,25 @@ class KindleData:
'Kindle 2': ((600, 800), Palette15a), 'Kindle 2': ((600, 800), Palette15a),
'Kindle 3': ((600, 800), Palette15a), 'Kindle 3': ((600, 800), Palette15a),
'Kindle 4': ((600, 800), Palette15b), 'Kindle 4': ((600, 800), Palette15b),
'Kindle 5': ((600, 800), Palette15b),
'Kindle DX': ((824, 1200), Palette15a), 'Kindle DX': ((824, 1200), Palette15a),
'Kindle DXG': ((824, 1200), Palette15a) 'Kindle DXG': ((824, 1200), Palette15a),
'Kindle Paperwhite': ((758, 1024), Palette15b)
} }
def splitLeft(image):
widthImg, heightImg = image.size
return image.crop((0, 0, widthImg / 2, heightImg))
def splitRight(image):
widthImg, heightImg = image.size
return image.crop((widthImg / 2, 0, widthImg, heightImg))
def quantizeImage(image, palette): def quantizeImage(image, palette):
colors = len(palette) / 3 colors = len(palette) / 3
if colors < 256: if colors < 256:
@ -161,17 +179,34 @@ def frameImage(image, foreground, background, size):
return imageBg return imageBg
def loadImage(source):
try:
return Image.open(source)
except IOError:
raise RuntimeError('Cannot read image file %s' % source)
def saveImage(image, target):
try:
image.save(target)
except IOError:
raise RuntimeError('Cannot write image file %s' % target)
def convertImage(source, target, device, flags): def convertImage(source, target, device, flags):
try: try:
size, palette = KindleData.Profiles[device] size, palette = KindleData.Profiles[device]
except KeyError: except KeyError:
raise RuntimeError('Unexpected output device %s' % device) raise RuntimeError('Unexpected output device %s' % device)
# Load image from source path
try: image = loadImage(source)
image = Image.open(source) # Format according to palette
except IOError:
raise RuntimeError('Cannot read image file %s' % source)
image = formatImage(image) image = formatImage(image)
# Apply flag transforms
if flags & ImageFlags.SplitRight:
image = splitRight(image)
if flags & ImageFlags.Split:
image = splitLeft(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:
@ -183,7 +218,4 @@ def convertImage(source, target, device, flags):
if flags & ImageFlags.Quantize: if flags & ImageFlags.Quantize:
image = quantizeImage(image, palette) image = quantizeImage(image, palette)
try: saveImage(image, target)
image.save(target)
except IOError:
raise RuntimeError('Cannot write image file %s' % target)

View File

@ -14,17 +14,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os.path from PyQt4 import QtGui, uic
import util
from PyQt4 import QtGui, QtCore, uic
from image import ImageFlags from image import ImageFlags
import util
class DialogOptions(QtGui.QDialog): class DialogOptions(QtGui.QDialog):
def __init__(self, parent, book): def __init__(self, parent, book):
QtGui.QDialog.__init__(self, parent) QtGui.QDialog.__init__(self, parent)
uic.loadUi(util.buildResPath('ui/options.ui'), self) uic.loadUi(util.buildResPath('mangle/ui/options.ui'), self)
self.accepted.connect(self.onAccept) self.accepted.connect(self.onAccept)
self.book = book self.book = book
@ -64,6 +64,8 @@ class DialogOptions(QtGui.QDialog):
imageFlags |= ImageFlags.Quantize imageFlags |= ImageFlags.Quantize
if self.checkboxFrame.isChecked(): if self.checkboxFrame.isChecked():
imageFlags |= ImageFlags.Frame imageFlags |= ImageFlags.Frame
if self.checkboxSplit.isChecked():
imageFlags |= ImageFlags.Split
modified = ( modified = (
self.book.title != title or self.book.title != title or

View File

@ -17,9 +17,10 @@
import os.path import os.path
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from image import KindleData from image import KindleData
class PDFImage(object): class PDFImage(object):
def __init__(self, path, title, device): def __init__(self, path, title, device):
outputDirectory = os.path.dirname(path) outputDirectory = os.path.dirname(path)
@ -28,7 +29,7 @@ class PDFImage(object):
self.currentDevice = device self.currentDevice = device
self.bookTitle = title self.bookTitle = title
self.pageSize = KindleData.Profiles[self.currentDevice][0] self.pageSize = KindleData.Profiles[self.currentDevice][0]
#pagesize could be letter or A4 for standarization but we need to control some image sizes # pagesize could be letter or A4 for standarization but we need to control some image sizes
self.canvas = canvas.Canvas(outputPath, pagesize=self.pageSize) self.canvas = canvas.Canvas(outputPath, pagesize=self.pageSize)
self.canvas.setAuthor("Mangle") self.canvas.setAuthor("Mangle")
self.canvas.setTitle(self.bookTitle) self.canvas.setTitle(self.bookTitle)

View File

@ -49,9 +49,9 @@
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans'; font-size:14pt;&quot;&gt;Mangle&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans'; font-size:14pt;&quot;&gt;Mangle&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt;Version 2.3&lt;/span&gt;&lt;/p&gt; &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt;Version 3&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans';&quot;&gt;&lt;/p&gt; &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans';&quot;&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt;Manga processor by Alex Yatskov for the Kindle e-book reader. Please see &lt;/span&gt;&lt;span style=&quot; font-family:'Sans'; font-style:italic;&quot;&gt;license.txt&lt;/span&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt; for licensing information. Visit the homepage at &lt;/span&gt;&lt;a href=&quot;http://foosoft.net/mangle&quot;&gt;&lt;span style=&quot; font-family:'Sans'; text-decoration: underline; color:#0000ff;&quot;&gt;http://foosoft.net/mangle&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt;Manga processor for the Kindle e-book reader. Please see &lt;/span&gt;&lt;span style=&quot; font-family:'Sans'; font-style:italic;&quot;&gt;license.txt&lt;/span&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt; for licensing information. Visit the homepage at &lt;/span&gt;&lt;a href=&quot;https://github.com/catmanjan/mangle&quot;&gt;&lt;span style=&quot; font-family:'Sans'; text-decoration: underline; color:#0000ff;&quot;&gt;https://github.com/catmanjan/mangle&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@ -76,6 +76,11 @@
<string>Kindle 4</string> <string>Kindle 4</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>Kindle 5</string>
</property>
</item>
<item> <item>
<property name="text"> <property name="text">
<string>Kindle DX</string> <string>Kindle DX</string>
@ -86,6 +91,11 @@
<string>Kindle DXG</string> <string>Kindle DXG</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>Kindle Paperwhite</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
@ -148,6 +158,13 @@
<string>Draw frame around images</string> <string>Draw frame around images</string>
</property> </property>
</widget> </widget>
</item>
<item>
<widget class="QCheckBox" name="checkboxSplit">
<property name="text">
<string>Split images into two pages (right, left)</string>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">

View File

@ -15,8 +15,9 @@
import os.path import os.path
import sys
def buildResPath(relative): def buildResPath(relative):
directory = os.path.dirname(__file__) directory = os.path.dirname(os.path.realpath(sys.argv[0]))
return os.path.join(directory, relative) return os.path.join(directory, relative)

10
nodist
View File

@ -1,10 +0,0 @@
.git
.gitignore
ref
build
dist
nodist
README
*.pyw
*.pyc
*.py

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# Copyright (C) 2010 Alex Yatskov # Copyright (C) 2013 Jan Martin
# #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -16,14 +16,33 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from distutils.core import setup from distutils.core import setup
import py2exe import sys
sys.argv.append('py2exe') sys.argv.append('py2exe')
setup( setup(
name='Mangle',
windows=[{'script': 'mangle.pyw'}], windows=[{'script': 'mangle.pyw'}],
options={'py2exe': {'bundle_files': 1, 'includes': ['sip']}}, data_files=[('', ['LICENSE']),
('mangle/ui', ['mangle/ui/book.ui',
'mangle/ui/about.ui',
'mangle/ui/options.ui']),
('mangle/img', ['mangle/img/add_directory.png',
'mangle/img/add_file.png',
'mangle/img/banner_about.png',
'mangle/img/export_book.png',
'mangle/img/file_new.png',
'mangle/img/file_open.png',
'mangle/img/remove_files.png',
'mangle/img/save_file.png',
'mangle/img/shift_down.png',
'mangle/img/shift_up.png'])],
options={'py2exe': {
'bundle_files': 1,
'includes': ['sip'],
'packages': ['reportlab.pdfbase'],
'dll_excludes': ['w9xpopen.exe']
}},
zipfile=None zipfile=None
) )