Merge remote branch 'development/development'

This commit is contained in:
Kálmán Tarnay 2010-04-25 21:13:47 +02:00
commit 4c5caf0d9c
7 changed files with 141 additions and 69 deletions

6
debian/changelog vendored
View file

@ -1,6 +1,12 @@
<<<<<<< HEAD
trimage (1.1.0b-0ubuntu1) jaunty; urgency=low trimage (1.1.0b-0ubuntu1) jaunty; urgency=low
* use multiprocessing for images * use multiprocessing for images
=======
trimage (1.0.1b-0ubuntu1) jaunty; urgency=low
* use a threadpool for images
>>>>>>> development/development
* more robust file handling * more robust file handling
* re-adding images now results in recompressing them * re-adding images now results in recompressing them
* compressing message now shows filename * compressing message now shows filename

View file

@ -32,3 +32,11 @@ later versions:
again would currently try to recompress all 100, when only 10 would be again would currently try to recompress all 100, when only 10 would be
worthy of trying to compress further. worthy of trying to compress further.
1.1.0 changes:
- use multiprocessing for images
- more robust file handling
- re-adding images now results in recompressing them
- compressing message now shows filename
- wider array of status messages in the table

View file

@ -9,7 +9,7 @@ if win:
from distutils.core import setup from distutils.core import setup
setup(name = "trimage", setup(name = "trimage",
version = "1.0.0b3", version = "1.0.1b",
description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files", description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files",
author = "Kilian Valkhof, Paul Chaplin", author = "Kilian Valkhof, Paul Chaplin",
author_email = "help@trimage.org", author_email = "help@trimage.org",

View file

@ -18,7 +18,7 @@ from multiprocessing import cpu_count
from ui import Ui_trimage from ui import Ui_trimage
VERSION = "1.0.0b3" VERSION = "1.0.1b"
class StartQT4(QMainWindow): class StartQT4(QMainWindow):
@ -28,6 +28,8 @@ class StartQT4(QMainWindow):
self.ui = Ui_trimage() self.ui = Ui_trimage()
self.ui.setupUi(self) self.ui.setupUi(self)
self.systemtray = Systray(self)
self.showapp = True self.showapp = True
self.verbose = True self.verbose = True
self.imagelist = [] self.imagelist = []
@ -150,20 +152,21 @@ class StartQT4(QMainWindow):
""" """
delegatorlist = [] delegatorlist = []
for fullpath in images: for fullpath in images:
try: # do not add already existing images again, recompress them instead try: # recompress images already in the list
image=(i.image for i in self.imagelist image = (i.image for i in self.imagelist
if i.image.fullpath == fullpath).next() if i.image.fullpath == fullpath).next()
if image.compressed: if image.compressed:
image.reset() image.reset()
image.recompression=True image.recompression = True
delegatorlist.append(image) delegatorlist.append(image)
except StopIteration: except StopIteration:
image=Image(fullpath) image = Image(fullpath)
if image.valid: if image.valid:
delegatorlist.append(image) delegatorlist.append(image)
self.imagelist.append(ImageRow(image,QIcon(QPixmap(self.ui.get_image("pixmaps/compressing.gif"))))) icon = QIcon(QPixmap(self.ui.get_image("pixmaps/compressing.gif")))
self.imagelist.append(ImageRow(image, icon))
else: else:
print >>sys.stderr, u"[error] %s not a supported image file" % image.fullpath print >> sys.stderr, u"[error] %s not a supported image file" % image.fullpath
self.update_table() self.update_table()
self.thread.compress_file(delegatorlist, self.showapp, self.verbose, self.thread.compress_file(delegatorlist, self.showapp, self.verbose,
@ -208,6 +211,7 @@ class StartQT4(QMainWindow):
def enable_recompress(self): def enable_recompress(self):
"""Enable the recompress button.""" """Enable the recompress button."""
self.ui.recompress.setEnabled(True) self.ui.recompress.setEnabled(True)
self.systemtray.recompress.setEnabled(True)
def checkapps(self): def checkapps(self):
"""Check if the required command line apps exist.""" """Check if the required command line apps exist."""
@ -240,6 +244,14 @@ class StartQT4(QMainWindow):
else: else:
raise raise
def hide_main_window(self):
if self.isVisible():
self.hide()
self.systemtray.hideMain.setText("&Show window")
else:
self.show()
self.systemtray.hideMain.setText("&Hide window")
class TriTableModel(QAbstractTableModel): class TriTableModel(QAbstractTableModel):
def __init__(self, parent, imagelist, header, *args): def __init__(self, parent, imagelist, header, *args):
@ -280,30 +292,39 @@ class TriTableModel(QAbstractTableModel):
role == Qt.DecorationRole): role == Qt.DecorationRole):
return QVariant(self.header[col]) return QVariant(self.header[col])
return QVariant() return QVariant()
class ImageRow:
def __init__(self, image, waitingIcon=None):
self.image=image
d={
'shortname': lambda i: self.statusStr() % i.shortname,
'oldfilesizestr': lambda i: size(i.oldfilesize, system=alternative) if i.compressed else "",
'newfilesizestr': lambda i: size(i.newfilesize, system=alternative) if i.compressed else "",
'ratiostr': lambda i:
"%.1f%%" % (100 - (float(i.newfilesize) / i.oldfilesize * 100)) if i.compressed else "",
'icon': lambda i: i.icon if i.compressed else waitingIcon,
'fullpath': lambda i: i.fullpath, #only used by cli
class ImageRow:
def __init__(self, image, waitingIcon=None):
""" Build the information visible in the table image row. """
self.image = image
d = {
'shortname': lambda i: self.statusStr() % i.shortname,
'oldfilesizestr': lambda i: size(i.oldfilesize, system=alternative)
if i.compressed else "",
'newfilesizestr': lambda i: size(i.newfilesize, system=alternative)
if i.compressed else "",
'ratiostr': lambda i:
"%.1f%%" % (100 - (float(i.newfilesize) / i.oldfilesize * 100))
if i.compressed else "",
'icon': lambda i: i.icon if i.compressed else waitingIcon,
'fullpath': lambda i: i.fullpath, #only used by cli
} }
for i,n in enumerate(['shortname','oldfilesizestr','newfilesizestr','ratiostr','icon']): names = ['shortname', 'oldfilesizestr', 'newfilesizestr',
d[i]=d[n] 'ratiostr', 'icon']
for i, n in enumerate(names):
d[i] = d[n]
self.d = d self.d = d
def statusStr(self): def statusStr(self):
""" Set the status message. """
if self.image.failed: if self.image.failed:
return "ERROR: %s" return "ERROR: %s"
if self.image.compressing: if self.image.compressing:
return "In Progress..." message = "Compressing %s..."
return message
if not self.image.compressed and self.image.recompression: if not self.image.compressed and self.image.recompression:
return "Queued for recompression..." return "Queued for recompression..."
if not self.image.compressed: if not self.image.compressed:
@ -313,12 +334,15 @@ class ImageRow:
def __getitem__(self, key): def __getitem__(self, key):
return self.d[key](self.image) return self.d[key](self.image)
class Image: class Image:
def __init__(self, fullpath): def __init__(self, fullpath):
""" gather image information. """
self.valid = False self.valid = False
self.reset() self.reset()
self.fullpath = fullpath self.fullpath = fullpath
if path.isfile(self.fullpath): if path.isfile(self.fullpath):
self.filetype = determinetype(self.fullpath) self.filetype = determinetype(self.fullpath)
if self.filetype in ["jpeg", "png"]: if self.filetype in ["jpeg", "png"]:
oldfile = QFileInfo(self.fullpath) oldfile = QFileInfo(self.fullpath)
@ -328,22 +352,25 @@ class Image:
self.valid = True self.valid = True
def _determinetype(self): def _determinetype(self):
filetype=determinetype(self.fullpath) """ Determine the filetype of the file using imghdr. """
filetype = determinetype(self.fullpath)
if filetype in ["jpeg", "png"]: if filetype in ["jpeg", "png"]:
self.filetype=filetype self.filetype = filetype
else: else:
self.filetype=None self.filetype = None
return self.filetype return self.filetype
def reset(self): def reset(self):
self.failed = False self.failed = False
self.compressed = False self.compressed = False
self.compressing = False self.compressing = False
self.recompression= False self.recompression = False
def compress(self): def compress(self):
""" Compress the image and return it to the thread. """
if not self.valid: if not self.valid:
raise "Tried to compress invalid image (unsupported format or not file)" raise "Tried to compress invalid image (unsupported format or not \
file)"
self.reset() self.reset()
self.compressing=True self.compressing=True
exe=".exe" if (sys.platform=="win32") else "" exe=".exe" if (sys.platform=="win32") else ""
@ -352,16 +379,16 @@ class Image:
"png" : u"optipng"+exe+" -force -o7 '%(file)s'&&advpng"+exe+" -z4 '%(file)s'"} "png" : u"optipng"+exe+" -force -o7 '%(file)s'&&advpng"+exe+" -z4 '%(file)s'"}
try: try:
retcode = call(runString[self.filetype] % {"file": self.fullpath}, retcode = call(runString[self.filetype] % {"file": self.fullpath},
shell = True, stdout=PIPE) shell=True, stdout=PIPE)
except: except:
retcode = -1 retcode = -1
if retcode == 0: if retcode == 0:
self.newfilesize = QFile(self.fullpath).size() self.newfilesize = QFile(self.fullpath).size()
self.compressed=True self.compressed = True
else: else:
self.failed=True self.failed = True
self.compressing=False self.compressing = False
self.retcode=retcode self.retcode = retcode
return self return self
@ -369,7 +396,7 @@ class Worker(QThread):
def __init__(self, parent=None): def __init__(self, parent=None):
QThread.__init__(self, parent) QThread.__init__(self, parent)
self.toDisplay=Queue() self.toDisplay = Queue()
self.threadpool = ThreadPool(max_workers=cpu_count()) self.threadpool = ThreadPool(max_workers=cpu_count())
def __del__(self): def __del__(self):
@ -378,8 +405,10 @@ class Worker(QThread):
def compress_file(self, images, showapp, verbose, imagelist): def compress_file(self, images, showapp, verbose, imagelist):
"""Start the worker thread.""" """Start the worker thread."""
for image in images: for image in images:
time.sleep(0.05) #FIXME: Workaround http://code.google.com/p/pythonthreadpool/issues/detail?id=5 #FIXME:http://code.google.com/p/pythonthreadpool/issues/detail?id=5
self.threadpool.add_job(image.compress, None, return_callback=self.toDisplay.put) time.sleep(0.05)
self.threadpool.add_job(image.compress, None,
return_callback=self.toDisplay.put)
self.showapp = showapp self.showapp = showapp
self.verbose = verbose self.verbose = verbose
self.imagelist = imagelist self.imagelist = imagelist
@ -388,41 +417,67 @@ class Worker(QThread):
def run(self): def run(self):
"""Compress the given file, get data from it and call update_table.""" """Compress the given file, get data from it and call update_table."""
tp = self.threadpool tp = self.threadpool
while self.showapp or not (tp._ThreadPool__active_worker_count==0 and tp._ThreadPool__jobs.empty()): while self.showapp or not (tp._ThreadPool__active_worker_count == 0 and
tp._ThreadPool__jobs.empty()):
image = self.toDisplay.get() image = self.toDisplay.get()
self.emit(SIGNAL("updateUi")) self.emit(SIGNAL("updateUi"))
if not self.showapp and self.verbose: # we work via the commandline if not self.showapp and self.verbose: # we work via the commandline
if image.retcode==0: if image.retcode == 0:
ir=ImageRow(image) ir = ImageRow(image)
print("File: " + ir['fullpath'] + ", Old Size: " print("File: " + ir['fullpath'] + ", Old Size: "
+ ir['oldfilesizestr'] + ", New Size: " + ir['newfilesizestr'] + ir['oldfilesizestr'] + ", New Size: "
+ ", Ratio: " + ir['ratiostr']) + ir['newfilesizestr'] + ", Ratio: " + ir['ratiostr'])
else: else:
print >>sys.stderr, u"[error] %s could not be compressed" % image.fullpath print >> sys.stderr, u"[error] %s could not be compressed" % image.fullpath
class TrimageTableView(QTableView): class Systray(QWidget):
"""Init the table drop event."""
def __init__(self, parent=None):
super(TrimageTableView, self).__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, event): def __init__(self, parent):
if event.mimeData().hasFormat("text/uri-list"): QWidget.__init__(self)
event.accept() self.parent = parent
else: self.createActions()
event.ignore() self.createTrayIcon()
self.trayIcon.show()
def dragMoveEvent(self, event): def createActions(self):
event.accept() self.quitAction = QAction(self.tr("&Quit"), self)
QObject.connect(self.quitAction, SIGNAL("triggered()"),
qApp, SLOT("quit()"))
self.addFiles = QAction(self.tr("&Add and compress"), self)
icon = QIcon()
icon.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/list-add.png"))),
QIcon.Normal, QIcon.Off)
self.addFiles.setIcon(icon)
QObject.connect(self.addFiles, SIGNAL("triggered()"), self.parent.file_dialog)
self.recompress = QAction(self.tr("&Recompress"), self)
icon2 = QIcon()
icon2.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/view-refresh.png"))),
QIcon.Normal, QIcon.Off)
self.recompress.setIcon(icon2)
self.recompress.setDisabled(True)
QObject.connect(self.addFiles, SIGNAL("triggered()"), self.parent.recompress_files)
self.hideMain = QAction(self.tr("&Hide window"), self)
QObject.connect(self.hideMain, SIGNAL("triggered()"), self.parent.hide_main_window)
def createTrayIcon(self):
self.trayIconMenu = QMenu(self)
self.trayIconMenu.addAction(self.addFiles)
self.trayIconMenu.addAction(self.recompress)
self.trayIconMenu.addAction(self.hideMain)
self.trayIconMenu.addAction(self.quitAction)
if QSystemTrayIcon.isSystemTrayAvailable():
self.trayIcon = QSystemTrayIcon(self)
self.trayIcon.setContextMenu(self.trayIconMenu)
self.trayIcon.setToolTip("Trimage image compressor")
self.trayIcon.setIcon(QIcon(self.parent.ui.get_image("pixmaps/trimage-icon.png")))
def dropEvent(self, event):
files = str(event.mimeData().data("text/uri-list")).strip().split()
for i, file in enumerate(files):
files[i] = QUrl(QString(file)).toLocalFile()
files=[i.toUtf8().decode("utf-8") for i in files]
self.emit(SIGNAL("fileDropEvent"), (files))
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)

