From 8eca5302754db5456c4049d1ae3d6b24e4c4949f Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 14:44:11 +0100 Subject: [PATCH 01/62] First try to adopt Python3 and PyQt5 --- src/trimage/{ThreadPool => }/ThreadPool.py | 124 ++++++++++----------- src/trimage/ThreadPool/__init__.py | 1 - src/trimage/{filesize => }/filesize.py | 0 src/trimage/filesize/PKG-INFO | 71 ------------ src/trimage/filesize/README.txt | 50 --------- src/trimage/filesize/__init__.py | 2 - src/trimage/trimage.py | 59 +++++----- src/trimage/ui.py | 37 +++--- 8 files changed, 109 insertions(+), 235 deletions(-) rename src/trimage/{ThreadPool => }/ThreadPool.py (84%) delete mode 100644 src/trimage/ThreadPool/__init__.py rename src/trimage/{filesize => }/filesize.py (100%) delete mode 100644 src/trimage/filesize/PKG-INFO delete mode 100644 src/trimage/filesize/README.txt delete mode 100644 src/trimage/filesize/__init__.py diff --git a/src/trimage/ThreadPool/ThreadPool.py b/src/trimage/ThreadPool.py similarity index 84% rename from src/trimage/ThreadPool/ThreadPool.py rename to src/trimage/ThreadPool.py index 5c64409..7f92344 100644 --- a/src/trimage/ThreadPool/ThreadPool.py +++ b/src/trimage/ThreadPool.py @@ -2,13 +2,13 @@ ThreadPool Implementation @author: Morten Holdflod Moeller - morten@holdflod.dk -@license: LGPL v3 +@license: LGPL v3 ''' from __future__ import with_statement from threading import Thread, RLock from time import sleep -from Queue import Queue, Empty +from queue import Queue, Empty import logging import sys @@ -31,7 +31,7 @@ class ThreadPoolMixIn: self.__private_threadpool = True else: self.__private_threadpool = False - + self.__threadpool = threadpool def process_request_thread(self, request, client_address): @@ -48,11 +48,11 @@ class ThreadPoolMixIn: self.close_request(request) def process_request(self, request, client_address): - self.__threadpool.add_job(self.process_request_thread, [request, client_address]) + self.__threadpool.add_job(self.process_request_thread, [request, client_address]) def shutdown(self): if (self.__private_threadpool): self.__threadpool.shutdown() - + class AddJobException(Exception): ''' @@ -61,64 +61,64 @@ class AddJobException(Exception): ''' def __init__(self, msg): Exception.__init__(self, msg) - + class ThreadPool: ''' The class implementing the ThreadPool. - - Instantiate and add jobs using add_job(func, args_list) + + Instantiate and add jobs using add_job(func, args_list) ''' class Job: #IGNORE:R0903 ''' Class encapsulating a job to be handled - by ThreadPool workers + by ThreadPool workers ''' def __init__(self, function, args, return_callback=None): self.callable = function self.arguments = args self.return_callback = return_callback - + def execute(self): ''' Called to execute the function ''' try: return_value = self.callable(*self.arguments) #IGNORE:W0142 - except Exception, excep: #IGNORE:W0703 + except Exception as excep: #IGNORE:W0703 logger = logging.getLogger("threadpool.worker") logger.warning("A job in the ThreadPool raised an exception: " + excep) #else do nothing cause we don't know what to do... - return - + return + try: if (self.return_callback != None): self.return_callback(return_value) - except Exception, _: #IGNORE:W0703 everything could go wrong... + except Exception as _: #IGNORE:W0703 everything could go wrong... logger = logging.getLogger('threadpool') logger.warning('Error while delivering return value to callback function') class Worker(Thread): ''' - A worker thread handling jobs in the thread pool + A worker thread handling jobs in the thread pool job queue ''' - + def __init__(self, pool): Thread.__init__(self) - + if (not isinstance(pool, ThreadPool)): raise TypeError("pool is not a ThreadPool instance") - + self.pool = pool - + self.alive = True self.start() - + def run(self): ''' - The workers main-loop getting jobs from queue + The workers main-loop getting jobs from queue and executing them ''' while self.alive: @@ -130,83 +130,83 @@ class ThreadPool: self.pool.worker_inactive() else: self.alive = False - + self.pool.punch_out() - + def __init__(self, max_workers = 5, kill_workers_after = 3): if (not isinstance(max_workers, int)): raise TypeError("max_workers is not an int") if (max_workers < 1): raise ValueError('max_workers must be >= 1') - + if (not isinstance(kill_workers_after, int)): raise TypeError("kill_workers_after is not an int") - + self.__max_workers = max_workers self.__kill_workers_after = kill_workers_after - + # This Queue is assumed Thread Safe self.__jobs = Queue() - - self.__worker_count_lock = RLock() + + self.__worker_count_lock = RLock() self.__worker_count = 0 self.__active_worker_count = 0 - + self.__shutting_down = False logger = logging.getLogger('threadpool') logger.info('started') - + def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5): if (not isinstance(clean_shutdown_reties, int)): raise TypeError("clean_shutdown_reties is not an int") if (not clean_shutdown_reties >= 0): raise ValueError('clean_shutdown_reties must be >= 0') - + if (not isinstance(wait_for_workers_period, int)): raise TypeError("wait_for_workers_period is not an int") if (not wait_for_workers_period >= 0): raise ValueError('wait_for_workers_period must be >= 0') - + logger = logging.getLogger("threadpool") logger.info("shutting down") - + with self.__worker_count_lock: self.__shutting_down = True self.__max_workers = 0 self.__kill_workers_after = 0 - + retries_left = clean_shutdown_reties while (retries_left > 0): - + with self.__worker_count_lock: logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count)) if (self.__worker_count > 0): retries_left -= 1 else: retries_left = 0 - + sleep(wait_for_workers_period) - - + + with self.__worker_count_lock: if (self.__worker_count > 0): logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count) clean_shutdown = False else: clean_shutdown = True - + logger.info("shutdown complete") - + return clean_shutdown - + def punch_out(self): ''' - Called by worker to update worker count + Called by worker to update worker count when the worker is shutting down ''' with self.__worker_count_lock: self.__worker_count -= 1 - + def __new_worker(self): ''' Adding a new worker thread to the thread pool @@ -214,58 +214,58 @@ class ThreadPool: with self.__worker_count_lock: ThreadPool.Worker(self) self.__worker_count += 1 - + def worker_active(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count + 1 - + def worker_inactive(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count - 1 - + def add_job(self, function, args = None, return_callback=None): ''' Put new job into queue ''' - + if (not callable(function)): raise TypeError("function is not a callable") if (not ( args == None or isinstance(args, list))): raise TypeError("args is not a list") if (not (return_callback == None or callable(return_callback))): raise TypeError("return_callback is not a callable") - + if (args == None): args = [] - + job = ThreadPool.Job(function, args, return_callback) - + with self.__worker_count_lock: if (self.__shutting_down): raise AddJobException("ThreadPool is shutting down") - + try: start_new_worker = False if (self.__worker_count < self.__max_workers): if (self.__active_worker_count == self.__worker_count): start_new_worker = True - + self.__jobs.put(job) - - if (start_new_worker): + + if (start_new_worker): self.__new_worker() - + except Exception: raise AddJobException("Could not add job") - - + + def get_job(self): ''' - Retrieve next job from queue - workers die (and should) when - returning None + Retrieve next job from queue + workers die (and should) when + returning None ''' - + job = None try: if (self.__kill_workers_after < 0): @@ -276,5 +276,5 @@ class ThreadPool: job = self.__jobs.get(True, self.__kill_workers_after) except Empty: job = None - + return job diff --git a/src/trimage/ThreadPool/__init__.py b/src/trimage/ThreadPool/__init__.py deleted file mode 100644 index 727a8b3..0000000 --- a/src/trimage/ThreadPool/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ThreadPool import ThreadPool, ThreadPoolMixIn \ No newline at end of file diff --git a/src/trimage/filesize/filesize.py b/src/trimage/filesize.py similarity index 100% rename from src/trimage/filesize/filesize.py rename to src/trimage/filesize.py diff --git a/src/trimage/filesize/PKG-INFO b/src/trimage/filesize/PKG-INFO deleted file mode 100644 index fe3aadb..0000000 --- a/src/trimage/filesize/PKG-INFO +++ /dev/null @@ -1,71 +0,0 @@ -Metadata-Version: 1.0 -Name: hurry.filesize -Version: 0.9 -Summary: A simple Python library for human readable file sizes (or anything sized in bytes). -Home-page: UNKNOWN -Author: Martijn Faassen, Startifact -Author-email: faassen@startifact.com -License: ZPL 2.1 -Description: hurry.filesize - ============== - - hurry.filesize a simple Python library that can take a number of bytes and - returns a human-readable string with the size in it, in kilobytes (K), - megabytes (M), etc. - - The default system it uses is "traditional", where multipliers of 1024 - increase the unit size:: - - >>> from hurry.filesize import size - >>> size(1024) - '1K' - - An alternative, slightly more verbose system:: - - >>> from hurry.filesize import alternative - >>> size(1, system=alternative) - '1 byte' - >>> size(10, system=alternative) - '10 bytes' - >>> size(1024, system=alternative) - '1 KB' - - A verbose system:: - - >>> from hurry.filesize import verbose - >>> size(10, system=verbose) - '10 bytes' - >>> size(1024, system=verbose) - '1 kilobyte' - >>> size(2000, system=verbose) - '1 kilobyte' - >>> size(3000, system=verbose) - '2 kilobytes' - >>> size(1024 * 1024, system=verbose) - '1 megabyte' - >>> size(1024 * 1024 * 3, system=verbose) - '3 megabytes' - - You can also use the SI system, where multipliers of 1000 increase the unit - size:: - - >>> from hurry.filesize import si - >>> size(1000, system=si) - '1K' - - - Changes - ======= - - 0.9 (2009-03-11) - ---------------- - - * Initial public release. - - Download - ======== - -Keywords: file size bytes -Platform: UNKNOWN -Classifier: Programming Language :: Python -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/trimage/filesize/README.txt b/src/trimage/filesize/README.txt deleted file mode 100644 index 6a0fad6..0000000 --- a/src/trimage/filesize/README.txt +++ /dev/null @@ -1,50 +0,0 @@ -hurry.filesize -============== - -hurry.filesize a simple Python library that can take a number of bytes and -returns a human-readable string with the size in it, in kilobytes (K), -megabytes (M), etc. - -The default system it uses is "traditional", where multipliers of 1024 -increase the unit size:: - - >>> from hurry.filesize import size - >>> size(1024) - '1K' - -An alternative, slightly more verbose system:: - - >>> from hurry.filesize import alternative - >>> size(1, system=alternative) - '1 byte' - >>> size(10, system=alternative) - '10 bytes' - >>> size(1024, system=alternative) - '1 KB' - -A verbose system:: - - >>> from hurry.filesize import verbose - >>> size(10, system=verbose) - '10 bytes' - >>> size(1024, system=verbose) - '1 kilobyte' - >>> size(2000, system=verbose) - '1 kilobyte' - >>> size(3000, system=verbose) - '2 kilobytes' - >>> size(1024 * 1024, system=verbose) - '1 megabyte' - >>> size(1024 * 1024 * 3, system=verbose) - '3 megabytes' - -You can also use the SI system, where multipliers of 1000 increase the unit -size:: - - >>> from hurry.filesize import si - >>> size(1000, system=si) - '1K' - - -http://pypi.python.org/pypi/hurry.filesize - diff --git a/src/trimage/filesize/__init__.py b/src/trimage/filesize/__init__.py deleted file mode 100644 index 68d9fc2..0000000 --- a/src/trimage/filesize/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from filesize import size -from filesize import traditional, alternative, verbose, iec, si diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 84b13a0..5fb8bf5 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -11,13 +11,14 @@ from shutil import copy from subprocess import call, PIPE from optparse import OptionParser -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * from filesize import * from imghdr import what as determinetype -from Queue import Queue -from ThreadPool import ThreadPool +from queue import Queue +from ThreadPool import ThreadPool, ThreadPoolMixIn from multiprocessing import cpu_count from ui import Ui_trimage @@ -25,7 +26,7 @@ from ui import Ui_trimage VERSION = "1.0.5" -class StartQT4(QMainWindow): +class StartQT5(QMainWindow): def __init__(self, parent=None): QWidget.__init__(self, parent) @@ -40,7 +41,7 @@ class StartQT4(QMainWindow): QCoreApplication.setOrganizationDomain("trimage.org") QCoreApplication.setApplicationName("Trimage") self.settings = QSettings() - self.restoreGeometry(self.settings.value("geometry").toByteArray()) + self.restoreGeometry(self.settings.value("geometry")) # check if apps are installed if self.checkapps(): @@ -61,17 +62,15 @@ class StartQT4(QMainWindow): self.thread = Worker() # connect signals with slots - QObject.connect(self.ui.addfiles, SIGNAL("clicked()"), - self.file_dialog) - QObject.connect(self.ui.recompress, SIGNAL("clicked()"), - self.recompress_files) - QObject.connect(self.quit_shortcut, SIGNAL("activated()"), - qApp, SLOT('quit()')) - QObject.connect(self.ui.processedfiles, SIGNAL("fileDropEvent"), - self.file_drop) - QObject.connect(self.thread, SIGNAL("finished()"), self.update_table) - QObject.connect(self.thread, SIGNAL("terminated()"), self.update_table) - QObject.connect(self.thread, SIGNAL("updateUi"), self.update_table) + self.ui.addfiles.clicked.connect(self.file_dialog) + self.ui.recompress.clicked.connect(self.recompress_files) + # QObject.connect(self.quit_shortcut, SIGNAL("activated()"), + # qApp, SLOT('quit()')) + # QObject.connect(self.ui.processedfiles, SIGNAL("fileDropEvent"), + # self.file_drop) + # QObject.connect(self.thread, SIGNAL("finished()"), self.update_table) + # QObject.connect(self.thread, SIGNAL("terminated()"), self.update_table) + # QObject.connect(self.thread, SIGNAL("updateUi"), self.update_table) self.compressing_icon = QIcon(QPixmap(self.ui.get_image("pixmaps/compressing.gif"))) @@ -143,8 +142,8 @@ class StartQT4(QMainWindow): def file_dialog(self): """Open a file dialog and send the selected images to compress_file.""" fd = QFileDialog(self) - fd.restoreState(self.settings.value("fdstate").toByteArray()) - directory = self.settings.value("directory", QVariant("")).toString() + fd.restoreState(self.settings.value("fdstate")) + directory = self.settings.value("directory", QVariant("")) fd.setDirectory(directory) images = fd.getOpenFileNames(self, @@ -155,8 +154,8 @@ class StartQT4(QMainWindow): self.settings.setValue("fdstate", QVariant(fd.saveState())) if images: - self.settings.setValue("directory", QVariant(path.dirname(unicode(images[0])))) - self.delegator([unicode(fullpath) for fullpath in images]) + self.settings.setValue("directory", QVariant(path.dirname(images[0][0]))) + self.delegator([fullpath for fullpath in images]) def recompress_files(self): """Send each file in the current file list to compress_file again.""" @@ -174,16 +173,16 @@ class StartQT4(QMainWindow): for fullpath in images: try: # recompress images already in the list image = (i.image for i in self.imagelist - if i.image.fullpath == fullpath).next() + if i.image.fullpath == fullpath[0]).__next__() if image.compressed: image.reset() image.recompression = True delegatorlist.append(image) except StopIteration: - if not path.isdir(fullpath): - self. add_image(fullpath, delegatorlist) + if not path.isdir(fullpath[0]): + self.add_image(fullpath[0], delegatorlist) else: - self.walk(fullpath, delegatorlist) + self.walk(fullpath[0], delegatorlist) self.update_table() self.thread.compress_file(delegatorlist, self.showapp, self.verbose, @@ -214,7 +213,7 @@ class StartQT4(QMainWindow): self.systemtray.trayIcon.setToolTip("Trimage image compressor (" + str(len(self.imagelist)) + " files)") self.setWindowTitle("Trimage image compressor (" + str(len(self.imagelist)) + " files)") else: - print >> sys.stderr, u"[error] %s not a supported image file and/or not writeable" % image.fullpath + print(str(sys.stderr) + "[error] {} not a supported image file and/or not writeable".format(image.fullpath)) """ UI Functions @@ -287,7 +286,7 @@ class StartQT4(QMainWindow): while True: try: return call(command, shell=True, stdout=PIPE) - except OSError, e: + except OSError as e: if e.errno == errno.EINTR: continue else: @@ -488,7 +487,7 @@ class Worker(QThread): tp._ThreadPool__jobs.empty()): 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 image.retcode == 0: @@ -497,7 +496,7 @@ class Worker(QThread): + ir['oldfilesizestr'] + ", New Size: " + ir['newfilesizestr'] + ", Ratio: " + ir['ratiostr']) else: - print >> sys.stderr, u"[error] %s could not be compressed" % image.fullpath + print(str(sys.stderr) + "[error] {} could not be compressed".format(image.fullpath)) class Systray(QWidget): @@ -550,7 +549,7 @@ class Systray(QWidget): if __name__ == "__main__": app = QApplication(sys.argv) - myapp = StartQT4() + myapp = StartQT5() if myapp.showapp: myapp.show() diff --git a/src/trimage/ui.py b/src/trimage/ui.py index 47937da..ca3a98f 100644 --- a/src/trimage/ui.py +++ b/src/trimage/ui.py @@ -1,5 +1,6 @@ -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * from os import path class TrimageTableView(QTableView): @@ -44,7 +45,7 @@ class Ui_trimage(object): self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QGridLayout(self.centralwidget) - self.gridLayout_2.setMargin(0) + #self.gridLayout_2.setMargin(0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") @@ -60,7 +61,7 @@ class Ui_trimage(object): self.verticalLayout = QVBoxLayout(self.widget) self.verticalLayout.setSpacing(0) - self.verticalLayout.setMargin(0) + #self.verticalLayout.setMargin(0) self.verticalLayout.setObjectName("verticalLayout") self.frame = QFrame(self.widget) @@ -68,12 +69,12 @@ class Ui_trimage(object): self.verticalLayout_2 = QVBoxLayout(self.frame) self.verticalLayout_2.setSpacing(0) - self.verticalLayout_2.setMargin(0) + #self.verticalLayout_2.setMargin(0) self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setMargin(10) + #self.horizontalLayout.setMargin(10) self.horizontalLayout.setObjectName("horizontalLayout") self.addfiles = QPushButton(self.frame) @@ -93,7 +94,7 @@ class Ui_trimage(object): font.setPointSize(8) self.label.setFont(font) self.label.setFrameShadow(QFrame.Plain) - self.label.setMargin(1) + #self.label.setMargin(1) self.label.setIndent(10) self.label.setObjectName("label") self.horizontalLayout.addWidget(self.label) @@ -143,24 +144,22 @@ class Ui_trimage(object): def retranslateUi(self, trimage): """ Fill in the texts for all UI elements """ trimage.setWindowTitle(QApplication.translate("trimage", - "Trimage image compressor", None, QApplication.UnicodeUTF8)) + "Trimage image compressor", None)) self.addfiles.setToolTip(QApplication.translate("trimage", - "Add file to the compression list", None, - QApplication.UnicodeUTF8)) + "Add file to the compression list", None)) self.addfiles.setText(QApplication.translate("trimage", - "&Add and compress", None, QApplication.UnicodeUTF8)) + "&Add and compress", None)) self.addfiles.setShortcut(QApplication.translate("trimage", - "Alt+A", None, QApplication.UnicodeUTF8)) + "Alt+A", None)) self.label.setText(QApplication.translate("trimage", - "Drag and drop images onto the table", None, - QApplication.UnicodeUTF8)) + "Drag and drop images onto the table", None)) self.recompress.setToolTip(QApplication.translate("trimage", - "Recompress all images", None, QApplication.UnicodeUTF8)) + "Recompress all images", None)) self.recompress.setText(QApplication.translate("trimage", - "&Recompress", None, QApplication.UnicodeUTF8)) + "&Recompress", None)) self.recompress.setShortcut(QApplication.translate("trimage", - "Alt+R", None, QApplication.UnicodeUTF8)) + "Alt+R", None)) self.processedfiles.setToolTip(QApplication.translate("trimage", - "Drag files in here", None, QApplication.UnicodeUTF8)) + "Drag files in here", None)) self.processedfiles.setWhatsThis(QApplication.translate("trimage", - "Drag files in here", None, QApplication.UnicodeUTF8)) + "Drag files in here", None)) From cd80b8c386ab92da8154d9e7fce897e363832912 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 14:57:02 +0100 Subject: [PATCH 02/62] Replace setMargin with setContentsMargins --- src/trimage/ui.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/trimage/ui.py b/src/trimage/ui.py index ca3a98f..c79f4fe 100644 --- a/src/trimage/ui.py +++ b/src/trimage/ui.py @@ -45,7 +45,7 @@ class Ui_trimage(object): self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QGridLayout(self.centralwidget) - #self.gridLayout_2.setMargin(0) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") @@ -61,7 +61,7 @@ class Ui_trimage(object): self.verticalLayout = QVBoxLayout(self.widget) self.verticalLayout.setSpacing(0) - #self.verticalLayout.setMargin(0) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.frame = QFrame(self.widget) @@ -69,12 +69,12 @@ class Ui_trimage(object): self.verticalLayout_2 = QVBoxLayout(self.frame) self.verticalLayout_2.setSpacing(0) - #self.verticalLayout_2.setMargin(0) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) - #self.horizontalLayout.setMargin(10) + self.horizontalLayout.setContentsMargins(10, 10, 10, 10) self.horizontalLayout.setObjectName("horizontalLayout") self.addfiles = QPushButton(self.frame) @@ -94,7 +94,7 @@ class Ui_trimage(object): font.setPointSize(8) self.label.setFont(font) self.label.setFrameShadow(QFrame.Plain) - #self.label.setMargin(1) + self.label.setContentsMargins(1, 1, 1, 1) self.label.setIndent(10) self.label.setObjectName("label") self.horizontalLayout.addWidget(self.label) From a3d9735ef82dbf95f025040e58fc27b4da9c6757 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:06:47 +0100 Subject: [PATCH 03/62] Fix canceling open image --- src/trimage/trimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 5fb8bf5..8163678 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -153,7 +153,7 @@ class StartQT5(QMainWindow): "Image files (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG)") self.settings.setValue("fdstate", QVariant(fd.saveState())) - if images: + if images[0]: self.settings.setValue("directory", QVariant(path.dirname(images[0][0]))) self.delegator([fullpath for fullpath in images]) From a20f7a5bebb2a95ab4c54dd0504946a9f3cfcea1 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:09:25 +0100 Subject: [PATCH 04/62] Fix version in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bae6f17..4e6538b 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ if win: from distutils.core import setup setup(name = "trimage", - version = "1.0.2", + version = "1.0.5", description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files", author = "Kilian Valkhof, Paul Chaplin", author_email = "help@trimage.org", From 623922d12ddf9e77e35ff1ca07df8e2c13ffd846 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:16:57 +0100 Subject: [PATCH 05/62] Port quit signal to new system --- src/trimage/trimage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 8163678..df22b6b 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -64,8 +64,7 @@ class StartQT5(QMainWindow): # connect signals with slots self.ui.addfiles.clicked.connect(self.file_dialog) self.ui.recompress.clicked.connect(self.recompress_files) - # QObject.connect(self.quit_shortcut, SIGNAL("activated()"), - # qApp, SLOT('quit()')) + self.quit_shortcut.activated.connect(self.close) # QObject.connect(self.ui.processedfiles, SIGNAL("fileDropEvent"), # self.file_drop) # QObject.connect(self.thread, SIGNAL("finished()"), self.update_table) From 69fb5c9c88ad47420832f79a645be90da859f7c3 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:25:11 +0100 Subject: [PATCH 06/62] Move todo in root for more visibility and convert it to markdown --- TODO.md | 30 ++++++++++++++++++++++++++++++ resources/todo | 37 ------------------------------------- 2 files changed, 30 insertions(+), 37 deletions(-) create mode 100644 TODO.md delete mode 100644 resources/todo diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..6e5833f --- /dev/null +++ b/TODO.md @@ -0,0 +1,30 @@ +# Todo app wise +- general refactoring +- sys.exit(1) for errors -- how to handle? Not good to simply sys.exit() from + any random part of code (can leave things in a mess) +- consider context managers for handling compression, so as to keep operations + atomic and/or rollback-able +- add a recursive option on the command-line for use with -d +- make -f accept a list of files +- make the current verbose be "normal", and make -verbose print the commandline + app prints as well +- find a way to specify the version once for everywhere +- notification area drag/drop widget -> probably need gtk for gnome + +# Todo else +- figure out how to make mac and win versions (someone else :) <- via gui2exe + +# Later versions +- animate compressing.gif +- allow selection/deletion of rows from table (and subsequently the imagelist) +- punypng api? http://www.gracepointafterfive.com/punypng/api +- imagemagick/graphicsmagick? +- always on top option +- intelligently recompress, i.e. go through the list of files, recompress + each until no more gains are seen (and a sensible number-of-tries limit + isn't exceeded), and flag that file as fully-optimised. Repeat for each + file in the list, until all are done. Saves pointlessly trying to + optimise files. Consider the case of a directory of 100 files, already + optimised once. Recompressing maximally compresses 90. Recompressing + again would currently try to recompress all 100, when only 10 would be + worthy of trying to compress further. diff --git a/resources/todo b/resources/todo deleted file mode 100644 index a61422c..0000000 --- a/resources/todo +++ /dev/null @@ -1,37 +0,0 @@ -========================================== -todo app wise -- general refactoring -- sys.exit(1) for errors -- how to handle? Not good to simply sys.exit() from - any random part of code (can leave things in a mess) -- consider context managers for handling compression, so as to keep operations - atomic and/or rollback-able - -- add a recursive option on the command-line for use with -d -- make -f accept a list of files -- make the current verbose be "normal", and make -verbose print the commandline - app prints as well - -- find a way to specify the version once for everywhere -- notification area drag/drop widget - -> probably need gtk for gnome - -todo else -- figure out how to make mac and win versions (someone else :) <- via gui2exe - - -=========================================== -later versions: - animate compressing.gif - allow selection/deletion of rows from table (and subsequently the imagelist) - punypng api? http://www.gracepointafterfive.com/punypng/api - imagemagick/graphicsmagick? - always on top option - intelligently recompress, i.e. go through the list of files, recompress - each until no more gains are seen (and a sensible number-of-tries limit - isn't exceeded), and flag that file as fully-optimised. Repeat for each - file in the list, until all are done. Saves pointlessly trying to - optimise files. Consider the case of a directory of 100 files, already - optimised once. Recompressing maximally compresses 90. Recompressing - again would currently try to recompress all 100, when only 10 would be - worthy of trying to compress further. - From e71c22b5191757b8cace52528ddff4fe59ef69db Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:27:02 +0100 Subject: [PATCH 07/62] Improve the .gitignore --- .gitignore | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 731c196..af2f537 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,104 @@ -*.pyc -MANIFEST -dist/ -build/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ From bc8c665d40e2dfea8e98b908dc79df39a71f5577 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:32:09 +0100 Subject: [PATCH 08/62] Remove unuseful unicode call --- src/trimage/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trimage/ui.py b/src/trimage/ui.py index c79f4fe..f3bcd9b 100644 --- a/src/trimage/ui.py +++ b/src/trimage/ui.py @@ -22,7 +22,7 @@ class TrimageTableView(QTableView): event.accept() filelist = [] for url in event.mimeData().urls(): - filelist.append(unicode(url.toLocalFile())) + filelist.append(url.toLocalFile()) self.emit(SIGNAL("fileDropEvent"), (filelist)) From 420c945f945a7558233d2438ab0ca2492b327bcf Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:34:22 +0100 Subject: [PATCH 09/62] Just add a return line after the shebang --- src/trimage/trimage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index df22b6b..063f35e 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -1,4 +1,5 @@ #!/usr/bin/python + import time import sys import errno From 063d2b8adbf0fb326e638c26bc774efb19dd64c3 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:45:04 +0100 Subject: [PATCH 10/62] Replace some old signals --- src/trimage/trimage.py | 9 +++++---- src/trimage/ui.py | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 063f35e..027eac2 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -66,11 +66,10 @@ class StartQT5(QMainWindow): self.ui.addfiles.clicked.connect(self.file_dialog) self.ui.recompress.clicked.connect(self.recompress_files) self.quit_shortcut.activated.connect(self.close) - # QObject.connect(self.ui.processedfiles, SIGNAL("fileDropEvent"), - # self.file_drop) + self.ui.processedfiles.drop_event_signal.connect(self.file_drop) # QObject.connect(self.thread, SIGNAL("finished()"), self.update_table) # QObject.connect(self.thread, SIGNAL("terminated()"), self.update_table) - # QObject.connect(self.thread, SIGNAL("updateUi"), self.update_table) + self.thread.update_ui_signal.connect(self.update_table) self.compressing_icon = QIcon(QPixmap(self.ui.get_image("pixmaps/compressing.gif"))) @@ -460,6 +459,8 @@ class Image: class Worker(QThread): + update_ui_signal = pyqtSignal() + def __init__(self, parent=None): QThread.__init__(self, parent) self.toDisplay = Queue() @@ -487,7 +488,7 @@ class Worker(QThread): tp._ThreadPool__jobs.empty()): image = self.toDisplay.get() - ##self.emit(SIGNAL("updateUi")) + self.update_ui_signal.emit() if not self.showapp and self.verbose: # we work via the commandline if image.retcode == 0: diff --git a/src/trimage/ui.py b/src/trimage/ui.py index f3bcd9b..53a2794 100644 --- a/src/trimage/ui.py +++ b/src/trimage/ui.py @@ -4,6 +4,9 @@ from PyQt5.QtWidgets import * from os import path class TrimageTableView(QTableView): + + drop_event_signal = pyqtSignal(list) + """Init the table drop event.""" def __init__(self, parent=None): super(TrimageTableView, self).__init__(parent) @@ -24,7 +27,7 @@ class TrimageTableView(QTableView): for url in event.mimeData().urls(): filelist.append(url.toLocalFile()) - self.emit(SIGNAL("fileDropEvent"), (filelist)) + self.drop_event_signal.emit(filelist) class Ui_trimage(object): From a24ef15ff5d99e10cf6eb76d39f2e0826263e820 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 15:50:51 +0100 Subject: [PATCH 11/62] Fix bug introduced by the first commit with list of images --- src/trimage/trimage.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 027eac2..fea9765 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -152,8 +152,9 @@ class StartQT5(QMainWindow): "Image files (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG)") self.settings.setValue("fdstate", QVariant(fd.saveState())) - if images[0]: - self.settings.setValue("directory", QVariant(path.dirname(images[0][0]))) + images = images[0] + if images: + self.settings.setValue("directory", QVariant(path.dirname(images[0]))) self.delegator([fullpath for fullpath in images]) def recompress_files(self): @@ -172,16 +173,16 @@ class StartQT5(QMainWindow): for fullpath in images: try: # recompress images already in the list image = (i.image for i in self.imagelist - if i.image.fullpath == fullpath[0]).__next__() + if i.image.fullpath == fullpath).__next__() if image.compressed: image.reset() image.recompression = True delegatorlist.append(image) except StopIteration: - if not path.isdir(fullpath[0]): - self.add_image(fullpath[0], delegatorlist) + if not path.isdir(fullpath): + self.add_image(fullpath, delegatorlist) else: - self.walk(fullpath[0], delegatorlist) + self.walk(fullpath, delegatorlist) self.update_table() self.thread.compress_file(delegatorlist, self.showapp, self.verbose, From 25836634afd57a5fd45f5aa538a8da8b818ad454 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 16:12:44 +0100 Subject: [PATCH 12/62] setup.py: change requires from pyqt4 to pyqt5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e6538b..51add46 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ setup(name = "trimage", ('share/man/man1', ['doc/trimage.1'])], scripts = ["trimage"], long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via optipng, advpng and jpegoptim, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""", - requires = ["PyQt4 (>=4.4)"], + requires = ["PyQt5"], #for py2exe windows=[r'src\trimage\trimage.py'], From 278ef269cf54ee97c3cff8f28b49d90c78ed1804 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 16:16:59 +0100 Subject: [PATCH 13/62] Reintroduce threadpool and filesize packages --- src/trimage/ThreadPool/ThreadPool.py | 280 +++++++++++++++++++++++++ src/trimage/ThreadPool/__init__.py | 1 + src/trimage/filesize/PKG-INFO | 71 +++++++ src/trimage/filesize/README.txt | 50 +++++ src/trimage/filesize/__init__.py | 2 + src/trimage/{ => filesize}/filesize.py | 0 src/trimage/trimage.py | 2 +- 7 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 src/trimage/ThreadPool/ThreadPool.py create mode 100644 src/trimage/ThreadPool/__init__.py create mode 100644 src/trimage/filesize/PKG-INFO create mode 100644 src/trimage/filesize/README.txt create mode 100644 src/trimage/filesize/__init__.py rename src/trimage/{ => filesize}/filesize.py (100%) diff --git a/src/trimage/ThreadPool/ThreadPool.py b/src/trimage/ThreadPool/ThreadPool.py new file mode 100644 index 0000000..5c64409 --- /dev/null +++ b/src/trimage/ThreadPool/ThreadPool.py @@ -0,0 +1,280 @@ +''' +ThreadPool Implementation + +@author: Morten Holdflod Moeller - morten@holdflod.dk +@license: LGPL v3 +''' + +from __future__ import with_statement +from threading import Thread, RLock +from time import sleep +from Queue import Queue, Empty +import logging +import sys + +class NullHandler(logging.Handler): + def emit(self, record): + pass + +h = sys.stderr +logging.getLogger('threadpool').addHandler(h) +logging.getLogger('threadpool.worker').addHandler(h) + + + +class ThreadPoolMixIn: + """Mix-in class to handle each request in a new thread from the ThreadPool.""" + + def __init__(self, threadpool=None): + if (threadpool == None): + threadpool = ThreadPool() + self.__private_threadpool = True + else: + self.__private_threadpool = False + + self.__threadpool = threadpool + + def process_request_thread(self, request, client_address): + """Same as in BaseServer but as a thread. + + In addition, exception handling is done here. + + """ + try: + self.finish_request(request, client_address) + self.close_request(request) + except: + self.handle_error(request, client_address) #IGNORE:W0702 + self.close_request(request) + + def process_request(self, request, client_address): + self.__threadpool.add_job(self.process_request_thread, [request, client_address]) + + def shutdown(self): + if (self.__private_threadpool): self.__threadpool.shutdown() + + +class AddJobException(Exception): + ''' + Exceptoion raised when a Job could not be added + to the queue + ''' + def __init__(self, msg): + Exception.__init__(self, msg) + + +class ThreadPool: + ''' + The class implementing the ThreadPool. + + Instantiate and add jobs using add_job(func, args_list) + ''' + + class Job: #IGNORE:R0903 + ''' + Class encapsulating a job to be handled + by ThreadPool workers + ''' + def __init__(self, function, args, return_callback=None): + self.callable = function + self.arguments = args + self.return_callback = return_callback + + def execute(self): + ''' + Called to execute the function + ''' + try: + return_value = self.callable(*self.arguments) #IGNORE:W0142 + except Exception, excep: #IGNORE:W0703 + logger = logging.getLogger("threadpool.worker") + logger.warning("A job in the ThreadPool raised an exception: " + excep) + #else do nothing cause we don't know what to do... + return + + try: + if (self.return_callback != None): + self.return_callback(return_value) + except Exception, _: #IGNORE:W0703 everything could go wrong... + logger = logging.getLogger('threadpool') + logger.warning('Error while delivering return value to callback function') + + class Worker(Thread): + ''' + A worker thread handling jobs in the thread pool + job queue + ''' + + def __init__(self, pool): + Thread.__init__(self) + + if (not isinstance(pool, ThreadPool)): + raise TypeError("pool is not a ThreadPool instance") + + self.pool = pool + + self.alive = True + self.start() + + def run(self): + ''' + The workers main-loop getting jobs from queue + and executing them + ''' + while self.alive: + #print self.pool.__active_worker_count, self.pool.__worker_count + job = self.pool.get_job() + if (job != None): + self.pool.worker_active() + job.execute() + self.pool.worker_inactive() + else: + self.alive = False + + self.pool.punch_out() + + def __init__(self, max_workers = 5, kill_workers_after = 3): + if (not isinstance(max_workers, int)): + raise TypeError("max_workers is not an int") + if (max_workers < 1): + raise ValueError('max_workers must be >= 1') + + if (not isinstance(kill_workers_after, int)): + raise TypeError("kill_workers_after is not an int") + + self.__max_workers = max_workers + self.__kill_workers_after = kill_workers_after + + # This Queue is assumed Thread Safe + self.__jobs = Queue() + + self.__worker_count_lock = RLock() + self.__worker_count = 0 + self.__active_worker_count = 0 + + self.__shutting_down = False + logger = logging.getLogger('threadpool') + logger.info('started') + + def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5): + if (not isinstance(clean_shutdown_reties, int)): + raise TypeError("clean_shutdown_reties is not an int") + if (not clean_shutdown_reties >= 0): + raise ValueError('clean_shutdown_reties must be >= 0') + + if (not isinstance(wait_for_workers_period, int)): + raise TypeError("wait_for_workers_period is not an int") + if (not wait_for_workers_period >= 0): + raise ValueError('wait_for_workers_period must be >= 0') + + logger = logging.getLogger("threadpool") + logger.info("shutting down") + + with self.__worker_count_lock: + self.__shutting_down = True + self.__max_workers = 0 + self.__kill_workers_after = 0 + + retries_left = clean_shutdown_reties + while (retries_left > 0): + + with self.__worker_count_lock: + logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count)) + if (self.__worker_count > 0): + retries_left -= 1 + else: + retries_left = 0 + + sleep(wait_for_workers_period) + + + with self.__worker_count_lock: + if (self.__worker_count > 0): + logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count) + clean_shutdown = False + else: + clean_shutdown = True + + logger.info("shutdown complete") + + return clean_shutdown + + def punch_out(self): + ''' + Called by worker to update worker count + when the worker is shutting down + ''' + with self.__worker_count_lock: + self.__worker_count -= 1 + + def __new_worker(self): + ''' + Adding a new worker thread to the thread pool + ''' + with self.__worker_count_lock: + ThreadPool.Worker(self) + self.__worker_count += 1 + + def worker_active(self): + with self.__worker_count_lock: + self.__active_worker_count = self.__active_worker_count + 1 + + def worker_inactive(self): + with self.__worker_count_lock: + self.__active_worker_count = self.__active_worker_count - 1 + + def add_job(self, function, args = None, return_callback=None): + ''' + Put new job into queue + ''' + + if (not callable(function)): + raise TypeError("function is not a callable") + if (not ( args == None or isinstance(args, list))): + raise TypeError("args is not a list") + if (not (return_callback == None or callable(return_callback))): + raise TypeError("return_callback is not a callable") + + if (args == None): + args = [] + + job = ThreadPool.Job(function, args, return_callback) + + with self.__worker_count_lock: + if (self.__shutting_down): + raise AddJobException("ThreadPool is shutting down") + + try: + start_new_worker = False + if (self.__worker_count < self.__max_workers): + if (self.__active_worker_count == self.__worker_count): + start_new_worker = True + + self.__jobs.put(job) + + if (start_new_worker): + self.__new_worker() + + except Exception: + raise AddJobException("Could not add job") + + + def get_job(self): + ''' + Retrieve next job from queue + workers die (and should) when + returning None + ''' + + job = None + try: + if (self.__kill_workers_after < 0): + job = self.__jobs.get(True) + elif (self.__kill_workers_after == 0): + job = self.__jobs.get(False) + else: + job = self.__jobs.get(True, self.__kill_workers_after) + except Empty: + job = None + + return job diff --git a/src/trimage/ThreadPool/__init__.py b/src/trimage/ThreadPool/__init__.py new file mode 100644 index 0000000..727a8b3 --- /dev/null +++ b/src/trimage/ThreadPool/__init__.py @@ -0,0 +1 @@ +from ThreadPool import ThreadPool, ThreadPoolMixIn \ No newline at end of file diff --git a/src/trimage/filesize/PKG-INFO b/src/trimage/filesize/PKG-INFO new file mode 100644 index 0000000..fe3aadb --- /dev/null +++ b/src/trimage/filesize/PKG-INFO @@ -0,0 +1,71 @@ +Metadata-Version: 1.0 +Name: hurry.filesize +Version: 0.9 +Summary: A simple Python library for human readable file sizes (or anything sized in bytes). +Home-page: UNKNOWN +Author: Martijn Faassen, Startifact +Author-email: faassen@startifact.com +License: ZPL 2.1 +Description: hurry.filesize + ============== + + hurry.filesize a simple Python library that can take a number of bytes and + returns a human-readable string with the size in it, in kilobytes (K), + megabytes (M), etc. + + The default system it uses is "traditional", where multipliers of 1024 + increase the unit size:: + + >>> from hurry.filesize import size + >>> size(1024) + '1K' + + An alternative, slightly more verbose system:: + + >>> from hurry.filesize import alternative + >>> size(1, system=alternative) + '1 byte' + >>> size(10, system=alternative) + '10 bytes' + >>> size(1024, system=alternative) + '1 KB' + + A verbose system:: + + >>> from hurry.filesize import verbose + >>> size(10, system=verbose) + '10 bytes' + >>> size(1024, system=verbose) + '1 kilobyte' + >>> size(2000, system=verbose) + '1 kilobyte' + >>> size(3000, system=verbose) + '2 kilobytes' + >>> size(1024 * 1024, system=verbose) + '1 megabyte' + >>> size(1024 * 1024 * 3, system=verbose) + '3 megabytes' + + You can also use the SI system, where multipliers of 1000 increase the unit + size:: + + >>> from hurry.filesize import si + >>> size(1000, system=si) + '1K' + + + Changes + ======= + + 0.9 (2009-03-11) + ---------------- + + * Initial public release. + + Download + ======== + +Keywords: file size bytes +Platform: UNKNOWN +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/trimage/filesize/README.txt b/src/trimage/filesize/README.txt new file mode 100644 index 0000000..6a0fad6 --- /dev/null +++ b/src/trimage/filesize/README.txt @@ -0,0 +1,50 @@ +hurry.filesize +============== + +hurry.filesize a simple Python library that can take a number of bytes and +returns a human-readable string with the size in it, in kilobytes (K), +megabytes (M), etc. + +The default system it uses is "traditional", where multipliers of 1024 +increase the unit size:: + + >>> from hurry.filesize import size + >>> size(1024) + '1K' + +An alternative, slightly more verbose system:: + + >>> from hurry.filesize import alternative + >>> size(1, system=alternative) + '1 byte' + >>> size(10, system=alternative) + '10 bytes' + >>> size(1024, system=alternative) + '1 KB' + +A verbose system:: + + >>> from hurry.filesize import verbose + >>> size(10, system=verbose) + '10 bytes' + >>> size(1024, system=verbose) + '1 kilobyte' + >>> size(2000, system=verbose) + '1 kilobyte' + >>> size(3000, system=verbose) + '2 kilobytes' + >>> size(1024 * 1024, system=verbose) + '1 megabyte' + >>> size(1024 * 1024 * 3, system=verbose) + '3 megabytes' + +You can also use the SI system, where multipliers of 1000 increase the unit +size:: + + >>> from hurry.filesize import si + >>> size(1000, system=si) + '1K' + + +http://pypi.python.org/pypi/hurry.filesize + diff --git a/src/trimage/filesize/__init__.py b/src/trimage/filesize/__init__.py new file mode 100644 index 0000000..68d9fc2 --- /dev/null +++ b/src/trimage/filesize/__init__.py @@ -0,0 +1,2 @@ +from filesize import size +from filesize import traditional, alternative, verbose, iec, si diff --git a/src/trimage/filesize.py b/src/trimage/filesize/filesize.py similarity index 100% rename from src/trimage/filesize.py rename to src/trimage/filesize/filesize.py diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index fea9765..5ca9a07 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -19,7 +19,7 @@ from filesize import * from imghdr import what as determinetype from queue import Queue -from ThreadPool import ThreadPool, ThreadPoolMixIn +from ThreadPool import ThreadPool from multiprocessing import cpu_count from ui import Ui_trimage From 352ba03d0226d88616ade1e5697b9fc15b75f21d Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 16:19:21 +0100 Subject: [PATCH 14/62] Revert to initial gitignore --- .gitignore | 106 ++--------------------------------------------------- 1 file changed, 3 insertions(+), 103 deletions(-) diff --git a/.gitignore b/.gitignore index af2f537..0bdf455 100644 --- a/.gitignore +++ b/.gitignore @@ -1,104 +1,4 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg +*.pyc MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -.static_storage/ -.media/ -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ +dist/ +build/ From 648f78c13915b4ac064c42f22edec29aa9e53a86 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 16:20:21 +0100 Subject: [PATCH 15/62] Remove unuseful file anymore (due too reintroduced packages) --- src/trimage/ThreadPool.py | 280 -------------------------------------- 1 file changed, 280 deletions(-) delete mode 100644 src/trimage/ThreadPool.py diff --git a/src/trimage/ThreadPool.py b/src/trimage/ThreadPool.py deleted file mode 100644 index 7f92344..0000000 --- a/src/trimage/ThreadPool.py +++ /dev/null @@ -1,280 +0,0 @@ -''' -ThreadPool Implementation - -@author: Morten Holdflod Moeller - morten@holdflod.dk -@license: LGPL v3 -''' - -from __future__ import with_statement -from threading import Thread, RLock -from time import sleep -from queue import Queue, Empty -import logging -import sys - -class NullHandler(logging.Handler): - def emit(self, record): - pass - -h = sys.stderr -logging.getLogger('threadpool').addHandler(h) -logging.getLogger('threadpool.worker').addHandler(h) - - - -class ThreadPoolMixIn: - """Mix-in class to handle each request in a new thread from the ThreadPool.""" - - def __init__(self, threadpool=None): - if (threadpool == None): - threadpool = ThreadPool() - self.__private_threadpool = True - else: - self.__private_threadpool = False - - self.__threadpool = threadpool - - def process_request_thread(self, request, client_address): - """Same as in BaseServer but as a thread. - - In addition, exception handling is done here. - - """ - try: - self.finish_request(request, client_address) - self.close_request(request) - except: - self.handle_error(request, client_address) #IGNORE:W0702 - self.close_request(request) - - def process_request(self, request, client_address): - self.__threadpool.add_job(self.process_request_thread, [request, client_address]) - - def shutdown(self): - if (self.__private_threadpool): self.__threadpool.shutdown() - - -class AddJobException(Exception): - ''' - Exceptoion raised when a Job could not be added - to the queue - ''' - def __init__(self, msg): - Exception.__init__(self, msg) - - -class ThreadPool: - ''' - The class implementing the ThreadPool. - - Instantiate and add jobs using add_job(func, args_list) - ''' - - class Job: #IGNORE:R0903 - ''' - Class encapsulating a job to be handled - by ThreadPool workers - ''' - def __init__(self, function, args, return_callback=None): - self.callable = function - self.arguments = args - self.return_callback = return_callback - - def execute(self): - ''' - Called to execute the function - ''' - try: - return_value = self.callable(*self.arguments) #IGNORE:W0142 - except Exception as excep: #IGNORE:W0703 - logger = logging.getLogger("threadpool.worker") - logger.warning("A job in the ThreadPool raised an exception: " + excep) - #else do nothing cause we don't know what to do... - return - - try: - if (self.return_callback != None): - self.return_callback(return_value) - except Exception as _: #IGNORE:W0703 everything could go wrong... - logger = logging.getLogger('threadpool') - logger.warning('Error while delivering return value to callback function') - - class Worker(Thread): - ''' - A worker thread handling jobs in the thread pool - job queue - ''' - - def __init__(self, pool): - Thread.__init__(self) - - if (not isinstance(pool, ThreadPool)): - raise TypeError("pool is not a ThreadPool instance") - - self.pool = pool - - self.alive = True - self.start() - - def run(self): - ''' - The workers main-loop getting jobs from queue - and executing them - ''' - while self.alive: - #print self.pool.__active_worker_count, self.pool.__worker_count - job = self.pool.get_job() - if (job != None): - self.pool.worker_active() - job.execute() - self.pool.worker_inactive() - else: - self.alive = False - - self.pool.punch_out() - - def __init__(self, max_workers = 5, kill_workers_after = 3): - if (not isinstance(max_workers, int)): - raise TypeError("max_workers is not an int") - if (max_workers < 1): - raise ValueError('max_workers must be >= 1') - - if (not isinstance(kill_workers_after, int)): - raise TypeError("kill_workers_after is not an int") - - self.__max_workers = max_workers - self.__kill_workers_after = kill_workers_after - - # This Queue is assumed Thread Safe - self.__jobs = Queue() - - self.__worker_count_lock = RLock() - self.__worker_count = 0 - self.__active_worker_count = 0 - - self.__shutting_down = False - logger = logging.getLogger('threadpool') - logger.info('started') - - def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5): - if (not isinstance(clean_shutdown_reties, int)): - raise TypeError("clean_shutdown_reties is not an int") - if (not clean_shutdown_reties >= 0): - raise ValueError('clean_shutdown_reties must be >= 0') - - if (not isinstance(wait_for_workers_period, int)): - raise TypeError("wait_for_workers_period is not an int") - if (not wait_for_workers_period >= 0): - raise ValueError('wait_for_workers_period must be >= 0') - - logger = logging.getLogger("threadpool") - logger.info("shutting down") - - with self.__worker_count_lock: - self.__shutting_down = True - self.__max_workers = 0 - self.__kill_workers_after = 0 - - retries_left = clean_shutdown_reties - while (retries_left > 0): - - with self.__worker_count_lock: - logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count)) - if (self.__worker_count > 0): - retries_left -= 1 - else: - retries_left = 0 - - sleep(wait_for_workers_period) - - - with self.__worker_count_lock: - if (self.__worker_count > 0): - logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count) - clean_shutdown = False - else: - clean_shutdown = True - - logger.info("shutdown complete") - - return clean_shutdown - - def punch_out(self): - ''' - Called by worker to update worker count - when the worker is shutting down - ''' - with self.__worker_count_lock: - self.__worker_count -= 1 - - def __new_worker(self): - ''' - Adding a new worker thread to the thread pool - ''' - with self.__worker_count_lock: - ThreadPool.Worker(self) - self.__worker_count += 1 - - def worker_active(self): - with self.__worker_count_lock: - self.__active_worker_count = self.__active_worker_count + 1 - - def worker_inactive(self): - with self.__worker_count_lock: - self.__active_worker_count = self.__active_worker_count - 1 - - def add_job(self, function, args = None, return_callback=None): - ''' - Put new job into queue - ''' - - if (not callable(function)): - raise TypeError("function is not a callable") - if (not ( args == None or isinstance(args, list))): - raise TypeError("args is not a list") - if (not (return_callback == None or callable(return_callback))): - raise TypeError("return_callback is not a callable") - - if (args == None): - args = [] - - job = ThreadPool.Job(function, args, return_callback) - - with self.__worker_count_lock: - if (self.__shutting_down): - raise AddJobException("ThreadPool is shutting down") - - try: - start_new_worker = False - if (self.__worker_count < self.__max_workers): - if (self.__active_worker_count == self.__worker_count): - start_new_worker = True - - self.__jobs.put(job) - - if (start_new_worker): - self.__new_worker() - - except Exception: - raise AddJobException("Could not add job") - - - def get_job(self): - ''' - Retrieve next job from queue - workers die (and should) when - returning None - ''' - - job = None - try: - if (self.__kill_workers_after < 0): - job = self.__jobs.get(True) - elif (self.__kill_workers_after == 0): - job = self.__jobs.get(False) - else: - job = self.__jobs.get(True, self.__kill_workers_after) - except Empty: - job = None - - return job From 461ae9fd57b0902b09d5f9fd14515c46c6b372a3 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 16:25:49 +0100 Subject: [PATCH 16/62] Reintroduce missing changes --- src/trimage/ThreadPool/ThreadPool.py | 124 +++++++++++++-------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/src/trimage/ThreadPool/ThreadPool.py b/src/trimage/ThreadPool/ThreadPool.py index 5c64409..7f92344 100644 --- a/src/trimage/ThreadPool/ThreadPool.py +++ b/src/trimage/ThreadPool/ThreadPool.py @@ -2,13 +2,13 @@ ThreadPool Implementation @author: Morten Holdflod Moeller - morten@holdflod.dk -@license: LGPL v3 +@license: LGPL v3 ''' from __future__ import with_statement from threading import Thread, RLock from time import sleep -from Queue import Queue, Empty +from queue import Queue, Empty import logging import sys @@ -31,7 +31,7 @@ class ThreadPoolMixIn: self.__private_threadpool = True else: self.__private_threadpool = False - + self.__threadpool = threadpool def process_request_thread(self, request, client_address): @@ -48,11 +48,11 @@ class ThreadPoolMixIn: self.close_request(request) def process_request(self, request, client_address): - self.__threadpool.add_job(self.process_request_thread, [request, client_address]) + self.__threadpool.add_job(self.process_request_thread, [request, client_address]) def shutdown(self): if (self.__private_threadpool): self.__threadpool.shutdown() - + class AddJobException(Exception): ''' @@ -61,64 +61,64 @@ class AddJobException(Exception): ''' def __init__(self, msg): Exception.__init__(self, msg) - + class ThreadPool: ''' The class implementing the ThreadPool. - - Instantiate and add jobs using add_job(func, args_list) + + Instantiate and add jobs using add_job(func, args_list) ''' class Job: #IGNORE:R0903 ''' Class encapsulating a job to be handled - by ThreadPool workers + by ThreadPool workers ''' def __init__(self, function, args, return_callback=None): self.callable = function self.arguments = args self.return_callback = return_callback - + def execute(self): ''' Called to execute the function ''' try: return_value = self.callable(*self.arguments) #IGNORE:W0142 - except Exception, excep: #IGNORE:W0703 + except Exception as excep: #IGNORE:W0703 logger = logging.getLogger("threadpool.worker") logger.warning("A job in the ThreadPool raised an exception: " + excep) #else do nothing cause we don't know what to do... - return - + return + try: if (self.return_callback != None): self.return_callback(return_value) - except Exception, _: #IGNORE:W0703 everything could go wrong... + except Exception as _: #IGNORE:W0703 everything could go wrong... logger = logging.getLogger('threadpool') logger.warning('Error while delivering return value to callback function') class Worker(Thread): ''' - A worker thread handling jobs in the thread pool + A worker thread handling jobs in the thread pool job queue ''' - + def __init__(self, pool): Thread.__init__(self) - + if (not isinstance(pool, ThreadPool)): raise TypeError("pool is not a ThreadPool instance") - + self.pool = pool - + self.alive = True self.start() - + def run(self): ''' - The workers main-loop getting jobs from queue + The workers main-loop getting jobs from queue and executing them ''' while self.alive: @@ -130,83 +130,83 @@ class ThreadPool: self.pool.worker_inactive() else: self.alive = False - + self.pool.punch_out() - + def __init__(self, max_workers = 5, kill_workers_after = 3): if (not isinstance(max_workers, int)): raise TypeError("max_workers is not an int") if (max_workers < 1): raise ValueError('max_workers must be >= 1') - + if (not isinstance(kill_workers_after, int)): raise TypeError("kill_workers_after is not an int") - + self.__max_workers = max_workers self.__kill_workers_after = kill_workers_after - + # This Queue is assumed Thread Safe self.__jobs = Queue() - - self.__worker_count_lock = RLock() + + self.__worker_count_lock = RLock() self.__worker_count = 0 self.__active_worker_count = 0 - + self.__shutting_down = False logger = logging.getLogger('threadpool') logger.info('started') - + def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5): if (not isinstance(clean_shutdown_reties, int)): raise TypeError("clean_shutdown_reties is not an int") if (not clean_shutdown_reties >= 0): raise ValueError('clean_shutdown_reties must be >= 0') - + if (not isinstance(wait_for_workers_period, int)): raise TypeError("wait_for_workers_period is not an int") if (not wait_for_workers_period >= 0): raise ValueError('wait_for_workers_period must be >= 0') - + logger = logging.getLogger("threadpool") logger.info("shutting down") - + with self.__worker_count_lock: self.__shutting_down = True self.__max_workers = 0 self.__kill_workers_after = 0 - + retries_left = clean_shutdown_reties while (retries_left > 0): - + with self.__worker_count_lock: logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count)) if (self.__worker_count > 0): retries_left -= 1 else: retries_left = 0 - + sleep(wait_for_workers_period) - - + + with self.__worker_count_lock: if (self.__worker_count > 0): logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count) clean_shutdown = False else: clean_shutdown = True - + logger.info("shutdown complete") - + return clean_shutdown - + def punch_out(self): ''' - Called by worker to update worker count + Called by worker to update worker count when the worker is shutting down ''' with self.__worker_count_lock: self.__worker_count -= 1 - + def __new_worker(self): ''' Adding a new worker thread to the thread pool @@ -214,58 +214,58 @@ class ThreadPool: with self.__worker_count_lock: ThreadPool.Worker(self) self.__worker_count += 1 - + def worker_active(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count + 1 - + def worker_inactive(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count - 1 - + def add_job(self, function, args = None, return_callback=None): ''' Put new job into queue ''' - + if (not callable(function)): raise TypeError("function is not a callable") if (not ( args == None or isinstance(args, list))): raise TypeError("args is not a list") if (not (return_callback == None or callable(return_callback))): raise TypeError("return_callback is not a callable") - + if (args == None): args = [] - + job = ThreadPool.Job(function, args, return_callback) - + with self.__worker_count_lock: if (self.__shutting_down): raise AddJobException("ThreadPool is shutting down") - + try: start_new_worker = False if (self.__worker_count < self.__max_workers): if (self.__active_worker_count == self.__worker_count): start_new_worker = True - + self.__jobs.put(job) - - if (start_new_worker): + + if (start_new_worker): self.__new_worker() - + except Exception: raise AddJobException("Could not add job") - - + + def get_job(self): ''' - Retrieve next job from queue - workers die (and should) when - returning None + Retrieve next job from queue + workers die (and should) when + returning None ''' - + job = None try: if (self.__kill_workers_after < 0): @@ -276,5 +276,5 @@ class ThreadPool: job = self.__jobs.get(True, self.__kill_workers_after) except Empty: job = None - + return job From 797cc433b37bb699bc6f13c418197537690e4676 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 16:40:05 +0100 Subject: [PATCH 17/62] Readme file in markdown --- README | 15 --------------- README.md | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index ec43bc4..0000000 --- a/README +++ /dev/null @@ -1,15 +0,0 @@ -Trimage image compressor -A cross-platform tool for optimizing PNG and JPG files. - -Trimage is a cross-platform GUI and command-line interface to optimize image -files via "optipng":http://optipng.sourceforge.net/, -"advpng":http://advancemame.sourceforge.net/comp-readme.html and -"jpegoptim":http://www.kokkonen.net/tjko/projects.html, depending on the -filetype (currently, PNG and JPG files are supported). It was inspired by -"imageoptim":http://imageoptim.pornel.net/. All image files are losslessly -compressed on the highest available compression levels. Trimage gives you -various input functions to fit your own workflow: A regular file dialog, -dragging and dropping and various command line options. - -Visit "Trimage.org":http://trimage.org for more information - diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d01128 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Trimage image compressor +A cross-platform tool for optimizing PNG and JPG files. + +Trimage is a cross-platform GUI and command-line interface to optimize image files via [optipng](http://optipng.sourceforge.net), [advpng](http://advancemame.sourceforge.net/comp-readme.html) and [jpegoptim](http://www.kokkonen.net/tjko/projects.html), depending on the +filetype (currently, PNG and JPG files are supported). +It was inspired by +[imageoptim](http://imageoptim.pornel.net). + +All image files are losslessly +compressed on the highest available compression levels. Trimage gives you +various input functions to fit your own workflow: a regular file dialog, +dragging and dropping and various command line options. + +Visit [Trimage.org](http://trimage.org) for more information. From d7a0f84f640b7b6871ce4b68f39412c1bb9c5d42 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 17:02:31 +0100 Subject: [PATCH 18/62] Adopt the new signal system for remaining items --- src/trimage/trimage.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 5ca9a07..9814eb7 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -67,8 +67,7 @@ class StartQT5(QMainWindow): self.ui.recompress.clicked.connect(self.recompress_files) self.quit_shortcut.activated.connect(self.close) self.ui.processedfiles.drop_event_signal.connect(self.file_drop) - # QObject.connect(self.thread, SIGNAL("finished()"), self.update_table) - # QObject.connect(self.thread, SIGNAL("terminated()"), self.update_table) + self.thread.finished.connect(self.update_table) self.thread.update_ui_signal.connect(self.update_table) self.compressing_icon = QIcon(QPixmap(self.ui.get_image("pixmaps/compressing.gif"))) @@ -101,7 +100,7 @@ class StartQT5(QMainWindow): # make sure we quit after processing finished if using cli if options.filename or options.directory: - QObject.connect(self.thread, SIGNAL("finished()"), quit) + self.thread.finished.connect(quit) self.cli = True # send to correct function @@ -512,15 +511,14 @@ class Systray(QWidget): def createActions(self): self.quitAction = QAction(self.tr("&Quit"), self) - QObject.connect(self.quitAction, SIGNAL("triggered()"), - qApp, SLOT("quit()")) + self.quitAction.triggered.connect(self.parent.close) 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.addFiles.triggered.connect(self.parent.file_dialog) self.recompress = QAction(self.tr("&Recompress"), self) icon2 = QIcon() @@ -528,10 +526,11 @@ class Systray(QWidget): QIcon.Normal, QIcon.Off) self.recompress.setIcon(icon2) self.recompress.setDisabled(True) - QObject.connect(self.addFiles, SIGNAL("triggered()"), self.parent.recompress_files) + + self.addFiles.triggered.connect(self.parent.recompress_files) self.hideMain = QAction(self.tr("&Hide window"), self) - QObject.connect(self.hideMain, SIGNAL("triggered()"), self.parent.hide_main_window) + self.hideMain.triggered.connect(self.parent.hide_main_window) def createTrayIcon(self): self.trayIconMenu = QMenu(self) From 573dcab3d202fc4b6789045758aeaed13601ee76 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 18:04:15 +0100 Subject: [PATCH 19/62] Remove decode methods (Python 3 is utf8 by default) --- src/trimage/trimage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 9814eb7..0818aee 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -105,9 +105,9 @@ class StartQT5(QMainWindow): # send to correct function if options.filename: - self.file_from_cmd(options.filename.decode("utf-8")) + self.file_from_cmd(options.filename) if options.directory: - self.dir_from_cmd(options.directory.decode("utf-8")) + self.dir_from_cmd(options.directory) self.verbose = options.verbose From 59b3d8c94d2aa9e0541026cc4480bd8e081dd960 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 18:05:28 +0100 Subject: [PATCH 20/62] Python 3 use utf8 by default --- trimage | 1 - 1 file changed, 1 deletion(-) diff --git a/trimage b/trimage index 58d7c4a..6906ad3 100644 --- a/trimage +++ b/trimage @@ -1,5 +1,4 @@ #!/usr/bin/env python -#coding: utf-8 # #Copyright (c) 2010 Kilian Valkhof, Paul Chaplin, Tarnay Kálmán # From 3a19f3cef82fe46009ccfbc26459bd27eff89c7c Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 20:18:48 +0100 Subject: [PATCH 21/62] Don't need "(object)" for classes in Python3 --- src/trimage/ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trimage/ui.py b/src/trimage/ui.py index 53a2794..b855a92 100644 --- a/src/trimage/ui.py +++ b/src/trimage/ui.py @@ -30,7 +30,7 @@ class TrimageTableView(QTableView): self.drop_event_signal.emit(filelist) -class Ui_trimage(object): +class Ui_trimage(): def get_image(self, image): """ Get the correct link to the images used in the UI """ imagelink = path.join(path.dirname(path.dirname(path.realpath(__file__))), "trimage/" + image) From 23c3243209fdd86ba5650450bf20832ba918fddd Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sun, 19 Nov 2017 17:48:13 +0100 Subject: [PATCH 22/62] Remove two remaining "u" chars --- src/trimage/trimage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 0818aee..582120e 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -429,8 +429,8 @@ class Image: self.compressing = True exe = ".exe" if (sys.platform == "win32") else "" runString = { - "jpeg": u"jpegoptim" + exe + " -f --strip-all '%(file)s'", - "png": u"optipng" + exe + " -force -o7 '%(file)s'&&advpng" + exe + " -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'" + "jpeg": "jpegoptim" + exe + " -f --strip-all '%(file)s'", + "png": "optipng" + exe + " -force -o7 '%(file)s'&&advpng" + exe + " -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'" } # Create a backup file copy(self.fullpath, self.fullpath + '~') From 8e415cb45199a0714c00ab6a8889219a30465ab3 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 25 Nov 2017 17:08:49 +0100 Subject: [PATCH 23/62] Update debian control file to Python 3 and Qt5 --- debian/control | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/debian/control b/debian/control index 68bdb14..d2501f0 100644 --- a/debian/control +++ b/debian/control @@ -3,13 +3,13 @@ Section: graphics Priority: optional Maintainer: Kilian Valkhof Build-Depends: debhelper (>=7), python-support (>=0.8.7), python -XS-Python-Version: >=2.6 +XS-Python-Version: >=3.4 Standards-Version: 3.9.1 Homepage: http://trimage.org Package: trimage Architecture: all -Depends: ${misc:Depends}, ${python:Depends}, python-qt4 (>=4.4), optipng (>=0.6.2.1), advancecomp (>=1.15), jpegoptim (>=1.2.2), pngcrush (>=1.6.7) +Depends: ${misc:Depends}, ${python:Depends}, python-pyqt5 (>=5.7), optipng (>=0.6.2.1), advancecomp (>=1.15), jpegoptim (>=1.2.2), pngcrush (>=1.6.7) XB-Python-Version: ${python:Versions} Description: GUI and command-line interface to optimize image files Trimage is a cross-platform GUI and command-line interface to optimize image @@ -18,4 +18,3 @@ Description: GUI and command-line interface to optimize image files compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options. - From 42008757f213beab59949b9eff4dafa508196db3 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sun, 26 Nov 2017 11:53:11 +0100 Subject: [PATCH 24/62] Redesign todo --- TODO.md | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/TODO.md b/TODO.md index 6e5833f..b375a3b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,30 +1,17 @@ -# Todo app wise +# Todo + - general refactoring -- sys.exit(1) for errors -- how to handle? Not good to simply sys.exit() from - any random part of code (can leave things in a mess) -- consider context managers for handling compression, so as to keep operations - atomic and/or rollback-able +- sys.exit(1) for errors -- how to handle? Not good to simply sys.exit() from any random part of code (can leave things in a mess) +- consider context managers for handling compression, so as to keep operations atomic and/or rollback-able - add a recursive option on the command-line for use with -d - make -f accept a list of files -- make the current verbose be "normal", and make -verbose print the commandline - app prints as well +- make the current verbose be "normal", and make -verbose print the commandline app prints as well - find a way to specify the version once for everywhere - notification area drag/drop widget -> probably need gtk for gnome - -# Todo else - figure out how to make mac and win versions (someone else :) <- via gui2exe - -# Later versions - animate compressing.gif - allow selection/deletion of rows from table (and subsequently the imagelist) - punypng api? http://www.gracepointafterfive.com/punypng/api - imagemagick/graphicsmagick? - always on top option -- intelligently recompress, i.e. go through the list of files, recompress - each until no more gains are seen (and a sensible number-of-tries limit - isn't exceeded), and flag that file as fully-optimised. Repeat for each - file in the list, until all are done. Saves pointlessly trying to - optimise files. Consider the case of a directory of 100 files, already - optimised once. Recompressing maximally compresses 90. Recompressing - again would currently try to recompress all 100, when only 10 would be - worthy of trying to compress further. +- intelligently recompress, i.e. go through the list of files, recompress each until no more gains are seen (and a sensible number-of-tries limit isn't exceeded), and flag that file as fully-optimised. Repeat for each file in the list, until all are done. Saves pointlessly trying to optimise files. Consider the case of a directory of 100 files, already optimised once. Recompressing maximally compresses 90. Recompressing again would currently try to recompress all 100, when only 10 would be worthy of trying to compress further From 26878908c02e0ebd35222b1cb570bb7b50208325 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Mon, 9 Apr 2018 19:16:47 +0200 Subject: [PATCH 25/62] Ue Python3 shebangs --- src/trimage/ThreadPool/ThreadPool.py | 2 ++ src/trimage/filesize/filesize.py | 2 ++ src/trimage/trimage.py | 2 +- src/trimage/ui.py | 2 ++ trimage | 2 +- 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/trimage/ThreadPool/ThreadPool.py b/src/trimage/ThreadPool/ThreadPool.py index 7f92344..2358602 100644 --- a/src/trimage/ThreadPool/ThreadPool.py +++ b/src/trimage/ThreadPool/ThreadPool.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + ''' ThreadPool Implementation diff --git a/src/trimage/filesize/filesize.py b/src/trimage/filesize/filesize.py index db3e1ba..4246868 100644 --- a/src/trimage/filesize/filesize.py +++ b/src/trimage/filesize/filesize.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + ''' hurry.filesize diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 582120e..e02f2f6 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 import time import sys diff --git a/src/trimage/ui.py b/src/trimage/ui.py index b855a92..983ca6e 100644 --- a/src/trimage/ui.py +++ b/src/trimage/ui.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * diff --git a/trimage b/trimage index 6906ad3..c402a5a 100644 --- a/trimage +++ b/trimage @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # #Copyright (c) 2010 Kilian Valkhof, Paul Chaplin, Tarnay Kálmán # From c38cf92f25b52ab20a17be626a618b54f8faf1a8 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Mon, 9 Apr 2018 19:19:41 +0200 Subject: [PATCH 26/62] Fix imports --- src/trimage/ThreadPool/__init__.py | 2 +- src/trimage/filesize/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/trimage/ThreadPool/__init__.py b/src/trimage/ThreadPool/__init__.py index 727a8b3..86974f0 100644 --- a/src/trimage/ThreadPool/__init__.py +++ b/src/trimage/ThreadPool/__init__.py @@ -1 +1 @@ -from ThreadPool import ThreadPool, ThreadPoolMixIn \ No newline at end of file +from .ThreadPool import ThreadPool, ThreadPoolMixIn diff --git a/src/trimage/filesize/__init__.py b/src/trimage/filesize/__init__.py index 68d9fc2..e926dd0 100644 --- a/src/trimage/filesize/__init__.py +++ b/src/trimage/filesize/__init__.py @@ -1,2 +1,2 @@ -from filesize import size -from filesize import traditional, alternative, verbose, iec, si +from .filesize import size +from .filesize import traditional, alternative, verbose, iec, si From 02a92463e50e1754e0bc035565bda8a7c8c6f885 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 14:49:00 +0100 Subject: [PATCH 27/62] Test geometry setting to avoid a TypeError --- src/trimage/trimage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index e02f2f6..479edd8 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -42,7 +42,8 @@ class StartQT5(QMainWindow): QCoreApplication.setOrganizationDomain("trimage.org") QCoreApplication.setApplicationName("Trimage") self.settings = QSettings() - self.restoreGeometry(self.settings.value("geometry")) + if self.settings.value("geometry"): + self.restoreGeometry(self.settings.value("geometry")) # check if apps are installed if self.checkapps(): From f7531a10951de3e83ceb9afdd25f6d0b7203040e Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 15:16:28 +0100 Subject: [PATCH 28/62] Replace sys.stderr.write by prints --- src/trimage/trimage.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 479edd8..1df64ae 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -213,7 +213,7 @@ class StartQT5(QMainWindow): self.systemtray.trayIcon.setToolTip("Trimage image compressor (" + str(len(self.imagelist)) + " files)") self.setWindowTitle("Trimage image compressor (" + str(len(self.imagelist)) + " files)") else: - print(str(sys.stderr) + "[error] {} not a supported image file and/or not writeable".format(image.fullpath)) + print("[error] {} not a supported image file and/or not writable".format(image.fullpath), file=sys.stderr) """ UI Functions @@ -263,22 +263,22 @@ class StartQT5(QMainWindow): retcode = self.safe_call("jpegoptim" + exe + " --version") if retcode != 0: status = True - sys.stderr.write("[error] please install jpegoptim") + print("[error] please install jpegoptim", file=sys.stderr) retcode = self.safe_call("optipng" + exe + " -v") if retcode != 0: status = True - sys.stderr.write("[error] please install optipng") + print("[error] please install optipng", file=sys.stderr) retcode = self.safe_call("advpng" + exe + " --version") if retcode != 0: status = True - sys.stderr.write("[error] please install advancecomp") + print("[error] please install advancecomp", file=sys.stderr) retcode = self.safe_call("pngcrush" + exe + " -version") if retcode != 0: status = True - sys.stderr.write("[error] please install pngcrush") + print("[error] please install pngcrush", file=sys.stderr) return status def safe_call(self, command): @@ -498,7 +498,7 @@ class Worker(QThread): + ir['oldfilesizestr'] + ", New Size: " + ir['newfilesizestr'] + ", Ratio: " + ir['ratiostr']) else: - print(str(sys.stderr) + "[error] {} could not be compressed".format(image.fullpath)) + print("[error] {} could not be compressed".format(image.fullpath), file=sys.stderr) class Systray(QWidget): From 13f43bfa2f4941d266034904d9b9fc58c4d30e7c Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 15:27:45 +0100 Subject: [PATCH 29/62] Simplify way to check if an image is valid determinetype functions has some defaults --- src/trimage/trimage.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 1df64ae..5dfe7f5 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -16,7 +16,6 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from filesize import * -from imghdr import what as determinetype from queue import Queue from ThreadPool import ThreadPool @@ -398,23 +397,14 @@ class Image: self.reset() self.fullpath = fullpath if path.isfile(self.fullpath) and access(self.fullpath, WRITEABLE): - self.filetype = determinetype(self.fullpath) - if self.filetype in ["jpeg", "png"]: + self.filetype = path.splitext(self.fullpath)[1] + if self.filetype in [".jpeg", ".jpg", ".png"]: oldfile = QFileInfo(self.fullpath) self.shortname = oldfile.fileName() self.oldfilesize = oldfile.size() self.icon = QIcon(self.fullpath) self.valid = True - #def _determinetype(self): - # """ Determine the filetype of the file using imghdr. """ - # filetype = determinetype(self.fullpath) - # if filetype in ["jpeg", "png"]: - # self.filetype = filetype - # else: - # self.filetype = None - # return self.filetype - def reset(self): self.failed = False self.compressed = False From d2c0a7905bc03f91b462a73f7812b46cc2b8c894 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 15:28:09 +0100 Subject: [PATCH 30/62] Add an other check to avoid an other TypeError --- src/trimage/trimage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 5dfe7f5..f9756fa 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -140,7 +140,8 @@ class StartQT5(QMainWindow): def file_dialog(self): """Open a file dialog and send the selected images to compress_file.""" fd = QFileDialog(self) - fd.restoreState(self.settings.value("fdstate")) + if (self.settings.value("fdstate")): + fd.restoreState(self.settings.value("fdstate")) directory = self.settings.value("directory", QVariant("")) fd.setDirectory(directory) From fb1c463a0352fd7deb717088a4bac019d71bace8 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 15:37:35 +0100 Subject: [PATCH 31/62] Fix filetype after last commit --- src/trimage/trimage.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index f9756fa..53a0cc6 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -398,8 +398,10 @@ class Image: self.reset() self.fullpath = fullpath if path.isfile(self.fullpath) and access(self.fullpath, WRITEABLE): - self.filetype = path.splitext(self.fullpath)[1] - if self.filetype in [".jpeg", ".jpg", ".png"]: + self.filetype = path.splitext(self.fullpath)[1][1:] + if self.filetype == "jpg": + self.filetype = "jpeg" + if self.filetype in ["jpeg", "png"]: oldfile = QFileInfo(self.fullpath) self.shortname = oldfile.fileName() self.oldfilesize = oldfile.size() From 95512644b62de5c1e599f545c5ac4d24272d397d Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 15:38:19 +0100 Subject: [PATCH 32/62] Update README --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d01128..f07a0f0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Trimage image compressor A cross-platform tool for optimizing PNG and JPG files. -Trimage is a cross-platform GUI and command-line interface to optimize image files via [optipng](http://optipng.sourceforge.net), [advpng](http://advancemame.sourceforge.net/comp-readme.html) and [jpegoptim](http://www.kokkonen.net/tjko/projects.html), depending on the +Trimage is a cross-platform GUI and command-line interface to optimize image files via [advpng](http://advancemame.sourceforge.net/comp-readme.html), [jpegoptim](http://www.kokkonen.net/tjko/projects.html), [optipng](http://optipng.sourceforge.net) and [pngcrush](https://pmt.sourceforge.io/pngcrush) depending on the filetype (currently, PNG and JPG files are supported). It was inspired by [imageoptim](http://imageoptim.pornel.net). @@ -12,3 +12,10 @@ various input functions to fit your own workflow: a regular file dialog, dragging and dropping and various command line options. Visit [Trimage.org](http://trimage.org) for more information. + +## Prerequisites + +- advpng +- jpegoptim +- optipng +- pngcrush From b2713d54859261f7754be581ce0c3c31746d9fa5 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 16:11:49 +0100 Subject: [PATCH 33/62] Update gitignore --- .gitignore | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0bdf455..0447b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,116 @@ -*.pyc -MANIFEST -dist/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ From 030684fe6053d47a48e50fa84f2a51e86379446e Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 16:21:09 +0100 Subject: [PATCH 34/62] Fix two little errors --- MANIFEST.in | 3 +-- setup.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 7cbcb9f..2ea29c0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include COPYING MANIFEST MANIFEST.in README trimage +include COPYING MANIFEST MANIFEST.in README.md trimage recursive-include desktop *.svg *.desktop recursive-include src/ *.py *.png - diff --git a/setup.py b/setup.py index 51add46..d7e47df 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys win=(sys.platform == "win32") From 460161b6ee20d21031027df48a94366ee238ce73 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 16:30:20 +0100 Subject: [PATCH 35/62] Update generators --- src/trimage/trimage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index 53a0cc6..bbe4017 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -172,8 +172,8 @@ class StartQT5(QMainWindow): delegatorlist = [] for fullpath in images: try: # recompress images already in the list - image = (i.image for i in self.imagelist - if i.image.fullpath == fullpath).__next__() + image = next(i.image for i in self.imagelist + if i.image.fullpath == fullpath) if image.compressed: image.reset() image.recompression = True From 1e602b2a77ac8146f99d4319755fcb63463f60c4 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Wed, 20 Feb 2019 17:10:03 +0100 Subject: [PATCH 36/62] Remove the need to use the hurry.filesize package --- setup.py | 3 +- src/trimage/filesize/PKG-INFO | 71 ------------------- src/trimage/filesize/README.txt | 50 ------------- src/trimage/filesize/__init__.py | 2 - src/trimage/filesize/filesize.py | 117 ------------------------------- src/trimage/tools.py | 9 +++ src/trimage/trimage.py | 6 +- 7 files changed, 13 insertions(+), 245 deletions(-) delete mode 100644 src/trimage/filesize/PKG-INFO delete mode 100644 src/trimage/filesize/README.txt delete mode 100644 src/trimage/filesize/__init__.py delete mode 100644 src/trimage/filesize/filesize.py create mode 100644 src/trimage/tools.py diff --git a/setup.py b/setup.py index d7e47df..c916921 100644 --- a/setup.py +++ b/setup.py @@ -17,8 +17,7 @@ setup(name = "trimage", license = "MIT license", package_dir = {'trimage' : 'src/trimage'}, packages = ["trimage", - "trimage.filesize", - "trimage.ThreadPool",], + "trimage.ThreadPool"], package_data = {"trimage" : ["pixmaps/*.*"] }, data_files=[('share/icons/hicolor/scalable/apps', ['desktop/trimage.svg']), ('share/applications', ['desktop/trimage.desktop']), diff --git a/src/trimage/filesize/PKG-INFO b/src/trimage/filesize/PKG-INFO deleted file mode 100644 index fe3aadb..0000000 --- a/src/trimage/filesize/PKG-INFO +++ /dev/null @@ -1,71 +0,0 @@ -Metadata-Version: 1.0 -Name: hurry.filesize -Version: 0.9 -Summary: A simple Python library for human readable file sizes (or anything sized in bytes). -Home-page: UNKNOWN -Author: Martijn Faassen, Startifact -Author-email: faassen@startifact.com -License: ZPL 2.1 -Description: hurry.filesize - ============== - - hurry.filesize a simple Python library that can take a number of bytes and - returns a human-readable string with the size in it, in kilobytes (K), - megabytes (M), etc. - - The default system it uses is "traditional", where multipliers of 1024 - increase the unit size:: - - >>> from hurry.filesize import size - >>> size(1024) - '1K' - - An alternative, slightly more verbose system:: - - >>> from hurry.filesize import alternative - >>> size(1, system=alternative) - '1 byte' - >>> size(10, system=alternative) - '10 bytes' - >>> size(1024, system=alternative) - '1 KB' - - A verbose system:: - - >>> from hurry.filesize import verbose - >>> size(10, system=verbose) - '10 bytes' - >>> size(1024, system=verbose) - '1 kilobyte' - >>> size(2000, system=verbose) - '1 kilobyte' - >>> size(3000, system=verbose) - '2 kilobytes' - >>> size(1024 * 1024, system=verbose) - '1 megabyte' - >>> size(1024 * 1024 * 3, system=verbose) - '3 megabytes' - - You can also use the SI system, where multipliers of 1000 increase the unit - size:: - - >>> from hurry.filesize import si - >>> size(1000, system=si) - '1K' - - - Changes - ======= - - 0.9 (2009-03-11) - ---------------- - - * Initial public release. - - Download - ======== - -Keywords: file size bytes -Platform: UNKNOWN -Classifier: Programming Language :: Python -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/trimage/filesize/README.txt b/src/trimage/filesize/README.txt deleted file mode 100644 index 6a0fad6..0000000 --- a/src/trimage/filesize/README.txt +++ /dev/null @@ -1,50 +0,0 @@ -hurry.filesize -============== - -hurry.filesize a simple Python library that can take a number of bytes and -returns a human-readable string with the size in it, in kilobytes (K), -megabytes (M), etc. - -The default system it uses is "traditional", where multipliers of 1024 -increase the unit size:: - - >>> from hurry.filesize import size - >>> size(1024) - '1K' - -An alternative, slightly more verbose system:: - - >>> from hurry.filesize import alternative - >>> size(1, system=alternative) - '1 byte' - >>> size(10, system=alternative) - '10 bytes' - >>> size(1024, system=alternative) - '1 KB' - -A verbose system:: - - >>> from hurry.filesize import verbose - >>> size(10, system=verbose) - '10 bytes' - >>> size(1024, system=verbose) - '1 kilobyte' - >>> size(2000, system=verbose) - '1 kilobyte' - >>> size(3000, system=verbose) - '2 kilobytes' - >>> size(1024 * 1024, system=verbose) - '1 megabyte' - >>> size(1024 * 1024 * 3, system=verbose) - '3 megabytes' - -You can also use the SI system, where multipliers of 1000 increase the unit -size:: - - >>> from hurry.filesize import si - >>> size(1000, system=si) - '1K' - - -http://pypi.python.org/pypi/hurry.filesize - diff --git a/src/trimage/filesize/__init__.py b/src/trimage/filesize/__init__.py deleted file mode 100644 index e926dd0..0000000 --- a/src/trimage/filesize/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .filesize import size -from .filesize import traditional, alternative, verbose, iec, si diff --git a/src/trimage/filesize/filesize.py b/src/trimage/filesize/filesize.py deleted file mode 100644 index 4246868..0000000 --- a/src/trimage/filesize/filesize.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 - -''' -hurry.filesize - -@author: Martijn Faassen, Startifact -@license: ZPL 2.1 -''' - -traditional = [ - (1024 ** 5, 'P'), - (1024 ** 4, 'T'), - (1024 ** 3, 'G'), - (1024 ** 2, 'M'), - (1024 ** 1, 'K'), - (1024 ** 0, 'B'), - ] - -alternative = [ - (1024 ** 5, ' PB'), - (1024 ** 4, ' TB'), - (1024 ** 3, ' GB'), - (1024 ** 2, ' MB'), - (1024 ** 1, ' KB'), - (1024 ** 0, (' byte', ' bytes')), - ] - -verbose = [ - (1024 ** 5, (' petabyte', ' petabytes')), - (1024 ** 4, (' terabyte', ' terabytes')), - (1024 ** 3, (' gigabyte', ' gigabytes')), - (1024 ** 2, (' megabyte', ' megabytes')), - (1024 ** 1, (' kilobyte', ' kilobytes')), - (1024 ** 0, (' byte', ' bytes')), - ] - -iec = [ - (1024 ** 5, 'Pi'), - (1024 ** 4, 'Ti'), - (1024 ** 3, 'Gi'), - (1024 ** 2, 'Mi'), - (1024 ** 1, 'Ki'), - (1024 ** 0, ''), - ] - -si = [ - (1000 ** 5, 'P'), - (1000 ** 4, 'T'), - (1000 ** 3, 'G'), - (1000 ** 2, 'M'), - (1000 ** 1, 'K'), - (1000 ** 0, 'B'), - ] - - - -def size(bytes, system=traditional): - """Human-readable file size. - - Using the traditional system, where a factor of 1024 is used:: - - >>> size(10) - '10B' - >>> size(100) - '100B' - >>> size(1000) - '1000B' - >>> size(2000) - '1K' - >>> size(10000) - '9K' - >>> size(20000) - '19K' - >>> size(100000) - '97K' - >>> size(200000) - '195K' - >>> size(1000000) - '976K' - >>> size(2000000) - '1M' - - Using the SI system, with a factor 1000:: - - >>> size(10, system=si) - '10B' - >>> size(100, system=si) - '100B' - >>> size(1000, system=si) - '1K' - >>> size(2000, system=si) - '2K' - >>> size(10000, system=si) - '10K' - >>> size(20000, system=si) - '20K' - >>> size(100000, system=si) - '100K' - >>> size(200000, system=si) - '200K' - >>> size(1000000, system=si) - '1M' - >>> size(2000000, system=si) - '2M' - - """ - for factor, suffix in system: - if bytes >= factor: - break - amount = int(bytes/factor) - if isinstance(suffix, tuple): - singular, multiple = suffix - if amount == 1: - suffix = singular - else: - suffix = multiple - return str(amount) + suffix diff --git a/src/trimage/tools.py b/src/trimage/tools.py new file mode 100644 index 0000000..398df9e --- /dev/null +++ b/src/trimage/tools.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + + +def human_readable_size(num, suffix='B'): + for unit in ['','K','M','G','T','P','E','Z']: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, 'Y', suffix) diff --git a/src/trimage/trimage.py b/src/trimage/trimage.py index bbe4017..b550a4e 100644 --- a/src/trimage/trimage.py +++ b/src/trimage/trimage.py @@ -15,8 +15,8 @@ from optparse import OptionParser from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * -from filesize import * +from tools import human_readable_size from queue import Queue from ThreadPool import ThreadPool from multiprocessing import cpu_count @@ -356,9 +356,9 @@ class ImageRow: self.image = image d = { 'shortname': lambda i: self.statusStr() % i.shortname, - 'oldfilesizestr': lambda i: size(i.oldfilesize, system=alternative) + 'oldfilesizestr': lambda i: human_readable_size(i.oldfilesize) if i.compressed else "", - 'newfilesizestr': lambda i: size(i.newfilesize, system=alternative) + 'newfilesizestr': lambda i: human_readable_size(i.newfilesize) if i.compressed else "", 'ratiostr': lambda i: "%.1f%%" % (100 - (float(i.newfilesize) / i.oldfilesize * 100)) From 006256e08bea832a937288b29aa506b65275e48c Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Thu, 21 Feb 2019 15:26:20 +0100 Subject: [PATCH 37/62] Update README --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f07a0f0..288cbf5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Trimage image compressor + A cross-platform tool for optimizing PNG and JPG files. Trimage is a cross-platform GUI and command-line interface to optimize image files via [advpng](http://advancemame.sourceforge.net/comp-readme.html), [jpegoptim](http://www.kokkonen.net/tjko/projects.html), [optipng](http://optipng.sourceforge.net) and [pngcrush](https://pmt.sourceforge.io/pngcrush) depending on the @@ -11,11 +12,23 @@ compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: a regular file dialog, dragging and dropping and various command line options. -Visit [Trimage.org](http://trimage.org) for more information. +## Installation instructions -## Prerequisites +Visit [Trimage.org](http://trimage.org) to install Trimage as a package. +## Building instructions + +### Prerequisites + +- PyQt5 - advpng - jpegoptim - optipng - pngcrush + +### Build from source + +Build and install by running: + + python setup.py build + sudo python setup.py install From 4e702d3f3a37960d0a1ef1a2f9da6d007a31ec45 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Thu, 21 Feb 2019 15:34:59 +0100 Subject: [PATCH 38/62] Simplify the structure --- MANIFEST.in | 4 ++-- trimage => bin/trimage | 0 setup.py | 7 +++---- {src/trimage => trimage}/ThreadPool/ThreadPool.py | 0 {src/trimage => trimage}/ThreadPool/__init__.py | 0 {src/trimage => trimage}/__init__.py | 0 {src/trimage => trimage}/pixmaps/compressing.gif | Bin {src/trimage => trimage}/pixmaps/list-add.png | Bin {src/trimage => trimage}/pixmaps/trimage-icon.png | Bin {src/trimage => trimage}/pixmaps/view-refresh.png | Bin {src/trimage => trimage}/tools.py | 0 {src/trimage => trimage}/trimage.py | 0 {src/trimage => trimage}/ui.py | 0 13 files changed, 5 insertions(+), 6 deletions(-) rename trimage => bin/trimage (100%) rename {src/trimage => trimage}/ThreadPool/ThreadPool.py (100%) rename {src/trimage => trimage}/ThreadPool/__init__.py (100%) rename {src/trimage => trimage}/__init__.py (100%) rename {src/trimage => trimage}/pixmaps/compressing.gif (100%) rename {src/trimage => trimage}/pixmaps/list-add.png (100%) rename {src/trimage => trimage}/pixmaps/trimage-icon.png (100%) rename {src/trimage => trimage}/pixmaps/view-refresh.png (100%) rename {src/trimage => trimage}/tools.py (100%) rename {src/trimage => trimage}/trimage.py (100%) rename {src/trimage => trimage}/ui.py (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 2ea29c0..d2dd0c9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -include COPYING MANIFEST MANIFEST.in README.md trimage +include COPYING MANIFEST MANIFEST.in README.md bin/trimage recursive-include desktop *.svg *.desktop -recursive-include src/ *.py *.png +recursive-include trimage/ *.py *.png diff --git a/trimage b/bin/trimage similarity index 100% rename from trimage rename to bin/trimage diff --git a/setup.py b/setup.py index c916921..abb3796 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import sys win=(sys.platform == "win32") if win: import py2exe - sys.path.append("src/trimage") + sys.path.append("trimage") from distutils.core import setup @@ -15,19 +15,18 @@ setup(name = "trimage", author_email = "help@trimage.org", url = "http://trimage.org", license = "MIT license", - package_dir = {'trimage' : 'src/trimage'}, packages = ["trimage", "trimage.ThreadPool"], package_data = {"trimage" : ["pixmaps/*.*"] }, data_files=[('share/icons/hicolor/scalable/apps', ['desktop/trimage.svg']), ('share/applications', ['desktop/trimage.desktop']), ('share/man/man1', ['doc/trimage.1'])], - scripts = ["trimage"], + scripts = ["bin/trimage"], long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via optipng, advpng and jpegoptim, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""", requires = ["PyQt5"], #for py2exe - windows=[r'src\trimage\trimage.py'], + windows=[r'trimage\trimage.py'], zipfile=None, options={"py2exe":{ "optimize":2, diff --git a/src/trimage/ThreadPool/ThreadPool.py b/trimage/ThreadPool/ThreadPool.py similarity index 100% rename from src/trimage/ThreadPool/ThreadPool.py rename to trimage/ThreadPool/ThreadPool.py diff --git a/src/trimage/ThreadPool/__init__.py b/trimage/ThreadPool/__init__.py similarity index 100% rename from src/trimage/ThreadPool/__init__.py rename to trimage/ThreadPool/__init__.py diff --git a/src/trimage/__init__.py b/trimage/__init__.py similarity index 100% rename from src/trimage/__init__.py rename to trimage/__init__.py diff --git a/src/trimage/pixmaps/compressing.gif b/trimage/pixmaps/compressing.gif similarity index 100% rename from src/trimage/pixmaps/compressing.gif rename to trimage/pixmaps/compressing.gif diff --git a/src/trimage/pixmaps/list-add.png b/trimage/pixmaps/list-add.png similarity index 100% rename from src/trimage/pixmaps/list-add.png rename to trimage/pixmaps/list-add.png diff --git a/src/trimage/pixmaps/trimage-icon.png b/trimage/pixmaps/trimage-icon.png similarity index 100% rename from src/trimage/pixmaps/trimage-icon.png rename to trimage/pixmaps/trimage-icon.png diff --git a/src/trimage/pixmaps/view-refresh.png b/trimage/pixmaps/view-refresh.png similarity index 100% rename from src/trimage/pixmaps/view-refresh.png rename to trimage/pixmaps/view-refresh.png diff --git a/src/trimage/tools.py b/trimage/tools.py similarity index 100% rename from src/trimage/tools.py rename to trimage/tools.py diff --git a/src/trimage/trimage.py b/trimage/trimage.py similarity index 100% rename from src/trimage/trimage.py rename to trimage/trimage.py diff --git a/src/trimage/ui.py b/trimage/ui.py similarity index 100% rename from src/trimage/ui.py rename to trimage/ui.py From fa94b546fbfbfa9fa166c1b822c6869365fadaa8 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Thu, 21 Feb 2019 16:25:15 +0100 Subject: [PATCH 39/62] Reorder imports (PEP-8) --- trimage/trimage.py | 14 +++++--------- trimage/ui.py | 4 +++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index b550a4e..39b0531 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -3,26 +3,22 @@ import time import sys import errno -from os import listdir -from os import path -from os import remove -from os import access -from os import W_OK as WRITEABLE +from os import listdir, path, remove, access, W_OK from shutil import copy from subprocess import call, PIPE from optparse import OptionParser +from multiprocessing import cpu_count +from queue import Queue from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from tools import human_readable_size -from queue import Queue from ThreadPool import ThreadPool -from multiprocessing import cpu_count - from ui import Ui_trimage + VERSION = "1.0.5" @@ -397,7 +393,7 @@ class Image: self.valid = False self.reset() self.fullpath = fullpath - if path.isfile(self.fullpath) and access(self.fullpath, WRITEABLE): + if path.isfile(self.fullpath) and access(self.fullpath, W_OK): self.filetype = path.splitext(self.fullpath)[1][1:] if self.filetype == "jpg": self.filetype = "jpeg" diff --git a/trimage/ui.py b/trimage/ui.py index 983ca6e..578c9a1 100644 --- a/trimage/ui.py +++ b/trimage/ui.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +from os import path + from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * -from os import path + class TrimageTableView(QTableView): From 06aad62cb6b0e8b27ba1c745c52e58b43f8a9304 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Thu, 21 Feb 2019 16:34:07 +0100 Subject: [PATCH 40/62] Simpler check of dependencies --- trimage/trimage.py | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 39b0531..99ce862 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -40,8 +40,8 @@ class StartQT5(QMainWindow): if self.settings.value("geometry"): self.restoreGeometry(self.settings.value("geometry")) - # check if apps are installed - if self.checkapps(): + # check if dependencies are installed + if not self.check_dependencies(): quit() #add quit shortcut @@ -252,29 +252,23 @@ class StartQT5(QMainWindow): if QSystemTrayIcon.isSystemTrayAvailable() and not self.cli: self.systemtray.recompress.setEnabled(True) - def checkapps(self): + def check_dependencies(self): """Check if the required command line apps exist.""" exe = ".exe" if (sys.platform == "win32") else "" - status = False - retcode = self.safe_call("jpegoptim" + exe + " --version") - if retcode != 0: - status = True - print("[error] please install jpegoptim", file=sys.stderr) + status = True + dependencies = { + "jpegoptim": "--version", + "optipng": "-v", + "advpng": "--version", + "pngcrush": "-version" + } - retcode = self.safe_call("optipng" + exe + " -v") - if retcode != 0: - status = True - print("[error] please install optipng", file=sys.stderr) + for elt in dependencies: + retcode = self.safe_call(elt + exe + " " + dependencies[elt]) + if retcode != 0: + status = False + print("[error] please install {}".format(elt), file=sys.stderr) - retcode = self.safe_call("advpng" + exe + " --version") - if retcode != 0: - status = True - print("[error] please install advancecomp", file=sys.stderr) - - retcode = self.safe_call("pngcrush" + exe + " -version") - if retcode != 0: - status = True - print("[error] please install pngcrush", file=sys.stderr) return status def safe_call(self, command): From 915a360a09704a014888983625e15cbdc4cbfd7a Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Thu, 21 Feb 2019 16:37:03 +0100 Subject: [PATCH 41/62] Uniformize comments and docstrings syntax --- trimage/trimage.py | 22 +++++++++++----------- trimage/ui.py | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 99ce862..99dc858 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -44,7 +44,7 @@ class StartQT5(QMainWindow): if not self.check_dependencies(): quit() - #add quit shortcut + # add quit shortcut if hasattr(QKeySequence, "Quit"): self.quit_shortcut = QShortcut(QKeySequence(QKeySequence.Quit), self) @@ -186,7 +186,7 @@ class StartQT5(QMainWindow): def walk(self, dir, delegatorlist): """ - Walks a directory, and executes a callback on each file + Walks a directory, and executes a callback on each file. """ dir = path.abspath(dir) for file in [file for file in listdir(dir) if not file in [".","..",".svn",".git",".hg",".bzr",".cvs"]]: @@ -199,7 +199,7 @@ class StartQT5(QMainWindow): def add_image(self, fullpath, delegatorlist): """ - Adds an image file to the delegator list and update the tray and the title of the window + Adds an image file to the delegator list and update the tray and the title of the window. """ image = Image(fullpath) if image.valid: @@ -272,7 +272,7 @@ class StartQT5(QMainWindow): return status def safe_call(self, command): - """ cross-platform command-line check """ + """Cross-platform command-line check.""" while True: try: return call(command, shell=True, stdout=PIPE) @@ -342,7 +342,7 @@ class TriTableModel(QAbstractTableModel): class ImageRow: def __init__(self, image, waitingIcon=None): - """ Build the information visible in the table image row. """ + """Build the information visible in the table image row.""" self.image = image d = { 'shortname': lambda i: self.statusStr() % i.shortname, @@ -364,7 +364,7 @@ class ImageRow: self.d = d def statusStr(self): - """ Set the status message. """ + """Set the status message.""" if self.image.failed: return "ERROR: %s" if self.image.compressing: @@ -383,7 +383,7 @@ class ImageRow: class Image: def __init__(self, fullpath): - """ gather image information. """ + """Gather image information.""" self.valid = False self.reset() self.fullpath = fullpath @@ -405,7 +405,7 @@ class Image: self.recompression = False def compress(self): - """ Compress the image and return it to the thread. """ + """Compress the image and return it to the thread.""" if not self.valid: raise "Tried to compress invalid image (unsupported format or not \ file)" @@ -416,7 +416,7 @@ class Image: "jpeg": "jpegoptim" + exe + " -f --strip-all '%(file)s'", "png": "optipng" + exe + " -force -o7 '%(file)s'&&advpng" + exe + " -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'" } - # Create a backup file + # create a backup file copy(self.fullpath, self.fullpath + '~') try: retcode = call(runString[self.filetype] % {"file": self.fullpath}, @@ -427,12 +427,12 @@ class Image: self.newfilesize = QFile(self.fullpath).size() self.compressed = True - # Checks the new file and copy the backup + # checks the new file and copy the backup if self.newfilesize >= self.oldfilesize: copy(self.fullpath + '~', self.fullpath) self.newfilesize = self.oldfilesize - # Removes the backup file + # removes the backup file remove(self.fullpath + '~') else: self.failed = True diff --git a/trimage/ui.py b/trimage/ui.py index 578c9a1..6544c58 100644 --- a/trimage/ui.py +++ b/trimage/ui.py @@ -36,12 +36,12 @@ class TrimageTableView(QTableView): class Ui_trimage(): def get_image(self, image): - """ Get the correct link to the images used in the UI """ + """Get the correct link to the images used in the UI.""" imagelink = path.join(path.dirname(path.dirname(path.realpath(__file__))), "trimage/" + image) return imagelink def setupUi(self, trimage): - """ Setup the entire UI """ + """Setup the entire UI.""" trimage.setObjectName("trimage") trimage.resize(600, 170) @@ -149,7 +149,7 @@ class Ui_trimage(): QMetaObject.connectSlotsByName(trimage) def retranslateUi(self, trimage): - """ Fill in the texts for all UI elements """ + """Fill in the texts for all UI elements.""" trimage.setWindowTitle(QApplication.translate("trimage", "Trimage image compressor", None)) self.addfiles.setToolTip(QApplication.translate("trimage", From b829b6ac5fad74a1397ed2a1d2b032a5599b9b05 Mon Sep 17 00:00:00 2001 From: Huluti Date: Fri, 22 Feb 2019 21:23:37 +0100 Subject: [PATCH 42/62] Minor cosmetics --- trimage/trimage.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 99dc858..e17f58c 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -14,15 +14,15 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * -from tools import human_readable_size from ThreadPool import ThreadPool +from tools import human_readable_size from ui import Ui_trimage VERSION = "1.0.5" -class StartQT5(QMainWindow): +class StartQt(QMainWindow): def __init__(self, parent=None): QWidget.__init__(self, parent) @@ -53,7 +53,6 @@ class StartQT5(QMainWindow): # disable recompress self.ui.recompress.setEnabled(False) - #self.ui.recompress.hide() # make a worker thread self.thread = Worker() @@ -75,11 +74,11 @@ class StartQT5(QMainWindow): self.systemtray = Systray(self) def commandline_options(self): - self.cli = False """Set up the command line options.""" + self.cli = False parser = OptionParser(version="%prog " + VERSION, description="GUI front-end to compress png and jpg images via " - "optipng, advpng and jpegoptim") + "advpng, jpegoptim, optipng and pngcrush") parser.set_defaults(verbose=True) parser.add_option("-v", "--verbose", action="store_true", @@ -141,14 +140,13 @@ class StartQT5(QMainWindow): directory = self.settings.value("directory", QVariant("")) fd.setDirectory(directory) - images = fd.getOpenFileNames(self, + images, _ = fd.getOpenFileNames(self, "Select one or more image files to compress", directory, # this is a fix for file dialog differentiating between cases "Image files (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG)") self.settings.setValue("fdstate", QVariant(fd.saveState())) - images = images[0] if images: self.settings.setValue("directory", QVariant(path.dirname(images[0]))) self.delegator([fullpath for fullpath in images]) @@ -534,7 +532,7 @@ class Systray(QWidget): if __name__ == "__main__": app = QApplication(sys.argv) - myapp = StartQT5() + myapp = StartQt() if myapp.showapp: myapp.show() From c040b63595cf6263dec4d8950ecb1cd8f531d6ba Mon Sep 17 00:00:00 2001 From: Huluti Date: Sat, 23 Feb 2019 20:48:49 +0100 Subject: [PATCH 43/62] Move two functions in the tools.py file --- trimage/tools.py | 43 ++++++++++++++++++++++++++++++++++++++++--- trimage/trimage.py | 41 +++-------------------------------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/trimage/tools.py b/trimage/tools.py index 398df9e..f787c8c 100644 --- a/trimage/tools.py +++ b/trimage/tools.py @@ -1,9 +1,46 @@ #!/usr/bin/env python3 +import sys +import errno +from subprocess import call, PIPE -def human_readable_size(num, suffix='B'): - for unit in ['','K','M','G','T','P','E','Z']: + +def check_dependencies(): + """Check if the required command line apps exist.""" + exe = ".exe" if (sys.platform == "win32") else "" + status = True + dependencies = { + "jpegoptim": "--version", + "optipng": "-v", + "advpng": "--version", + "pngcrush": "-version" + } + + for elt in dependencies: + retcode = safe_call(elt + exe + " " + dependencies[elt]) + if retcode != 0: + status = False + print("[error] please install {}".format(elt), file=sys.stderr) + + return status + + +def safe_call(command): + """Cross-platform command-line check.""" + while True: + try: + return call(command, shell=True, stdout=PIPE) + except OSError as e: + if e.errno == errno.EINTR: + continue + else: + raise + + +def human_readable_size(num, suffix="B"): + """Bytes to a readable size format""" + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 - return "%.1f%s%s" % (num, 'Y', suffix) + return "%.1f%s%s" % (num, "Y", suffix) \ No newline at end of file diff --git a/trimage/trimage.py b/trimage/trimage.py index e17f58c..1f9da2b 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -2,10 +2,9 @@ import time import sys -import errno from os import listdir, path, remove, access, W_OK from shutil import copy -from subprocess import call, PIPE + from optparse import OptionParser from multiprocessing import cpu_count from queue import Queue @@ -15,8 +14,8 @@ from PyQt5.QtGui import * from PyQt5.QtWidgets import * from ThreadPool import ThreadPool -from tools import human_readable_size from ui import Ui_trimage +from tools import * VERSION = "1.0.5" @@ -41,7 +40,7 @@ class StartQt(QMainWindow): self.restoreGeometry(self.settings.value("geometry")) # check if dependencies are installed - if not self.check_dependencies(): + if not check_dependencies(): quit() # add quit shortcut @@ -240,46 +239,12 @@ class StartQt(QMainWindow): # enable recompress button self.enable_recompress() - """ - Helper functions - """ - def enable_recompress(self): """Enable the recompress button.""" self.ui.recompress.setEnabled(True) if QSystemTrayIcon.isSystemTrayAvailable() and not self.cli: self.systemtray.recompress.setEnabled(True) - def check_dependencies(self): - """Check if the required command line apps exist.""" - exe = ".exe" if (sys.platform == "win32") else "" - status = True - dependencies = { - "jpegoptim": "--version", - "optipng": "-v", - "advpng": "--version", - "pngcrush": "-version" - } - - for elt in dependencies: - retcode = self.safe_call(elt + exe + " " + dependencies[elt]) - if retcode != 0: - status = False - print("[error] please install {}".format(elt), file=sys.stderr) - - return status - - def safe_call(self, command): - """Cross-platform command-line check.""" - while True: - try: - return call(command, shell=True, stdout=PIPE) - except OSError as e: - if e.errno == errno.EINTR: - continue - else: - raise - def hide_main_window(self): if self.isVisible(): self.hide() From 9ea961e87cf01a353109736755e64563b4990f2d Mon Sep 17 00:00:00 2001 From: Huluti Date: Sat, 23 Feb 2019 20:54:27 +0100 Subject: [PATCH 44/62] Remove some extra spaces --- trimage/trimage.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 1f9da2b..a07fa68 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -22,7 +22,6 @@ VERSION = "1.0.5" class StartQt(QMainWindow): - def __init__(self, parent=None): QWidget.__init__(self, parent) self.ui = Ui_trimage() @@ -261,7 +260,6 @@ class StartQt(QMainWindow): class TriTableModel(QAbstractTableModel): - def __init__(self, parent, imagelist, header, *args): """ @param parent Qt parent object. @@ -303,7 +301,6 @@ class TriTableModel(QAbstractTableModel): class ImageRow: - def __init__(self, image, waitingIcon=None): """Build the information visible in the table image row.""" self.image = image @@ -344,7 +341,6 @@ class ImageRow: class Image: - def __init__(self, fullpath): """Gather image information.""" self.valid = False @@ -405,7 +401,6 @@ class Image: class Worker(QThread): - update_ui_signal = pyqtSignal() def __init__(self, parent=None): @@ -448,7 +443,6 @@ class Worker(QThread): class Systray(QWidget): - def __init__(self, parent): QWidget.__init__(self) self.parent = parent From 5553e7fabe494035c175f2936fbe0ba2fddb053d Mon Sep 17 00:00:00 2001 From: Huluti Date: Sat, 23 Feb 2019 20:58:02 +0100 Subject: [PATCH 45/62] Fix a bug introcuded in 13f43bf: allow extensions even in upper case --- trimage/trimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index a07fa68..a32a1d5 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -347,7 +347,7 @@ class Image: self.reset() self.fullpath = fullpath if path.isfile(self.fullpath) and access(self.fullpath, W_OK): - self.filetype = path.splitext(self.fullpath)[1][1:] + self.filetype = path.splitext(self.fullpath)[1][1:].lower() if self.filetype == "jpg": self.filetype = "jpeg" if self.filetype in ["jpeg", "png"]: From 27b22807bcbac938f1154683470d5759e1627c0a Mon Sep 17 00:00:00 2001 From: Huluti Date: Mon, 4 Mar 2019 21:46:25 +0100 Subject: [PATCH 46/62] Fix #41 --- trimage/ThreadPool/ThreadPool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trimage/ThreadPool/ThreadPool.py b/trimage/ThreadPool/ThreadPool.py index 2358602..9f9e051 100644 --- a/trimage/ThreadPool/ThreadPool.py +++ b/trimage/ThreadPool/ThreadPool.py @@ -90,7 +90,7 @@ class ThreadPool: return_value = self.callable(*self.arguments) #IGNORE:W0142 except Exception as excep: #IGNORE:W0703 logger = logging.getLogger("threadpool.worker") - logger.warning("A job in the ThreadPool raised an exception: " + excep) + logger.warning("A job in the ThreadPool raised an exception: ", excep) #else do nothing cause we don't know what to do... return From f5ff57ff48baa0f746bf98476d965fb608a63316 Mon Sep 17 00:00:00 2001 From: Huluti Date: Thu, 7 Mar 2019 15:59:05 +0100 Subject: [PATCH 47/62] Just one return line fix --- trimage/trimage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index a32a1d5..2679924 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -4,7 +4,6 @@ import time import sys from os import listdir, path, remove, access, W_OK from shutil import copy - from optparse import OptionParser from multiprocessing import cpu_count from queue import Queue From d6118b520b2f9fd3ba23b33015cd23b6474064ed Mon Sep 17 00:00:00 2001 From: Huluti Date: Thu, 7 Mar 2019 16:09:21 +0100 Subject: [PATCH 48/62] Remove Windows options from setup.py --- setup.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index abb3796..cdf0490 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,8 @@ #!/usr/bin/env python3 -import sys -win=(sys.platform == "win32") -if win: - import py2exe - sys.path.append("trimage") - from distutils.core import setup + setup(name = "trimage", version = "1.0.5", description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files", @@ -23,17 +18,5 @@ setup(name = "trimage", ('share/man/man1', ['doc/trimage.1'])], scripts = ["bin/trimage"], long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via optipng, advpng and jpegoptim, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""", - requires = ["PyQt5"], - - #for py2exe - windows=[r'trimage\trimage.py'], - zipfile=None, - options={"py2exe":{ - "optimize":2, - "compressed":1, - "bundle_files":1, - "includes":["sip",], - "excludes":['email'], - }, - }, + requires = ["PyQt5"] ) From a3f417c43eaf97b6e914bebd5162e86838609bcc Mon Sep 17 00:00:00 2001 From: Huluti Date: Thu, 7 Mar 2019 16:10:19 +0100 Subject: [PATCH 49/62] setup.py: add a missing dependency in the long description --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cdf0490..dc8cfe3 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,6 @@ setup(name = "trimage", ('share/applications', ['desktop/trimage.desktop']), ('share/man/man1', ['doc/trimage.1'])], scripts = ["bin/trimage"], - long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via optipng, advpng and jpegoptim, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""", + long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via advpng, jpegoptim, optipng and pngcrush, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""", requires = ["PyQt5"] ) From 5f5757f57576c71f0cdfad28ad7fe8415daa6c6b Mon Sep 17 00:00:00 2001 From: Huluti Date: Thu, 7 Mar 2019 16:45:55 +0100 Subject: [PATCH 50/62] Fix #28 --- trimage/trimage.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 2679924..a0f7092 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -4,6 +4,7 @@ import time import sys from os import listdir, path, remove, access, W_OK from shutil import copy + from optparse import OptionParser from multiprocessing import cpu_count from queue import Queue @@ -304,7 +305,7 @@ class ImageRow: """Build the information visible in the table image row.""" self.image = image d = { - 'shortname': lambda i: self.statusStr() % i.shortname, + 'filename_w_ext': lambda i: self.statusStr() % i.filename_w_ext, 'oldfilesizestr': lambda i: human_readable_size(i.oldfilesize) if i.compressed else "", 'newfilesizestr': lambda i: human_readable_size(i.newfilesize) @@ -315,7 +316,7 @@ class ImageRow: 'icon': lambda i: i.icon if i.compressed else waitingIcon, 'fullpath': lambda i: i.fullpath, #only used by cli } - names = ['shortname', 'oldfilesizestr', 'newfilesizestr', + names = ['filename_w_ext', 'oldfilesizestr', 'newfilesizestr', 'ratiostr', 'icon'] for i, n in enumerate(names): d[i] = d[n] @@ -345,13 +346,14 @@ class Image: self.valid = False self.reset() self.fullpath = fullpath + self.filename_w_ext = path.basename(self.fullpath) + self.filename, self.filetype = path.splitext(self.filename_w_ext) if path.isfile(self.fullpath) and access(self.fullpath, W_OK): - self.filetype = path.splitext(self.fullpath)[1][1:].lower() + self.filetype = self.filetype[1:].lower() if self.filetype == "jpg": self.filetype = "jpeg" if self.filetype in ["jpeg", "png"]: oldfile = QFileInfo(self.fullpath) - self.shortname = oldfile.fileName() self.oldfilesize = oldfile.size() self.icon = QIcon(self.fullpath) self.valid = True @@ -375,7 +377,8 @@ class Image: "png": "optipng" + exe + " -force -o7 '%(file)s'&&advpng" + exe + " -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'" } # create a backup file - copy(self.fullpath, self.fullpath + '~') + backupfullpath = '/tmp/' + self.filename_w_ext + copy(self.fullpath, backupfullpath) try: retcode = call(runString[self.filetype] % {"file": self.fullpath}, shell=True, stdout=PIPE) @@ -387,11 +390,11 @@ class Image: # checks the new file and copy the backup if self.newfilesize >= self.oldfilesize: - copy(self.fullpath + '~', self.fullpath) + copy(backupfullpath, self.fullpath) self.newfilesize = self.oldfilesize # removes the backup file - remove(self.fullpath + '~') + remove(backupfullpath) else: self.failed = True self.compressing = False From e3b514697745e281cde507828879d2f090e92e39 Mon Sep 17 00:00:00 2001 From: kilian Date: Tue, 12 Mar 2019 11:49:45 +0100 Subject: [PATCH 51/62] update website --- website/index.html | 287 ++++++++++++++++++++++--------------------- website/mac.png | Bin 891 -> 0 bytes website/mandriva.png | Bin 1957 -> 0 bytes website/opensuse.png | Bin 1297 -> 0 bytes website/windows.png | Bin 1705 -> 0 bytes 5 files changed, 144 insertions(+), 143 deletions(-) delete mode 100644 website/mac.png delete mode 100644 website/mandriva.png delete mode 100644 website/opensuse.png delete mode 100644 website/windows.png diff --git a/website/index.html b/website/index.html index beada69..fd290c5 100644 --- a/website/index.html +++ b/website/index.html @@ -1,13 +1,18 @@ - + Trimage (lossless) image compressor - + - -
-

Trimage image compressor – 1.0.5

- A cross-platform tool for losslessly optimizing PNG and JPG files. -

Trimage is a cross-platform GUI and command-line interface to optimize image - files for websites, using optipng, pngcrush, - advpng and - jpegoptim, depending on the filetype - (currently, PNG and JPG files are supported). It was inspired by - imageoptim. All image files are losslessy compressed - on the highest available compression levels, and EXIF and other metadata is removed. Trimage gives you various input functions to fit your - own workflow: A regular file dialog, dragging and dropping and various command line options.

+ +
+

+ Trimage image compressor – + 1.0.5 +

+ A cross-platform tool for losslessly optimizing PNG and JPG files for + web. +

+ Trimage is a cross-platform GUI and command-line interface to optimize + image files for websites, using + optipng, + pngcrush, + advpng + and jpegoptim, + depending on the filetype (currently, PNG and JPG files are supported). + It was inspired by + imageoptim. All image files + are losslessy compressed on the highest available compression levels, + and EXIF and other metadata is removed. Trimage gives you various input + functions to fit your own workflow: A regular file dialog, dragging and + dropping and various command line options. +

-

Trimage in action

- a screenshot of Trimage +

Trimage in action

+ a screenshot of Trimage -
-

Download

+
+

Download

-

Debian (sid)

-

Trimage is available in the official Debian Sid repositories:

-
    -
  1. sudo apt-get install trimage
  2. -
+

Debian (sid)

+

Trimage is available in the official Debian Sid repositories:

+
    +
  1. sudo apt-get install trimage
  2. +
-

Ubuntu

-

If you're on Natty (11.04), Trimage is available in the official repositories:

- Download for Ubuntu button -

Alternatively:

-
    -
  1. sudo apt-get install trimage
  2. -
-

If you're on Maveric (10.10), Karmic (9.10) or Lucid (10.04), type the following commands into your console:

-
    -
  1. sudo add-apt-repository ppa:kilian/trimage
  2. -
  3. sudo apt-get update
  4. -
  5. sudo apt-get install trimage
  6. -
-

If you are still on Jaunty, read this guide on installing PPA's.

+

Ubuntu

+

Trimage is available in the official repositories:

+ Download for Ubuntu +

Alternatively:

+
    +
  1. sudo apt-get install trimage
  2. +
-

Arch Linux

- Trimage is available from AUR, to install, type: -
    -
  1. yaourt -S trimage-git
  2. -
-

Mandriva Linux 2010.1

- Trimage is available Via two RPMs provided by Mandriva international backports -
    -
  1. Trimage 1.0.5 32bit
  2. -
  3. Trimage 1.0.5 63bit
  4. -
-

OpenSuse

-

Trimage is available for a variety of versions: 11.3, 11.4 and Factory and SLE. You can find them on software.opensuse.org.

+

Arch Linux

+ Trimage is available from AUR, to install, type: +
    +
  1. yaourt -S trimage
  2. +
+

Other *nix

+
    +
  1. Download the source via git or bzr (see repositories)
  2. +
  3. + Make sure you have all the requirements installed (see requirements) +
  4. +
  5. Enter python setup.py install into your console
  6. +
  7. Launch by executing trimage
  8. +
+

+ Help us make .snaps, .appimages etc: + contact us or + open a pull request +

+ +
+
+

Repositories

+

+ Git: Trimage is primarily developed on + GitHub. +

+

+ Bzr: Trimage is also available on + Launchpad. +

+

Trimage is MIT licenced. We encourage contributions via GitHub.

+
+
+

Thanks

+

The following people helped develop Trimage:

+
    +
  • Hugo Posnic
  • +
  • Neil Wallace
  • +
  • Jeroen Goudsmit
  • +
  • Tarnay Kálmán
  • +
  • Thomas Lété
  • +
  • Kyrill Detinov
  • +
+
+
+

Requirements

+
    +
  • python 3
  • +
  • python-qt5 5
  • +
  • optipng 0.6.2.1
  • +
  • pngcrush 1.6.7
  • +
  • advancecomp 1.15
  • +
  • jpegoptim 1.2.2
  • +
+
-

Other *nix

-
    -
  1. Download the source via git or bzr (see repositories)
  2. -
  3. Make sure you have all the requirements installed (see requirements)
  4. -
  5. Enter python setup.py install into your console
  6. -
  7. Launch by executing trimage
  8. -
-

Help us make .deb's, rpms's etc: contact us

-

Mac

-

Trimage should be able to run on Mac. Help us with this

-

Windows

-

Trimage should be able to run on Windows. Help us with this

+
+

Command line options

+
+
--version
+
show program's version number and exit
+
-h, --help
+
show this help message and exit
+
-v, --verbose
+
Verbose mode (default)
+
-q, --quiet
+
Quiet mode
+
-f FILENAME, --file=FILENAME
+
compresses image and exit
+
-d DIRECTORY, --directory=DIRECTORY
+
compresses images in directory and exit
+
+
+
+

Help

+

+ Please use GitHub for + reporting bugs and email + help@trimage.org for general + questions (though we are not a helpdesk!) +

+
+
-
-

Repositories

-

Git: Trimage is primarily developed on GitHub.

-

Bzr: Trimage is also available on Launchpad.

-

Trimage is MIT licenced. We encourage contributions via GitHub.

-
-
-

Thanks

-

The following people helped develop Trimage:

-
    -
  • Neil Wallace
  • -
  • Jeroen Goudsmit
  • -
  • Tarnay Kálmán
  • -
  • Thomas Lété
  • -
  • Kyrill Detinov
  • -
-
-
-

Donate

-

If you enjoy using Trimage, please buy us a coffee or a beer :)

- Click here to lend your support to: Trimage and make a donation at www.pledgie.com ! - - -
-
-

Requirements

-
    -
  • python 2.6
  • -
  • python-qt4 4.4
  • -
  • optipng 0.6.2.1
  • -
  • pngcrush 1.6.7
  • -
  • advancecomp 1.15
  • -
  • jpegoptim 1.2.2
  • -
-
- -
-

Command line options

-
-
--version
-
show program's version number and exit
-
-h, --help
-
show this help message and exit
-
-v, --verbose
-
Verbose mode (default)
-
-q, --quiet
-
Quiet mode
-
-f FILENAME, --file=FILENAME
-
compresses image and exit
-
-d DIRECTORY, --directory=DIRECTORY
-
compresses images in directory and exit
-
-
-
-

Help

-

Please use GitHub for reporting bugs and email help@trimage.org for general questions (though we are not a helpdesk!)

-
- -

Planned features

-

Version 1.1.0 final:

-
    -
  • Expand command line options
  • -
  • General refactoring
  • -
-

Version 1.1.1 final:

-
    -
  • Use multiprocessing instead of threads
  • -
- -

Beyond that

-
    -
  • Deletion of rows in the table view
  • -
  • Integration with online services such as punypng
  • -
  • Suggest something
  • -
-
- diff --git a/website/mac.png b/website/mac.png deleted file mode 100644 index d25935a3176f3be9aa708afc36a502922cd5ca5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 891 zcmeAS@N?(olHy`uVBq!ia0y~yV2}Y}4rT@h2G`5_%NQ6K%L9BuT$k>7Tr=lk(UiNB zx4pf1^Yf-XclPYrd-&Sdkg79*sZ*nyF8}`fPe;%AXIeP4xM>3 zZ{LT52M^_R98K-IdH(vR-Isr+XIBIlZ;Nd{S5nb@`t+HHPrvLv@pAE+VWrBiu3WkMGqvMt?Sf~gu6%53Yfq?HS2go~enH9pQ?K0e53b*NGdebD|AB*^ z@e_`nIJ^De!+Wp)hu5A9sXB4@@i+hSlW*StIC}p5!)HH&%Z{A8aOK#!SIVkd=dOR< zbL@HHgd1zOUpsO6%fZuc>;kI~U;lRM%D1ii@0Crvm)Cz?LQ3w&y{|uh{gsiH+Ohw7 z--cIH=WV%s^UaeN-`Ah`xbNiig4xe{Cd@m0_Rab|=j4x>STHa!FqQ=Q1v5B2yOGAg zz_`iN#WAGfR_z5||62hPZHZBuWhb<{M9ejvPfzLZn`)(~r^cPLGNDUM_FBlI)g2FB zIEYPl-7WwAahbbWQ7 z=&~VdX@I3yp!C=8b^G^kY)`e2k=q^Nlb+LiDtx8J@?RIP)vgTba>pSOux5H=Ex~cEjS0`fTE0%uj{evLB`{@#H*OM=wkP@A S)fgBU7(8A5T-G@yGywpL*zRut diff --git a/website/mandriva.png b/website/mandriva.png deleted file mode 100644 index b75f0e7bda8df26e6a9559324c2cd36156e74a1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1957 zcmeAS@N?(olHy`uVBq!ia0y~yV2}Y}4mJh`h9chkQy3W7b39!fLp+Y(4Xy5p36=eK zuJ-KQoyOMI*8R2iR=e-!&A%;n#?3%fBZOnA$^^G0jzwk?F3B(Ak4brUxcRPHr0|J@ zNh?!J)U;`%vVylK15(gy#vaeIH=|ForF*{u52_Y*65 zH!gcMzm9?5{O1ZDZolQz4*i|*K-=BlLGeh3>5G#oItH(U6@UHuweHOP^K%?de^7W6 z%71rl=g+fJ-$f5;)yAdfdA5hzSyVi0E&uTB|Hb)|+Gyh%P*Uuj}yzn`? zHg>b|&M6$N9f3P~Rdcv8u1$-9C511ZYII#4gvF^($mF{PjN=;0=-ZW9> z*Up>8*S`nMUZcri5WQk?z?bj`IK#QOA~c<48CoZ5`Vk!gkgKx)F(ykd)N%v z6U8>2s-^8elVo){H}@9` zDsUxw1&9WDE!iz(G2OCr*1iAihkt*a{$TfPz3{Ns6g`RL>8(3lrnUwiaJzBs8LRQW zXTEpm%L!FYdC@Yp>EgHcyQ^~7N0}BxZU|fcp~iRpZzj8`YNadIX>@Tj_;R0DSatMz zK^YD|$DJ&xEOyEZ zd=~gU+Az!f-Tti0U%#j7Kl}6UoEU?}5B=PWw*}n}1n6tDRLq=NmGx?pam2ccbaU2v z=F@eh9_xGW9@{Gwo5AC<#lF_99Wz*-HUvm?6o}8?)BYp=_f?^q8Si+MB?8Vb zo%Q95r~6u?4CV<(PfQI@-ZOzI*y+-n-8UT{26}(> z({}o(m?0XK+Zidg$&Ms`0;ldHbPA;lbMS_Y-gVK0m*iKkdDHeCGQi8Edmc zb{o|`x@2D5n|Oa>&h-x`cb;9GexlS}C)Cr8ldUSNu}+Rk5Ht}xf{aXni~QIU|Xt?iAi zzn31laEoPI?Xy=u^Z);NdgRtq!`$Cz_x@Zd8Lbn&?ah3T^ZP%ro!TaEGxy~B{l(tz z-(1%3@ndCjJHJ&rdhPG`I}3FtpFHvJ-@pEMiF@PP3pTr@-TJ5be2=M|?Z*cnPW~yM zrPN{n>CTD6?0qpS{(d@Y&b5Aj`AI43?FqH7R&C4L7_{HQXxi?hpZCZ=<(fLZ`QQQJ zm)|a!cPx?J|Ksk(xm}A=J%6aBqlPQ1(TnB-k-`t*#;Lj(K2Uw5DL3l$S(m=Q0Mv85rS zWAgR;vv+PY_Vl^^@$1KvK_(Yh%-X!OXth&BPJzPI^4D=IlESa^EfGH0wt@3}dQ^Dy z5$20r4=1RFPG#wze`+0%go?%F)oju_rW59G4}Z$t9eDryLO~}7hBfhmuEmoT^`7QR z@<|@ySz%D3^t^l9t;=E)V}vGkMW0Kk*SR=r%d5qYTOGV(RPW~YUNCtSxNL`_@XvE* zpOcD}ukn9TYfFDNXSL>1w|w^ryX{mISyU#UR1o0ESQ_*(EHu}5TBpk`%^r*K2d;cH5d?}Ub4P40Q^czMT*YkNf}c6<%n81Zk( s^PgvVCw`devE}3WIfnfIzyGj~%Xw_|EbvP`0|Nttr>mdKI;Vst04;XqegFUf diff --git a/website/opensuse.png b/website/opensuse.png deleted file mode 100644 index 46dea9b6d406375327c4bfc86c567ef75ecb223b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1297 zcmeAS@N?(olHy`uVBq!ia0y~yV2}Y}4mJh`h9chkQy3UnZg{#lhE&{o8(JN5xm4u; z(v|g7+`Wyh=JqXR`_Xacl1O976&^3!pA3)A792=u&Q;+%9z55*v4X$F@_>&g^F$X3 zeo1Hk{@z|sp1E@zo*CWXF?J5R`L<5I;Dd~OiOur;Kjsvle_#Fn{kyt%ufNNhHgXDN z_?v;+Zt@8YV^2_sp9Q2}KC4R_ELo zpWwt;w^sJe+SptrtDk4*%@Uok{#jhd>f$+t-w(!mB~P*SQ+lhXAi8(n>J*c9FEKk6 z6%93^5*wp8|6la9L~`BAzgU{hH-GOehE1=J^V_;Dd$)E`9`BtGy=!8x%E#FK|H*P* zaoO~}xmHU%o42H$cbsy&soSriqH@ZH=jY32cg%YFtSfGckH&Z9^x%E7l8-RG`Vc8s z@-Nn6ZRh_vuMDmUZ_1pwbz7RzrLGByis?ze*SE}B#m{hD*(>ADwZ4|UEAHErS=M-b z-JQbb8gP5=^wTnOc6muNKjz0?c)D-ajW^R8<{#D)x6e$RmnNh-x=674P7yq%fxF@D4 zsP!jB#re*ScYKdsy_Y=+d^VHoz8b>}SH&)Cmf41q#+vb2>{pBp3jXt%dRffT6vovA(to-W`@zlwIDfZ-5=a@TsTg=zlE%z-v{P4bCR>@5F-o}Wdi;AL|Dif!6Kgh_= z__c%MV^);IrspAgjlbpzS9h$vtKiGAJ}&K+`fZC@KTm#J;c0Pw`LVz|I+Ft)AGORi zn!Ba-jPUffEqorURpv&<^KP%}I^7tdXsjJ7H)CFiSI(cBiCd0e@Sb{F`o?cRk&0zg zMb;nvuIrPy>W=Zl2Jzn>ZL-?dzOE&4b$wRPstt2>o}bwIOj7l4PWZ{GhvQc4xB7I> z&gRGEqb2$q3hq97eIT_h_rAPo`L#a%_nOvQCmSr7v?=!0_O7!H=CRu?}51>3T&lwLvbQX~z<6n~(+1zCB*laW2+-pK@NRB=_y)Ngv`F z(hs*-N`K#<>iU^sz6xW`gTgkUZm#mE9l3j#zuIG_sbd}Mw>H`{{@|kTm8x6^q-!N4 zPdb0DjVX`(x%;r6BTt8Fs!bQebeAh@^iSH>>zaOBV^*jcpCw zDtNq{`$nV0{2jrc_dlrTmJl#|%WKYb!1`V%`|ggnuPf~4>{QvD#1(v?bH8*)@8rpW zM=d7_?wWVp?Dgzq<%MsrpS!+7@Wj`p>|N_+wk-R{zoL5eN=~1IJwL>iPINx!JSJzw Qz`(%Z>FVdQ&MBb@0PGNIW&i*H diff --git a/website/windows.png b/website/windows.png deleted file mode 100644 index 1b24e92fcd320e42d19aff07604d05c5445376d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1705 zcmeAS@N?(olHy`uVBq!ia0y~yV2}Y}4kiW$hQIk9hZz_c7>k44ofy`glX=O&z`$AH z5n0T@z%2~Ij105pNH8!ku$OrHy0YKolws4?-CI(+n}LBrvcxr_#5q4VH#M(>!MP|k zu_QG`p**uBL&4qCHz2%`Pn>~)fi20~-G$*l2rk&Wd@=(A8;hrlV~EG`t<$3OW5Old z?w8)GK5uuXC%r~Oev-uT#w$w%mphzTsMVpZqPP0qmvDtT2BvjC`2Tcxad2*Zv?yfG zm8R@VDy4j-$?A_KtJ91&r+-U)yY<@f*2^r>PLG)G$$xlJy=7nc{Un!`|D_iH_$M+X za7|$3=2Jom2rHpevdPpSOoUdj~ zb$FrK(>MRlUyY#58xu{^AD1Nw`e}TAZIh|9tMC7I_xfFzfB)U;%|7?(*K3C=Pgv;R zxy$~2Te+Q4$iGK(`npvNZI+@6R zwR!mW@5$Z2CQ2=r@O$I4yZN7@P5I|NAz&2n#C%-yZMx_7GZ3r}SghJdce?Mq!sK2CeP=GW}$so4j={I_^(>wR4Q+K-w? zA056YXH98M$_jB^BX%mPO(AvP6L0R*fj6pdUK9^_`|;7A?8)2fw#lv+>Da))8Fs^7W3{@(t}ufH#hRx4lhX{I;ob zrs3wB6E8EbPIq0kKIg8`*04|pQ-*?bh38ZozQ3kHRJ z-Itk>^=jgL*?(c4;=5h_->E*6PmJ>^pZ~>K>ioTqq#tt_*d8qTC*+WyZttX&cSq&?n-ib+hjY(nVG?zy;kq?n zfaQYhiH4Uhe3NcH_EAqYD0e)0Gk)T|;5~7krz`ci&icqdwUGArJ`wggOyH+f!J&`F z=3ZvqYx!13iyy7?7jeIG_ul>5*tPoi|7G#AFYuQXTIRaICuUnTp=X;i;aBbh-LvQSE{eN(-ZgTnY=DTdxEYhz6Pc8lMscKfb z#H>k9fxqkY=imEZa9s4qxv#zF9%cTOzh7}yUAarfNl(MXrtImdM_aqSKi_=(ZgZyV zl@D`QCA_&IIwMLiozYcS@}5@quKGV$eE;4|5$#^CF8W~B#szA?s`)>@%rHtSC?G(le!-uest+cHR-nOxnJwAr~iN1p2Wa*d|`LV zls>-Y&2cmR=UhzPBYOPfojWm;RJ=65rT){-$W%~eVK7o)j%MQHVe1rl#PihWAFH3M XxzmM`iE9}c7#KWV{an^LB{Ts5u;nX7 From 54d4b741726485321422e9f6e6da91c376874420 Mon Sep 17 00:00:00 2001 From: kilian Date: Tue, 12 Mar 2019 12:03:15 +0100 Subject: [PATCH 52/62] update debian folder --- debian/changelog | 9 ++++++++- debian/compat | 2 +- debian/control | 6 ++---- debian/copyright | 44 -------------------------------------------- debian/pycompat | 2 +- debian/rules | 0 6 files changed, 12 insertions(+), 51 deletions(-) mode change 100644 => 100755 debian/rules diff --git a/debian/changelog b/debian/changelog index 30f9a17..fa8c6f2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +trimage (1.0.6-0ubuntu1) bionic; urgency=low + + * Migrate to python 3 and python-qt 5 + * fix bug with hanging thread + * remove hurry.filesize package + + -- Kilian Valkhof Tue, 12 Mar 2019 11:54:00 +0200 + trimage (1.0.5-0ubuntu1) jaunty; urgency=low * prevent images from becoming larger after recompression @@ -70,4 +78,3 @@ trimage (1.0.0b-0ubuntu1) jaunty; urgency=low * Trimage image compressor -- Kilian Valkhof Tue, 23 Mar 2010 20:18:17 +0100 - diff --git a/debian/compat b/debian/compat index 7f8f011..f599e28 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -7 +10 diff --git a/debian/control b/debian/control index d2501f0..9646ecf 100644 --- a/debian/control +++ b/debian/control @@ -2,15 +2,13 @@ Source: trimage Section: graphics Priority: optional Maintainer: Kilian Valkhof -Build-Depends: debhelper (>=7), python-support (>=0.8.7), python -XS-Python-Version: >=3.4 -Standards-Version: 3.9.1 +Build-Depends: debhelper (>=7), python3 +Standards-Version: 3.9.2 Homepage: http://trimage.org Package: trimage Architecture: all Depends: ${misc:Depends}, ${python:Depends}, python-pyqt5 (>=5.7), optipng (>=0.6.2.1), advancecomp (>=1.15), jpegoptim (>=1.2.2), pngcrush (>=1.6.7) -XB-Python-Version: ${python:Versions} Description: GUI and command-line interface to optimize image files Trimage is a cross-platform GUI and command-line interface to optimize image files via optipng, advpng, pngcrush and jpegoptim, depending on the filetype diff --git a/debian/copyright b/debian/copyright index 2a6f587..a9897e2 100644 --- a/debian/copyright +++ b/debian/copyright @@ -39,49 +39,6 @@ The Debian packaging is: and is licensed under the MIT license, see above. -hurry.filesize is: - - Copyright (C) Martijn Faassen, Startifact - -License: - - Zope Public License (ZPL) Version 2.1 - A copyright notice accompanies this license document that identifies the - copyright holders. This license has been certified as open source. It has - also been designated as GPL compatible by the Free Software Foundation (FSF). - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions in source code must retain the accompanying copyright - notice, this list of conditions, and the following disclaimer. - 2. Redistributions in binary form must reproduce the accompanying - copyright notice, this list of conditions, and the following disclaimer - in the documentation and/or other materials provided with the - distribution. - 3. Names of the copyright holders must not be used to endorse or promote - products derived from this software without prior written permission from - the copyright holders. - 4. The right to distribute this software or to use it for any purpose does - not give you the right to use Servicemarks (sm) or Trademarks (tm) of the - copyright holders. Use of them is covered by separate agreement with the - copyright holders. - 5. If any files are modified, you must cause the modified files to carry - prominent notices stating that you changed the files and the date of any - change. - - Disclaimer - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY - EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ThreadPool is: Copyright (c) Morten Holdflod Moeller @@ -253,4 +210,3 @@ License: apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. - diff --git a/debian/pycompat b/debian/pycompat index 0cfbf08..00750ed 100644 --- a/debian/pycompat +++ b/debian/pycompat @@ -1 +1 @@ -2 +3 diff --git a/debian/rules b/debian/rules old mode 100644 new mode 100755 From 8376c4a238ff725c29b435bdd5627e8e22a8d07a Mon Sep 17 00:00:00 2001 From: kilian Date: Tue, 12 Mar 2019 12:15:49 +0100 Subject: [PATCH 53/62] update version number to 1.0.6 in various places --- doc/trimage.1 | 2 +- setup.py | 2 +- trimage/trimage.py | 2 +- website/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/trimage.1 b/doc/trimage.1 index 501742d..0907c27 100644 --- a/doc/trimage.1 +++ b/doc/trimage.1 @@ -3,7 +3,7 @@ .\" This manual page is distributed under the terms .\" of the GNU Free Documentation License version 1.3. .\" -.TH TRIMAGE "1" "2011-07-11" "trimage 1.0.5" "User Commands" +.TH TRIMAGE "1" "2019-03-12" "trimage 1.0.6" "User Commands" .SH NAME trimage \- losslessly optimizing png and jpeg liles diff --git a/setup.py b/setup.py index dc8cfe3..2ba0a6f 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from distutils.core import setup setup(name = "trimage", - version = "1.0.5", + version = "1.0.6", description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files", author = "Kilian Valkhof, Paul Chaplin", author_email = "help@trimage.org", diff --git a/trimage/trimage.py b/trimage/trimage.py index a0f7092..2fefedf 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -18,7 +18,7 @@ from ui import Ui_trimage from tools import * -VERSION = "1.0.5" +VERSION = "1.0.6" class StartQt(QMainWindow): diff --git a/website/index.html b/website/index.html index fd290c5..0598c85 100644 --- a/website/index.html +++ b/website/index.html @@ -76,7 +76,7 @@

Trimage image compressor – - 1.0.5 + 1.0.6

A cross-platform tool for losslessly optimizing PNG and JPG files for From 9bdd44a4e4c3877cf2d62fb9888db02a95ca0ee3 Mon Sep 17 00:00:00 2001 From: kilian Date: Tue, 12 Mar 2019 12:36:32 +0100 Subject: [PATCH 54/62] further refinement/updates of debian folder --- debian/compat | 2 +- debian/control | 2 +- debian/pycompat | 1 - debian/rules | 1 - desktop/trimage.desktop | 2 +- 5 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 debian/pycompat diff --git a/debian/compat b/debian/compat index f599e28..b4de394 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -10 +11 diff --git a/debian/control b/debian/control index 9646ecf..7396a46 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: graphics Priority: optional Maintainer: Kilian Valkhof Build-Depends: debhelper (>=7), python3 -Standards-Version: 3.9.2 +Standards-Version: 4.3.0 Homepage: http://trimage.org Package: trimage diff --git a/debian/pycompat b/debian/pycompat deleted file mode 100644 index 00750ed..0000000 --- a/debian/pycompat +++ /dev/null @@ -1 +0,0 @@ -3 diff --git a/debian/rules b/debian/rules index 4f2c774..cbe925d 100755 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,3 @@ #!/usr/bin/make -f %: dh $@ - diff --git a/desktop/trimage.desktop b/desktop/trimage.desktop index 2cf870a..80342ec 100644 --- a/desktop/trimage.desktop +++ b/desktop/trimage.desktop @@ -7,4 +7,4 @@ Type=Application Exec=trimage Categories=Application;Qt;Graphics; StartupNotify=true - +Keywords=compression,compressor,images,jpg,jpeg,png,web From 9d6edd3847743d26309f4fcf0bbda8da42869f97 Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Thu, 3 Oct 2019 17:05:33 +0200 Subject: [PATCH 55/62] Remove win32 bytes following d6118b5 --- trimage/tools.py | 5 ++--- trimage/trimage.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/trimage/tools.py b/trimage/tools.py index f787c8c..98e0218 100644 --- a/trimage/tools.py +++ b/trimage/tools.py @@ -7,7 +7,6 @@ from subprocess import call, PIPE def check_dependencies(): """Check if the required command line apps exist.""" - exe = ".exe" if (sys.platform == "win32") else "" status = True dependencies = { "jpegoptim": "--version", @@ -17,7 +16,7 @@ def check_dependencies(): } for elt in dependencies: - retcode = safe_call(elt + exe + " " + dependencies[elt]) + retcode = safe_call(elt + " " + dependencies[elt]) if retcode != 0: status = False print("[error] please install {}".format(elt), file=sys.stderr) @@ -43,4 +42,4 @@ def human_readable_size(num, suffix="B"): if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 - return "%.1f%s%s" % (num, "Y", suffix) \ No newline at end of file + return "%.1f%s%s" % (num, "Y", suffix) diff --git a/trimage/trimage.py b/trimage/trimage.py index 2fefedf..3f7dbc3 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -371,10 +371,9 @@ class Image: file)" self.reset() self.compressing = True - exe = ".exe" if (sys.platform == "win32") else "" runString = { - "jpeg": "jpegoptim" + exe + " -f --strip-all '%(file)s'", - "png": "optipng" + exe + " -force -o7 '%(file)s'&&advpng" + exe + " -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'" + "jpeg": "jpegoptim -f --strip-all '%(file)s'", + "png": "optipng -force -o7 '%(file)s'&&advpng -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'" } # create a backup file backupfullpath = '/tmp/' + self.filename_w_ext From 1342503b5449458d8cec23c891d526497089e235 Mon Sep 17 00:00:00 2001 From: Boyuan Yang Date: Tue, 3 Dec 2019 17:00:44 -0500 Subject: [PATCH 56/62] Fix trimage.desktop grammar According to https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s04.html , Keywords should be split by semicolons, not commas. --- desktop/trimage.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/trimage.desktop b/desktop/trimage.desktop index 80342ec..8b48fa6 100644 --- a/desktop/trimage.desktop +++ b/desktop/trimage.desktop @@ -7,4 +7,4 @@ Type=Application Exec=trimage Categories=Application;Qt;Graphics; StartupNotify=true -Keywords=compression,compressor,images,jpg,jpeg,png,web +Keywords=compression;compressor;images;jpg;jpeg;png;web; From 269e052ba407c16ab32953276c776d543930b917 Mon Sep 17 00:00:00 2001 From: Kilian Valkhof Date: Fri, 20 Dec 2019 16:51:10 +0100 Subject: [PATCH 57/62] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 288cbf5..75b2061 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +### Made by [@kilianvalkhof](https://twitter.com/kilianvalkhof) + +#### Other projects: + +- 💻 [Polypane](https://polypane.app) - Develop responsive websites and apps twice as fast on multiple screens at once +- 🖌️ [Superposition](https://superposition.design) - Kickstart your design system by extracting design tokens from your website +- 🗒️ [FromScratch](https://fromscratch.rocks) - A smart but simple autosaving scratchpad + +--- + # Trimage image compressor A cross-platform tool for optimizing PNG and JPG files. From c3244e19a665bc6f3ec03e10e87467a19660469a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20Tarnay?= Date: Thu, 23 Apr 2020 17:44:12 +0200 Subject: [PATCH 58/62] add macos instructions to website --- website/index.html | 10 ++++++++-- website/macos.png | Bin 0 -> 835 bytes 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 website/macos.png diff --git a/website/index.html b/website/index.html index 0598c85..0e7995a 100644 --- a/website/index.html +++ b/website/index.html @@ -125,6 +125,14 @@
  1. yaourt -S trimage
+ +

macOS

+ Trimage is available from Homebrew, to install, type: +
    +
  1. brew install trimage
  2. +
+

Launch by executing trimage in Terminal.app

+

Other *nix

  1. Download the source via git or bzr (see repositories)
  2. @@ -142,8 +150,6 @@ >

    diff --git a/website/macos.png b/website/macos.png new file mode 100644 index 0000000000000000000000000000000000000000..120e1fd371a7b5212bde85f68a6295baa0aec8a3 GIT binary patch literal 835 zcmeAS@N?(olHy`uVBq!ia0y~yU{GLSU{L2^VPIf5Qr2{wfq{V~-O<;Pfnj4m_n$;o z1_lPEByV>Y28LC=%0>(f4D2PIzOL*~Sj73|yC}CM>cNTXMVRNFe5xn{8s43wbFuD@}jzUl{Cb3 zAGwIQYSsR@_hV6%Uq8vt zkp1vgj;O7tlBbA9%_)rD`q8aQ@AUWB-vz#0NmajVemZ7xb>`XKFS2<*NqP#}9qxbDxiRhan`@yv zj&0oLen|3Z@YWWcPJJDL?B#2(#_Txu(yp^D{_D)sDJ(ihhC=t=-DaAz;MAXt|MgiK zds`BER$gRrx7AX=oT04zP#ACMdN1fmk zn;sR9Gt;%SGYZYEI{5d*UJ}^y(|T6I9A396A|;mU_v}9P3PwgUrkl75FR6K#{@#oA z&*MMZ86H1x_^YvO`F)jpeUkYdZb^$v`yYo)nOt2eq7}5b>SspN{kaMvmy`N~Cw^cr ztBAn*CgRe6=_msOgKCLuL`h0wNvc(HQEFmI zDua=Mk%6v(iLQ}xh@q*Kv4NGbp|*j6m4Sh#Rk;|7hTQy=%(P0}8Y0rquVP?eVDNPH Kb6Mw<&;$Ui$YqZJ literal 0 HcmV?d00001 From 493a0e18d2ac69c3bc3df6cc9998dbccd03ff409 Mon Sep 17 00:00:00 2001 From: Carson Reinke Date: Tue, 23 Jun 2020 15:30:34 -0400 Subject: [PATCH 59/62] Missing format specifier for arguments of image status Fixes https://github.com/Kilian/Trimage/issues/73 --- trimage/trimage.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 3f7dbc3..0fd2644 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -304,6 +304,7 @@ class ImageRow: def __init__(self, image, waitingIcon=None): """Build the information visible in the table image row.""" self.image = image + d = { 'filename_w_ext': lambda i: self.statusStr() % i.filename_w_ext, 'oldfilesizestr': lambda i: human_readable_size(i.oldfilesize) @@ -331,9 +332,9 @@ class ImageRow: message = "Compressing %s..." return message if not self.image.compressed and self.image.recompression: - return "Queued for recompression..." + return "Queued for recompression %s..." if not self.image.compressed: - return "Queued..." + return "Queued %s..." return "%s" def __getitem__(self, key): From 8af532f25ece2c01d81e6859225b7209f2f64321 Mon Sep 17 00:00:00 2001 From: Andrew Wong Date: Wed, 30 Sep 2020 19:22:31 +1000 Subject: [PATCH 60/62] Remove unreachable shutdown code * Implementing `__del__` is discouraged * Imports (i.e. `import logging`) are cleaned up, leading to NoneType related errors * When running via GUI mode, the shutdown code is never reached; we can let Python's internal GC clean everything --- trimage/ThreadPool/ThreadPool.py | 7 ------- trimage/trimage.py | 3 --- 2 files changed, 10 deletions(-) diff --git a/trimage/ThreadPool/ThreadPool.py b/trimage/ThreadPool/ThreadPool.py index 9f9e051..17e55e4 100644 --- a/trimage/ThreadPool/ThreadPool.py +++ b/trimage/ThreadPool/ThreadPool.py @@ -30,9 +30,6 @@ class ThreadPoolMixIn: def __init__(self, threadpool=None): if (threadpool == None): threadpool = ThreadPool() - self.__private_threadpool = True - else: - self.__private_threadpool = False self.__threadpool = threadpool @@ -52,10 +49,6 @@ class ThreadPoolMixIn: def process_request(self, request, client_address): self.__threadpool.add_job(self.process_request_thread, [request, client_address]) - def shutdown(self): - if (self.__private_threadpool): self.__threadpool.shutdown() - - class AddJobException(Exception): ''' Exceptoion raised when a Job could not be added diff --git a/trimage/trimage.py b/trimage/trimage.py index 3f7dbc3..1ef81be 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -409,9 +409,6 @@ class Worker(QThread): self.toDisplay = Queue() self.threadpool = ThreadPool(max_workers=cpu_count()) - def __del__(self): - self.threadpool.shutdown() - def compress_file(self, images, showapp, verbose, imagelist): """Start the worker thread.""" for image in images: From 636b5a975072fdb8159753eb28062fbed901a23d Mon Sep 17 00:00:00 2001 From: Daniele Scasciafratte Date: Thu, 4 Mar 2021 19:26:55 +0100 Subject: [PATCH 61/62] Fix #77 This fix https://github.com/Kilian/Trimage/issues/77 As debian user this fix the issue for me --- trimage/trimage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 7efb522..725d208 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -306,7 +306,7 @@ class ImageRow: self.image = image d = { - 'filename_w_ext': lambda i: self.statusStr() % i.filename_w_ext, + 'filename_w_ext': lambda i: self.statusStr().format(i.filename_w_ext), 'oldfilesizestr': lambda i: human_readable_size(i.oldfilesize) if i.compressed else "", 'newfilesizestr': lambda i: human_readable_size(i.newfilesize) @@ -335,7 +335,7 @@ class ImageRow: return "Queued for recompression %s..." if not self.image.compressed: return "Queued %s..." - return "%s" + return "{0}" def __getitem__(self, key): return self.d[key](self.image) From 031adf60540614355620ce8eaa01a4c63a085d62 Mon Sep 17 00:00:00 2001 From: Daniele Scasciafratte Date: Wed, 10 Mar 2021 12:58:46 +0100 Subject: [PATCH 62/62] Fix incomplete code of #77 I forgot to change the various `%s` in the pull request with the rest of the function or it will be print "Compressing %s". --- trimage/trimage.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/trimage/trimage.py b/trimage/trimage.py index 725d208..b618b4b 100644 --- a/trimage/trimage.py +++ b/trimage/trimage.py @@ -327,14 +327,14 @@ class ImageRow: def statusStr(self): """Set the status message.""" if self.image.failed: - return "ERROR: %s" + return "ERROR: {0}" if self.image.compressing: - message = "Compressing %s..." + message = "Compressing {0}..." return message if not self.image.compressed and self.image.recompression: - return "Queued for recompression %s..." + return "Queued for recompression {0}..." if not self.image.compressed: - return "Queued %s..." + return "Queued {0}..." return "{0}" def __getitem__(self, key):