Merge remote-tracking branch 'catmanjan/master' into dev
This commit is contained in:
commit
be4cd7e44c
10
README.md
Normal file
10
README.md
Normal 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)
|
@ -17,7 +17,9 @@
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from mangle.book import MainWindowBook
|
||||
|
||||
|
||||
|
@ -14,12 +14,12 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os.path
|
||||
import util
|
||||
from PyQt4 import QtGui, uic
|
||||
|
||||
import util
|
||||
|
||||
|
||||
class DialogAbout(QtGui.QDialog):
|
||||
def __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)
|
||||
|
@ -14,22 +14,24 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os
|
||||
import os.path
|
||||
from os.path import basename
|
||||
import util
|
||||
import os.path
|
||||
import tempfile
|
||||
from PyQt4 import QtGui, QtCore, QtXml, uic
|
||||
from zipfile import ZipFile
|
||||
from image import ImageFlags
|
||||
|
||||
from PyQt4 import QtGui, QtCore, QtXml, uic
|
||||
from natsort import natsorted
|
||||
|
||||
from about import DialogAbout
|
||||
from options import DialogOptions
|
||||
from convert import DialogConvert
|
||||
from image import ImageFlags
|
||||
from options import DialogOptions
|
||||
import util
|
||||
|
||||
|
||||
class Book(object):
|
||||
DefaultDevice = 'Kindle 4'
|
||||
DefaultOutputFormat = 'PDF only'
|
||||
DefaultDevice = 'Kindle Paperwhite'
|
||||
DefaultOutputFormat = 'CBZ only'
|
||||
DefaultOverwrite = True
|
||||
DefaultImageFlags = ImageFlags.Orient | ImageFlags.Resize | ImageFlags.Quantize
|
||||
|
||||
@ -39,6 +41,7 @@ class Book(object):
|
||||
self.filename = None
|
||||
self.modified = False
|
||||
self.title = None
|
||||
self.titleSet = False
|
||||
self.device = Book.DefaultDevice
|
||||
self.overwrite = Book.DefaultOverwrite
|
||||
self.imageFlags = Book.DefaultImageFlags
|
||||
@ -115,7 +118,7 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
def __init__(self, filename=None):
|
||||
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.actionFileNew.triggered.connect(self.onFileNew)
|
||||
self.actionFileOpen.triggered.connect(self.onFileOpen)
|
||||
@ -221,6 +224,7 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
def onBookAddDirectory(self):
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(self, 'Select an image directory to add')
|
||||
if not directory.isNull():
|
||||
self.book.title = os.path.basename(os.path.normpath(unicode(directory)))
|
||||
self.addImageDirs([directory])
|
||||
|
||||
|
||||
@ -238,7 +242,8 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
|
||||
def onBookOptions(self):
|
||||
dialog = DialogOptions(self, self.book)
|
||||
dialog.exec_()
|
||||
if dialog.exec_() == QtGui.QDialog.Accepted:
|
||||
self.book.titleSet = True
|
||||
|
||||
|
||||
def onBookExport(self):
|
||||
@ -246,10 +251,12 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
QtGui.QMessageBox.warning(self, 'Mangle', 'This book has no images to export')
|
||||
return
|
||||
|
||||
if self.book.title is None:
|
||||
if not self.book.titleSet: # if self.book.title is None:
|
||||
dialog = DialogOptions(self, self.book)
|
||||
if dialog.exec_() == QtGui.QDialog.Rejected:
|
||||
return
|
||||
else:
|
||||
self.book.titleSet = True
|
||||
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(self, 'Select a directory to export book to')
|
||||
if not directory.isNull():
|
||||
@ -259,7 +266,7 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
|
||||
def onHelpHomepage(self):
|
||||
services = QtGui.QDesktopServices()
|
||||
services.openUrl(QtCore.QUrl('http://foosoft.net/mangle'))
|
||||
services.openUrl(QtCore.QUrl('https://github.com/catmanjan/mangle'))
|
||||
|
||||
|
||||
def onHelpAbout(self):
|
||||
@ -357,13 +364,11 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
|
||||
|
||||
def addImageFiles(self, filenames):
|
||||
filenames.sort()
|
||||
|
||||
filenamesListed = []
|
||||
for i in xrange(0, self.listWidgetFiles.count()):
|
||||
filenamesListed.append(self.listWidgetFiles.item(i).text())
|
||||
|
||||
for filename in filenames:
|
||||
for filename in natsorted(filenames):
|
||||
if filename not in filenamesListed:
|
||||
filename = QtCore.QString(filename)
|
||||
self.listWidgetFiles.addItem(filename)
|
||||
@ -374,7 +379,7 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
filenames = []
|
||||
|
||||
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:
|
||||
path = os.path.join(root, filename)
|
||||
if self.isImageFile(path):
|
||||
@ -398,16 +403,15 @@ class MainWindowBook(QtGui.QMainWindow):
|
||||
for f in cbzFile.namelist():
|
||||
if f.endswith('/'):
|
||||
try:
|
||||
os.makedirs(path+f)
|
||||
os.makedirs(path + f)
|
||||
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:
|
||||
cbzFile.extract(f, path)
|
||||
#Add the directories
|
||||
if os.path.isdir(unicode(path)):
|
||||
if os.path.isdir(unicode(path)): # Add the directories
|
||||
directories.append(path)
|
||||
#Add the files
|
||||
self.addImageDirs(directories)
|
||||
|
||||
self.addImageDirs(directories) # Add the files
|
||||
|
||||
|
||||
def isImageFile(self, filename):
|
||||
|
@ -14,10 +14,14 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os, shutil
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
import image
|
||||
from image import ImageFlags
|
||||
|
||||
import cbz
|
||||
import image
|
||||
import pdfimage
|
||||
|
||||
|
||||
@ -56,7 +60,7 @@ class DialogConvert(QtGui.QProgressDialog):
|
||||
# Close the archive if we created a CBZ file
|
||||
if self.archive is not None:
|
||||
self.archive.close()
|
||||
#Close and generate the PDF File
|
||||
# Close and generate the PDF File
|
||||
if self.pdf is not None:
|
||||
self.pdf.close()
|
||||
|
||||
@ -65,6 +69,14 @@ class DialogConvert(QtGui.QProgressDialog):
|
||||
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):
|
||||
index = self.value()
|
||||
target = os.path.join(self.bookPath, '%05d.png' % index)
|
||||
@ -104,11 +116,22 @@ class DialogConvert(QtGui.QProgressDialog):
|
||||
|
||||
try:
|
||||
if self.book.overwrite or not os.path.isfile(target):
|
||||
image.convertImage(source, target, str(self.book.device), self.book.imageFlags)
|
||||
if self.archive is not None:
|
||||
self.archive.addFile(target)
|
||||
if self.pdf is not None:
|
||||
self.pdf.addImage(target)
|
||||
device = str(self.book.device)
|
||||
flags = self.book.imageFlags
|
||||
archive = self.archive
|
||||
pdf = self.pdf
|
||||
|
||||
# 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:
|
||||
result = QtGui.QMessageBox.critical(
|
||||
self,
|
||||
|
@ -14,6 +14,8 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
|
||||
@ -23,6 +25,8 @@ class ImageFlags:
|
||||
Frame = 1 << 2
|
||||
Quantize = 1 << 3
|
||||
Stretch = 1 << 4
|
||||
Split = 1 << 5
|
||||
SplitRight = 1 << 6
|
||||
|
||||
|
||||
class KindleData:
|
||||
@ -74,11 +78,25 @@ class KindleData:
|
||||
'Kindle 2': ((600, 800), Palette15a),
|
||||
'Kindle 3': ((600, 800), Palette15a),
|
||||
'Kindle 4': ((600, 800), Palette15b),
|
||||
'Kindle 5': ((600, 800), Palette15b),
|
||||
'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):
|
||||
colors = len(palette) / 3
|
||||
if colors < 256:
|
||||
@ -161,17 +179,34 @@ def frameImage(image, foreground, background, size):
|
||||
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):
|
||||
try:
|
||||
size, palette = KindleData.Profiles[device]
|
||||
except KeyError:
|
||||
raise RuntimeError('Unexpected output device %s' % device)
|
||||
|
||||
try:
|
||||
image = Image.open(source)
|
||||
except IOError:
|
||||
raise RuntimeError('Cannot read image file %s' % source)
|
||||
# Load image from source path
|
||||
image = loadImage(source)
|
||||
# Format according to palette
|
||||
image = formatImage(image)
|
||||
# Apply flag transforms
|
||||
if flags & ImageFlags.SplitRight:
|
||||
image = splitRight(image)
|
||||
if flags & ImageFlags.Split:
|
||||
image = splitLeft(image)
|
||||
if flags & ImageFlags.Orient:
|
||||
image = orientImage(image, size)
|
||||
if flags & ImageFlags.Resize:
|
||||
@ -183,7 +218,4 @@ def convertImage(source, target, device, flags):
|
||||
if flags & ImageFlags.Quantize:
|
||||
image = quantizeImage(image, palette)
|
||||
|
||||
try:
|
||||
image.save(target)
|
||||
except IOError:
|
||||
raise RuntimeError('Cannot write image file %s' % target)
|
||||
saveImage(image, target)
|
||||
|
@ -14,17 +14,17 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os.path
|
||||
import util
|
||||
from PyQt4 import QtGui, QtCore, uic
|
||||
from PyQt4 import QtGui, uic
|
||||
|
||||
from image import ImageFlags
|
||||
import util
|
||||
|
||||
|
||||
class DialogOptions(QtGui.QDialog):
|
||||
def __init__(self, parent, book):
|
||||
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.book = book
|
||||
@ -64,6 +64,8 @@ class DialogOptions(QtGui.QDialog):
|
||||
imageFlags |= ImageFlags.Quantize
|
||||
if self.checkboxFrame.isChecked():
|
||||
imageFlags |= ImageFlags.Frame
|
||||
if self.checkboxSplit.isChecked():
|
||||
imageFlags |= ImageFlags.Split
|
||||
|
||||
modified = (
|
||||
self.book.title != title or
|
||||
|
@ -17,9 +17,10 @@
|
||||
import os.path
|
||||
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import letter
|
||||
|
||||
from image import KindleData
|
||||
|
||||
|
||||
class PDFImage(object):
|
||||
def __init__(self, path, title, device):
|
||||
outputDirectory = os.path.dirname(path)
|
||||
@ -28,7 +29,7 @@ class PDFImage(object):
|
||||
self.currentDevice = device
|
||||
self.bookTitle = title
|
||||
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.setAuthor("Mangle")
|
||||
self.canvas.setTitle(self.bookTitle)
|
||||
|
@ -49,9 +49,9 @@
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:14pt;">Mangle</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans';">Version 2.3</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans';">Version 3</span></p>
|
||||
<p style="-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';"></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans';">Manga processor by Alex Yatskov for the Kindle e-book reader. Please see </span><span style=" font-family:'Sans'; font-style:italic;">license.txt</span><span style=" font-family:'Sans';"> for licensing information. Visit the homepage at </span><a href="http://foosoft.net/mangle"><span style=" font-family:'Sans'; text-decoration: underline; color:#0000ff;">http://foosoft.net/mangle</span></a><span style=" font-family:'Sans';">.</span></p></body></html></string>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans';">Manga processor for the Kindle e-book reader. Please see </span><span style=" font-family:'Sans'; font-style:italic;">license.txt</span><span style=" font-family:'Sans';"> for licensing information. Visit the homepage at </span><a href="https://github.com/catmanjan/mangle"><span style=" font-family:'Sans'; text-decoration: underline; color:#0000ff;">https://github.com/catmanjan/mangle</span></a><span style=" font-family:'Sans';">.</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
|
@ -76,6 +76,11 @@
|
||||
<string>Kindle 4</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Kindle 5</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Kindle DX</string>
|
||||
@ -86,6 +91,11 @@
|
||||
<string>Kindle DXG</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Kindle Paperwhite</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
@ -148,6 +158,13 @@
|
||||
<string>Draw frame around images</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkboxSplit">
|
||||
<property name="text">
|
||||
<string>Split images into two pages (right, left)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
|
@ -15,8 +15,9 @@
|
||||
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
def buildResPath(relative):
|
||||
directory = os.path.dirname(__file__)
|
||||
directory = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
return os.path.join(directory, relative)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB |
27
setup.py
27
setup.py
@ -1,6 +1,6 @@
|
||||
#!/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
|
||||
# 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/>.
|
||||
|
||||
|
||||
import sys
|
||||
from distutils.core import setup
|
||||
import py2exe
|
||||
import sys
|
||||
|
||||
|
||||
sys.argv.append('py2exe')
|
||||
setup(
|
||||
name='Mangle',
|
||||
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
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user