View file

@ -36,7 +36,9 @@ class Ui_trimage(object):
""" Setup the entire UI """ """ Setup the entire UI """
trimage.setObjectName("trimage") trimage.setObjectName("trimage")
trimage.resize(600, 170) trimage.resize(600, 170)
trimage.setWindowIcon(QIcon(self.get_image("pixmaps/trimage-icon.png")))
trimageIcon = QIcon(self.get_image("pixmaps/trimage-icon.png"))
trimage.setWindowIcon(trimageIcon)
self.centralwidget = QWidget(trimage) self.centralwidget = QWidget(trimage)
self.centralwidget.setObjectName("centralwidget") self.centralwidget.setObjectName("centralwidget")

View file

@ -69,7 +69,7 @@
<body> <body>
<div id="wrap"> <div id="wrap">
<h1><img src="trimage-icon.png" alt=""> Trimage image compressor &ndash; 1.0.0b3 (beta)</h1> <h1><img src="trimage-icon.png" alt=""> Trimage image compressor &ndash; 1.0.1b (beta)</h1>
<span class="subtitle">A cross-platform tool for losslessly optimizing PNG and JPG files.</span> <span class="subtitle">A cross-platform tool for losslessly optimizing PNG and JPG files.</span>
<p class="tri">Trimage is a cross-platform GUI and command-line interface to optimize image <p class="tri">Trimage is a cross-platform GUI and command-line interface to optimize image
files via <a href="http://optipng.sourceforge.net/">optipng</a>, files via <a href="http://optipng.sourceforge.net/">optipng</a>,
@ -171,10 +171,11 @@
<li>Make sure a compressed file is always smaller than the original one, or don't compress</li> <li>Make sure a compressed file is always smaller than the original one, or don't compress</li>
<li>General refactoring</li> <li>General refactoring</li>
</ul> </ul>
<p>Version 1.1.0</p> <p>Version 1.1.0 final:</p>
<ul> <ul>
<li>Use multiprocessing instead of threading</li> <li>Use multiprocessing</li>
</ul> </ul>
<p>Beyond that</p> <p>Beyond that</p>
<ul> <ul>
<li>Deletion of rows in the table view</li> <li>Deletion of rows in the table view</li>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After