diff --git a/.gitignore b/.gitignore
index 27cd58f..dd6195d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,7 +108,7 @@ celerybeat.pid
.env
.venv
env/
-venv/
+venv*/
ENV/
env.bak/
venv.bak/
@@ -145,6 +145,7 @@ cython_debug/
*.cbz
*.pdf
+settings.json
*.bat
.idea/
diff --git a/mangle.pyw b/mangle.pyw
index 9bc73ae..69a4e8c 100755
--- a/mangle.pyw
+++ b/mangle.pyw
@@ -15,15 +15,25 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+## TODO # Port to Python3, PyQt5 # TODO ##
+# X run 2to3
+# X change imports to PyQt5 and update PyQt method calls
+# X test main functionality:
+# X build comic with images and generate all formats for at least 2 types of Kindle
+# X test load/save .mngl files and other menu bar buttons.
+# - remove unused imports
+# - remove print/ debug
+# - test all functions
import sys
-from PyQt4 import QtGui
+from PyQt5 import QtGui, QtCore, uic, QtWidgets
+from PyQt5.QtWidgets import QMainWindow, QApplication
+from PyQt5.QtCore import *
from mangle.book import MainWindowBook
-
-application = QtGui.QApplication(sys.argv)
+application = QtWidgets.QApplication(sys.argv)
filename = sys.argv[1] if len(sys.argv) > 1 else None
window = MainWindowBook(filename)
window.show()
diff --git a/mangle/about.py b/mangle/about.py
index 1cb0b77..cac74ca 100644
--- a/mangle/about.py
+++ b/mangle/about.py
@@ -14,12 +14,13 @@
# along with this program. If not, see .
-from PyQt4 import QtGui, uic
+#from PyQt4 import QtGui, uic
+from PyQt5 import QtWidgets, uic
+# from PyQt5.QtCore import *
-import util
+from . import util
-
-class DialogAbout(QtGui.QDialog):
+class DialogAbout(QtWidgets.QDialog):
def __init__(self, parent):
- QtGui.QDialog.__init__(self, parent)
+ QtWidgets.QDialog.__init__(self, parent)
uic.loadUi(util.buildResPath('mangle/ui/about.ui'), self)
diff --git a/mangle/book.py b/mangle/book.py
index e4d545a..953710d 100644
--- a/mangle/book.py
+++ b/mangle/book.py
@@ -19,28 +19,33 @@
import tempfile
from zipfile import ZipFile
-from PyQt4 import QtGui, QtCore, QtXml, uic
+# from PyQt4 import QtGui, QtCore, QtXml, uic
+from PyQt5 import QtGui, QtCore, uic, QtXml, QtWidgets
+from PyQt5.QtCore import *
-from about import DialogAbout
-from convert import DialogConvert
-from image import ImageFlags
-from options import DialogOptions
-import util
+from .about import DialogAbout
+from .convert import DialogConvert
+from .image import ImageFlags
+from .options import DialogOptions
+from . 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)
+# Sort function used to sort files in a natural order, by lowering
+# characters, and managing 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_):
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())
+ print(f"string_ => {string_}")
+ print(f"type(string_) => {type(string_)}")
+ if string_:
+ for s in re.split(r'(\d+)', string_):
+ # str does not have isdigit, so convert it if needed
+ if isinstance(s, str):
+ s = str(s)
+ if s.isdigit():
+ l.append(int(s))
+ else:
+ l.append(s.lower())
return l
@@ -80,14 +85,18 @@ def save(self, filename):
root.appendChild(itemImg)
itemImg.setAttribute('filename', filenameImg)
- textXml = document.toString(4).toUtf8()
+ textXml = document.toString(4)
+
+ if len(filename) == 2:
+ filename = filename[0] # filename received is a tuple: get the first item
try:
- fileXml = open(unicode(filename), 'w')
+ fileXml = open(str(filename), 'w')
fileXml.write(textXml)
fileXml.close()
- except IOError:
- raise RuntimeError('Cannot create book file %s' % filename)
+ except IOError as ioe:
+ print(f"IOError caught =>\n{ioe}")
+ raise RuntimeError(f'Can\'t save file {filename}')
self.filename = filename
self.modified = False
@@ -95,20 +104,20 @@ def save(self, filename):
def load(self, filename):
try:
- fileXml = open(unicode(filename), 'r')
+ fileXml = open(str(filename), 'r')
textXml = fileXml.read()
fileXml.close()
except IOError:
- raise RuntimeError('Cannot open book file %s' % filename)
+ raise RuntimeError(f'Can\'t open Mangle file {filename}')
document = QtXml.QDomDocument()
- if not document.setContent(QtCore.QString.fromUtf8(textXml)):
- raise RuntimeError('Error parsing book file %s' % filename)
+ if not document.setContent(textXml):
+ raise RuntimeError(f'Error parsing Mangle file {filename}')
root = document.documentElement()
if root.tagName() != 'book':
- raise RuntimeError('Unexpected book format in file %s' % filename)
+ raise RuntimeError(f'Unexpected Mangle format in file {filename}')
self.title = root.attribute('title', 'Untitled')
self.overwrite = root.attribute('overwrite', 'true' if Book.DefaultOverwrite else 'false') == 'true'
@@ -123,15 +132,15 @@ def load(self, filename):
if items is None:
return
- for i in xrange(0, len(items)):
+ for i in range(0, len(items)):
item = items.at(i).toElement()
if item.hasAttribute('filename'):
self.images.append(item.attribute('filename'))
-class MainWindowBook(QtGui.QMainWindow):
+class MainWindowBook(QtWidgets.QMainWindow):
def __init__(self, filename=None):
- QtGui.QMainWindow.__init__(self)
+ QtWidgets.QMainWindow.__init__(self)
uic.loadUi(util.buildResPath('mangle/ui/book.ui'), self)
self.listWidgetFiles.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
@@ -174,7 +183,7 @@ def dropEvent(self, event):
filename = url.toLocalFile()
if self.isImageFile(filename):
filenames.append(filename)
- elif os.path.isdir(unicode(filename)):
+ elif os.path.isdir(str(filename)):
directories.append(filename)
self.addImageDirs(directories)
@@ -191,12 +200,14 @@ def onFileOpen(self):
if not self.saveIfNeeded():
return
- filename = QtGui.QFileDialog.getOpenFileName(
+ filename = QtWidgets.QFileDialog.getOpenFileName(
parent=self,
caption='Select a book file to open',
filter='Mangle files (*.mngl);;All files (*.*)'
)
- if not filename.isNull():
+ filename = filename[0] # first item is filename (second is filter)
+
+ if filename:
self.loadBook(self.cleanupBookFile(filename))
@@ -209,7 +220,7 @@ def onFileSaveAs(self):
def onFilesContextMenu(self, point):
- menu = QtGui.QMenu(self)
+ menu = QtWidgets.QMenu(self)
menu.addAction(self.menu_Add.menuAction())
if len(self.listWidgetFiles.selectedItems()) > 0:
@@ -225,11 +236,13 @@ def onFilesDoubleClick(self, item):
def onBookAddFiles(self):
- filenames = QtGui.QFileDialog.getOpenFileNames(
+ filenames = QtWidgets.QFileDialog.getOpenFileNames(
parent=self,
caption='Select image file(s) to add',
filter='Image files (*.jpeg *.jpg *.gif *.png);;Comic files (*.cbz)'
)
+ filenames = filenames[0] # get first tuple item (second is file filter)
+
if(self.containsCbzFile(filenames)):
self.addCBZFiles(filenames)
else:
@@ -237,9 +250,9 @@ def onBookAddFiles(self):
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)))
+ directory = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select an image directory to add')
+ if directory:
+ self.book.title = os.path.basename(os.path.normpath(str(directory)))
self.addImageDirs([directory])
@@ -257,24 +270,24 @@ def onBookRemove(self):
def onBookOptions(self):
dialog = DialogOptions(self, self.book)
- if dialog.exec_() == QtGui.QDialog.Accepted:
+ if dialog.exec_() == QtWidgets.QDialog.Accepted:
self.book.titleSet = True
def onBookExport(self):
if len(self.book.images) == 0:
- QtGui.QMessageBox.warning(self, 'Mangle', 'This book has no images to export')
+ QtWidgets.QMessageBox.warning(self, 'Mangle', 'This book has no images to export')
return
if not self.book.titleSet: # if self.book.title is None:
dialog = DialogOptions(self, self.book)
- if dialog.exec_() == QtGui.QDialog.Rejected:
+ if dialog.exec_() == QtWidgets.QDialog.Rejected:
return
else:
self.book.titleSet = True
- directory = QtGui.QFileDialog.getExistingDirectory(self, 'Select a directory to export book to')
- if not directory.isNull():
+ directory = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select a directory to export book to')
+ if directory:
dialog = DialogConvert(self, self.book, directory)
dialog.exec_()
@@ -293,40 +306,40 @@ def saveIfNeeded(self):
if not self.book.modified:
return True
- result = QtGui.QMessageBox.question(
+ result = QtWidgets.QMessageBox.question(
self,
'Mangle',
'Save changes to the current book?',
- QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.Cancel,
- QtGui.QMessageBox.Yes
+ QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel,
+ QtWidgets.QMessageBox.Yes
)
return (
- result == QtGui.QMessageBox.No or
- result == QtGui.QMessageBox.Yes and self.saveBook()
+ result == QtWidgets.QMessageBox.No or
+ result == QtWidgets.QMessageBox.Yes and self.saveBook()
)
def saveBook(self, browse=False):
if self.book.title is None:
- QtGui.QMessageBox.warning(self, 'Mangle', 'You must specify a title for this book before saving')
+ QtWidgets.QMessageBox.warning(self, 'Mangle', 'You must specify a title for this book before saving')
return False
filename = self.book.filename
if filename is None or browse:
- filename = QtGui.QFileDialog.getSaveFileName(
+ filename = QtWidgets.QFileDialog.getSaveFileName(
parent=self,
caption='Select a book file to save as',
filter='Mangle files (*.mngl);;All files (*.*)'
)
- if filename.isNull():
+ if not filename:
return False
filename = self.cleanupBookFile(filename)
try:
self.book.save(filename)
- except RuntimeError, error:
- QtGui.QMessageBox.critical(self, 'Mangle', str(error))
+ except RuntimeError as error:
+ QtWidgets.QMessageBox.critical(self, 'Mangle', str(error))
return False
return True
@@ -335,8 +348,8 @@ def saveBook(self, browse=False):
def loadBook(self, filename):
try:
self.book.load(filename)
- except RuntimeError, error:
- QtGui.QMessageBox.critical(self, 'Mangle', str(error))
+ except RuntimeError as error:
+ QtWidgets.QMessageBox.critical(self, 'Mangle', str(error))
else:
self.listWidgetFiles.clear()
for image in self.book.images:
@@ -380,13 +393,15 @@ def removeImageFiles(self):
def addImageFiles(self, filenames):
filenamesListed = []
- for i in xrange(0, self.listWidgetFiles.count()):
+ for i in range(0, self.listWidgetFiles.count()):
filenamesListed.append(self.listWidgetFiles.item(i).text())
-
+
# Get files but in a natural sorted order
- for filename in sorted(filenames, key=natural_key):
+ sorted_filenames = sorted(filenames, key=natural_key)
+
+ for filename in sorted_filenames:
if filename not in filenamesListed:
- filename = QtCore.QString(filename)
+ filename = str(filename)
self.listWidgetFiles.addItem(filename)
self.book.images.append(filename)
self.book.modified = True
@@ -396,12 +411,12 @@ def addImageDirs(self, directories):
filenames = []
for directory in directories:
- for root, _, subfiles in os.walk(unicode(directory)):
+ for root, _, subfiles in os.walk(str(directory)):
for filename in subfiles:
path = os.path.join(root, filename)
if self.isImageFile(path):
filenames.append(path)
-
+
self.addImageFiles(filenames)
@@ -411,7 +426,7 @@ def addCBZFiles(self, filenames):
filenames.sort()
filenamesListed = []
- for i in xrange(0, self.listWidgetFiles.count()):
+ for i in range(0, self.listWidgetFiles.count()):
filenamesListed.append(self.listWidgetFiles.item(i).text())
for filename in filenames:
@@ -426,7 +441,7 @@ def addCBZFiles(self, filenames):
pass # the dir exists so we are going to extract the images only.
else:
cbzFile.extract(f, path)
- if os.path.isdir(unicode(path)): # Add the directories
+ if os.path.isdir(str(path)): # Add the directories
directories.append(path)
self.addImageDirs(directories) # Add the files
@@ -434,7 +449,7 @@ def addCBZFiles(self, filenames):
def isImageFile(self, filename):
imageExts = ['.jpeg', '.jpg', '.gif', '.png']
- filename = unicode(filename)
+ filename = str(filename)
return (
os.path.isfile(filename) and
os.path.splitext(filename)[1].lower() in imageExts
@@ -443,7 +458,7 @@ def isImageFile(self, filename):
def containsCbzFile(self, filenames):
cbzExts = ['.cbz']
for filename in filenames:
- filename = unicode(filename)
+ filename = str(filename)
result = (
os.path.isfile(filename) and
os.path.splitext(filename)[1].lower() in cbzExts
@@ -453,6 +468,6 @@ def containsCbzFile(self, filenames):
return False
def cleanupBookFile(self, filename):
- if len(os.path.splitext(unicode(filename))[1]) == 0:
+ if len(os.path.splitext(str(filename))[1]) == 0:
filename += '.mngl'
return filename
diff --git a/mangle/convert.py b/mangle/convert.py
index 8c9c92e..6a97a8e 100644
--- a/mangle/convert.py
+++ b/mangle/convert.py
@@ -16,21 +16,23 @@
import os
import shutil
-from PyQt4 import QtGui, QtCore
+#from PyQt4 import QtGui, QtCore
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5.QtCore import *
-from image import ImageFlags
-import cbz
-import image
-import pdfimage
+from .image import ImageFlags
+from . import cbz
+from . import image
+from . import pdfimage
-class DialogConvert(QtGui.QProgressDialog):
+class DialogConvert(QtWidgets.QProgressDialog):
def __init__(self, parent, book, directory):
- QtGui.QProgressDialog.__init__(self)
+ QtWidgets.QProgressDialog.__init__(self)
self.book = book
- self.bookPath = os.path.join(unicode(directory), unicode(self.book.title))
+ self.bookPath = os.path.join(str(directory), str(self.book.title))
self.timer = None
self.setWindowTitle('Exporting book...')
@@ -82,19 +84,19 @@ def onTimer(self):
index = self.value()
pages_split = self.increment
target = os.path.join(self.bookPath, '%05d.png' % (index + pages_split))
- source = unicode(self.book.images[index])
+ source = str(self.book.images[index])
if index == 0:
try:
if not os.path.isdir(self.bookPath):
os.makedirs(self.bookPath)
except OSError:
- QtGui.QMessageBox.critical(self, 'Mangle', 'Cannot create directory %s' % self.bookPath)
+ QtWidgets.QMessageBox.critical(self, 'Mangle', 'Cannot create directory %s' % self.bookPath)
self.close()
return
try:
- base = os.path.join(self.bookPath, unicode(self.book.title))
+ base = os.path.join(self.bookPath, str(self.book.title))
mangaName = base + '.manga'
if self.book.overwrite or not os.path.isfile(mangaName):
@@ -105,12 +107,12 @@ def onTimer(self):
mangaSaveName = base + '.manga_save'
if self.book.overwrite or not os.path.isfile(mangaSaveName):
mangaSave = open(base + '.manga_save', 'w')
- saveData = u'LAST=/mnt/us/pictures/%s/%s' % (self.book.title, os.path.split(target)[1])
- mangaSave.write(saveData.encode('utf-8'))
+ saveData = 'LAST=/mnt/us/pictures/%s/%s' % (self.book.title, os.path.split(target)[1])
+ mangaSave.write(saveData)
mangaSave.close()
except IOError:
- QtGui.QMessageBox.critical(self, 'Mangle', 'Cannot write manga file(s) to directory %s' % self.bookPath)
+ QtWidgets.QMessageBox.critical(self, 'Mangle', 'Cannot write manga file(s) to directory %s' % self.bookPath)
self.close()
return False
@@ -155,15 +157,15 @@ def onTimer(self):
# Convert page
self.convertAndSave(source, target, device, flags, archive, pdf)
- except RuntimeError, error:
- result = QtGui.QMessageBox.critical(
+ except RuntimeError as error:
+ result = QtWidgets.QMessageBox.critical(
self,
'Mangle',
str(error),
- QtGui.QMessageBox.Abort | QtGui.QMessageBox.Ignore,
- QtGui.QMessageBox.Ignore
+ QtWidgets.QMessageBox.Abort | QtWidgets.QMessageBox.Ignore,
+ QtWidgets.QMessageBox.Ignore
)
- if result == QtGui.QMessageBox.Abort:
+ if result == QtWidgets.QMessageBox.Abort:
self.close()
return
diff --git a/mangle/image.py b/mangle/image.py
index 2fe18b4..7977e7b 100644
--- a/mangle/image.py
+++ b/mangle/image.py
@@ -105,9 +105,9 @@ def func_wrapper(*args, **kwargs):
return args[0]
return func_wrapper
-
-@protect_bad_image
+
+@protect_bad_image
def splitLeft(image):
widthImg, heightImg = image.size
@@ -125,7 +125,7 @@ def splitRight(image):
def quantizeImage(image, palette):
colors = len(palette) / 3
if colors < 256:
- palette = palette + palette[:3] * (256 - colors)
+ palette = palette + palette[:3] * int(256 - colors)
palImg = Image.new('P', (1, 1))
palImg.putpalette(palette)
@@ -226,7 +226,7 @@ def loadImage(source):
return Image.open(source)
except IOError:
raise RuntimeError('Cannot read image file %s' % source)
-
+
def saveImage(image, target):
try:
@@ -243,7 +243,7 @@ def isSplitable(source):
try:
widthImg, heightImg = image.size
return widthImg > heightImg
- except IOError:
+ except IOError:
raise RuntimeError('Cannot read image file %s' % source)
@@ -281,5 +281,5 @@ def convertImage(source, target, device, flags):
image = frameImage(image, tuple(palette[:3]), tuple(palette[-3:]), size)
if flags & ImageFlags.Quantize:
image = quantizeImage(image, palette)
-
+
saveImage(image, target)
diff --git a/mangle/options.py b/mangle/options.py
index 8829463..2b06506 100644
--- a/mangle/options.py
+++ b/mangle/options.py
@@ -14,15 +14,16 @@
# along with this program. If not, see .
-from PyQt4 import QtGui, uic
+#from PyQt4 import QtGui, uic
+from PyQt5 import QtGui, uic, QtWidgets
-from image import ImageFlags
-import util
+from .image import ImageFlags
+from . import util
-class DialogOptions(QtGui.QDialog):
+class DialogOptions(QtWidgets.QDialog):
def __init__(self, parent, book):
- QtGui.QDialog.__init__(self, parent)
+ QtWidgets.QDialog.__init__(self, parent)
uic.loadUi(util.buildResPath('mangle/ui/options.ui'), self)
self.accepted.connect(self.onAccept)
diff --git a/mangle/pdfimage.py b/mangle/pdfimage.py
index 908be05..3f9b157 100644
--- a/mangle/pdfimage.py
+++ b/mangle/pdfimage.py
@@ -18,7 +18,7 @@
from reportlab.pdfgen import canvas
-from image import KindleData
+from .image import KindleData
class PDFImage(object):
diff --git a/requirements.txt b/requirements.txt
index 7a2c36e..ef571f6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,2 @@
-py2exe-py2==0.6.9; platform_system=='Windows'
-PyQt4 @ https://download.lfd.uci.edu/pythonlibs/w4tscw6k/cp27/PyQt4-4.11.4-cp27-cp27m-win32.whl; platform_system=='Windows'
-reportlab==3.5.59
+PyQt5==5.15.4
+reportlab==3.5.68