From 8eca5302754db5456c4049d1ae3d6b24e4c4949f Mon Sep 17 00:00:00 2001 From: Hugo Posnic Date: Sat, 18 Nov 2017 14:44:11 +0100 Subject: [PATCH 01/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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))