mirror of
https://github.com/Kilian/Trimage.git
synced 2026-01-26 01:58:41 -05:00
Merge pull request #47 from Huluti/master
First try to adopt Python3 and PyQt5
This commit is contained in:
commit
117a1a36cc
18 changed files with 313 additions and 449 deletions
119
.gitignore
vendored
119
.gitignore
vendored
|
|
@ -1,5 +1,116 @@
|
||||||
*.pyc
|
# Byte-compiled / optimized / DLL files
|
||||||
MANIFEST
|
__pycache__/
|
||||||
dist/
|
*.py[cod]
|
||||||
build/
|
*$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/
|
||||||
|
|
|
||||||
|
|
@ -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 desktop *.svg *.desktop
|
||||||
recursive-include src/ *.py *.png
|
recursive-include src/ *.py *.png
|
||||||
|
|
||||||
|
|
|
||||||
15
README
15
README
|
|
@ -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
|
|
||||||
|
|
||||||
21
README.md
Normal file
21
README.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Trimage image compressor
|
||||||
|
A cross-platform tool for optimizing PNG and JPG files.
|
||||||
|
|
||||||
|
Trimage is a cross-platform GUI and command-line interface to optimize image files via [advpng](http://advancemame.sourceforge.net/comp-readme.html), [jpegoptim](http://www.kokkonen.net/tjko/projects.html), [optipng](http://optipng.sourceforge.net) and [pngcrush](https://pmt.sourceforge.io/pngcrush) depending on the
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- advpng
|
||||||
|
- jpegoptim
|
||||||
|
- optipng
|
||||||
|
- pngcrush
|
||||||
17
TODO.md
Normal file
17
TODO.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 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
|
||||||
|
- 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
|
||||||
|
- figure out how to make mac and win versions (someone else :) <- via gui2exe
|
||||||
|
- 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
|
||||||
5
debian/control
vendored
5
debian/control
vendored
|
|
@ -3,13 +3,13 @@ Section: graphics
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Kilian Valkhof <kilian@kilianvalkhof.com>
|
Maintainer: Kilian Valkhof <kilian@kilianvalkhof.com>
|
||||||
Build-Depends: debhelper (>=7), python-support (>=0.8.7), python
|
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
|
Standards-Version: 3.9.1
|
||||||
Homepage: http://trimage.org
|
Homepage: http://trimage.org
|
||||||
|
|
||||||
Package: trimage
|
Package: trimage
|
||||||
Architecture: all
|
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}
|
XB-Python-Version: ${python:Versions}
|
||||||
Description: GUI and command-line interface to optimize image files
|
Description: GUI and command-line interface to optimize image files
|
||||||
Trimage is a cross-platform GUI and command-line interface to optimize image
|
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
|
compressed on the highest available compression levels. Trimage gives you
|
||||||
various input functions to fit your own workflow: A regular file dialog,
|
various input functions to fit your own workflow: A regular file dialog,
|
||||||
dragging and dropping and various command line options.
|
dragging and dropping and various command line options.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
||||||
9
setup.py
9
setup.py
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
win=(sys.platform == "win32")
|
win=(sys.platform == "win32")
|
||||||
|
|
@ -9,7 +9,7 @@ if win:
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
setup(name = "trimage",
|
setup(name = "trimage",
|
||||||
version = "1.0.2",
|
version = "1.0.5",
|
||||||
description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files",
|
description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files",
|
||||||
author = "Kilian Valkhof, Paul Chaplin",
|
author = "Kilian Valkhof, Paul Chaplin",
|
||||||
author_email = "help@trimage.org",
|
author_email = "help@trimage.org",
|
||||||
|
|
@ -17,15 +17,14 @@ setup(name = "trimage",
|
||||||
license = "MIT license",
|
license = "MIT license",
|
||||||
package_dir = {'trimage' : 'src/trimage'},
|
package_dir = {'trimage' : 'src/trimage'},
|
||||||
packages = ["trimage",
|
packages = ["trimage",
|
||||||
"trimage.filesize",
|
"trimage.ThreadPool"],
|
||||||
"trimage.ThreadPool",],
|
|
||||||
package_data = {"trimage" : ["pixmaps/*.*"] },
|
package_data = {"trimage" : ["pixmaps/*.*"] },
|
||||||
data_files=[('share/icons/hicolor/scalable/apps', ['desktop/trimage.svg']),
|
data_files=[('share/icons/hicolor/scalable/apps', ['desktop/trimage.svg']),
|
||||||
('share/applications', ['desktop/trimage.desktop']),
|
('share/applications', ['desktop/trimage.desktop']),
|
||||||
('share/man/man1', ['doc/trimage.1'])],
|
('share/man/man1', ['doc/trimage.1'])],
|
||||||
scripts = ["trimage"],
|
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.""",
|
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
|
#for py2exe
|
||||||
windows=[r'src\trimage\trimage.py'],
|
windows=[r'src\trimage\trimage.py'],
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
'''
|
'''
|
||||||
ThreadPool Implementation
|
ThreadPool Implementation
|
||||||
|
|
||||||
@author: Morten Holdflod Moeller - morten@holdflod.dk
|
@author: Morten Holdflod Moeller - morten@holdflod.dk
|
||||||
@license: LGPL v3
|
@license: LGPL v3
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
from threading import Thread, RLock
|
from threading import Thread, RLock
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from Queue import Queue, Empty
|
from queue import Queue, Empty
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
@ -31,7 +33,7 @@ class ThreadPoolMixIn:
|
||||||
self.__private_threadpool = True
|
self.__private_threadpool = True
|
||||||
else:
|
else:
|
||||||
self.__private_threadpool = False
|
self.__private_threadpool = False
|
||||||
|
|
||||||
self.__threadpool = threadpool
|
self.__threadpool = threadpool
|
||||||
|
|
||||||
def process_request_thread(self, request, client_address):
|
def process_request_thread(self, request, client_address):
|
||||||
|
|
@ -48,11 +50,11 @@ class ThreadPoolMixIn:
|
||||||
self.close_request(request)
|
self.close_request(request)
|
||||||
|
|
||||||
def process_request(self, request, client_address):
|
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):
|
def shutdown(self):
|
||||||
if (self.__private_threadpool): self.__threadpool.shutdown()
|
if (self.__private_threadpool): self.__threadpool.shutdown()
|
||||||
|
|
||||||
|
|
||||||
class AddJobException(Exception):
|
class AddJobException(Exception):
|
||||||
'''
|
'''
|
||||||
|
|
@ -61,64 +63,64 @@ class AddJobException(Exception):
|
||||||
'''
|
'''
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
Exception.__init__(self, msg)
|
Exception.__init__(self, msg)
|
||||||
|
|
||||||
|
|
||||||
class ThreadPool:
|
class ThreadPool:
|
||||||
'''
|
'''
|
||||||
The class implementing the 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 Job: #IGNORE:R0903
|
||||||
'''
|
'''
|
||||||
Class encapsulating a job to be handled
|
Class encapsulating a job to be handled
|
||||||
by ThreadPool workers
|
by ThreadPool workers
|
||||||
'''
|
'''
|
||||||
def __init__(self, function, args, return_callback=None):
|
def __init__(self, function, args, return_callback=None):
|
||||||
self.callable = function
|
self.callable = function
|
||||||
self.arguments = args
|
self.arguments = args
|
||||||
self.return_callback = return_callback
|
self.return_callback = return_callback
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
'''
|
'''
|
||||||
Called to execute the function
|
Called to execute the function
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
return_value = self.callable(*self.arguments) #IGNORE:W0142
|
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 = logging.getLogger("threadpool.worker")
|
||||||
logger.warning("A job in the ThreadPool raised an exception: " + excep)
|
logger.warning("A job in the ThreadPool raised an exception: " + excep)
|
||||||
#else do nothing cause we don't know what to do...
|
#else do nothing cause we don't know what to do...
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if (self.return_callback != None):
|
if (self.return_callback != None):
|
||||||
self.return_callback(return_value)
|
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 = logging.getLogger('threadpool')
|
||||||
logger.warning('Error while delivering return value to callback function')
|
logger.warning('Error while delivering return value to callback function')
|
||||||
|
|
||||||
class Worker(Thread):
|
class Worker(Thread):
|
||||||
'''
|
'''
|
||||||
A worker thread handling jobs in the thread pool
|
A worker thread handling jobs in the thread pool
|
||||||
job queue
|
job queue
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, pool):
|
def __init__(self, pool):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
|
|
||||||
if (not isinstance(pool, ThreadPool)):
|
if (not isinstance(pool, ThreadPool)):
|
||||||
raise TypeError("pool is not a ThreadPool instance")
|
raise TypeError("pool is not a ThreadPool instance")
|
||||||
|
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
|
|
||||||
self.alive = True
|
self.alive = True
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
'''
|
'''
|
||||||
The workers main-loop getting jobs from queue
|
The workers main-loop getting jobs from queue
|
||||||
and executing them
|
and executing them
|
||||||
'''
|
'''
|
||||||
while self.alive:
|
while self.alive:
|
||||||
|
|
@ -130,83 +132,83 @@ class ThreadPool:
|
||||||
self.pool.worker_inactive()
|
self.pool.worker_inactive()
|
||||||
else:
|
else:
|
||||||
self.alive = False
|
self.alive = False
|
||||||
|
|
||||||
self.pool.punch_out()
|
self.pool.punch_out()
|
||||||
|
|
||||||
def __init__(self, max_workers = 5, kill_workers_after = 3):
|
def __init__(self, max_workers = 5, kill_workers_after = 3):
|
||||||
if (not isinstance(max_workers, int)):
|
if (not isinstance(max_workers, int)):
|
||||||
raise TypeError("max_workers is not an int")
|
raise TypeError("max_workers is not an int")
|
||||||
if (max_workers < 1):
|
if (max_workers < 1):
|
||||||
raise ValueError('max_workers must be >= 1')
|
raise ValueError('max_workers must be >= 1')
|
||||||
|
|
||||||
if (not isinstance(kill_workers_after, int)):
|
if (not isinstance(kill_workers_after, int)):
|
||||||
raise TypeError("kill_workers_after is not an int")
|
raise TypeError("kill_workers_after is not an int")
|
||||||
|
|
||||||
self.__max_workers = max_workers
|
self.__max_workers = max_workers
|
||||||
self.__kill_workers_after = kill_workers_after
|
self.__kill_workers_after = kill_workers_after
|
||||||
|
|
||||||
# This Queue is assumed Thread Safe
|
# This Queue is assumed Thread Safe
|
||||||
self.__jobs = Queue()
|
self.__jobs = Queue()
|
||||||
|
|
||||||
self.__worker_count_lock = RLock()
|
self.__worker_count_lock = RLock()
|
||||||
self.__worker_count = 0
|
self.__worker_count = 0
|
||||||
self.__active_worker_count = 0
|
self.__active_worker_count = 0
|
||||||
|
|
||||||
self.__shutting_down = False
|
self.__shutting_down = False
|
||||||
logger = logging.getLogger('threadpool')
|
logger = logging.getLogger('threadpool')
|
||||||
logger.info('started')
|
logger.info('started')
|
||||||
|
|
||||||
def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5):
|
def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5):
|
||||||
if (not isinstance(clean_shutdown_reties, int)):
|
if (not isinstance(clean_shutdown_reties, int)):
|
||||||
raise TypeError("clean_shutdown_reties is not an int")
|
raise TypeError("clean_shutdown_reties is not an int")
|
||||||
if (not clean_shutdown_reties >= 0):
|
if (not clean_shutdown_reties >= 0):
|
||||||
raise ValueError('clean_shutdown_reties must be >= 0')
|
raise ValueError('clean_shutdown_reties must be >= 0')
|
||||||
|
|
||||||
if (not isinstance(wait_for_workers_period, int)):
|
if (not isinstance(wait_for_workers_period, int)):
|
||||||
raise TypeError("wait_for_workers_period is not an int")
|
raise TypeError("wait_for_workers_period is not an int")
|
||||||
if (not wait_for_workers_period >= 0):
|
if (not wait_for_workers_period >= 0):
|
||||||
raise ValueError('wait_for_workers_period must be >= 0')
|
raise ValueError('wait_for_workers_period must be >= 0')
|
||||||
|
|
||||||
logger = logging.getLogger("threadpool")
|
logger = logging.getLogger("threadpool")
|
||||||
logger.info("shutting down")
|
logger.info("shutting down")
|
||||||
|
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
self.__shutting_down = True
|
self.__shutting_down = True
|
||||||
self.__max_workers = 0
|
self.__max_workers = 0
|
||||||
self.__kill_workers_after = 0
|
self.__kill_workers_after = 0
|
||||||
|
|
||||||
retries_left = clean_shutdown_reties
|
retries_left = clean_shutdown_reties
|
||||||
while (retries_left > 0):
|
while (retries_left > 0):
|
||||||
|
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count))
|
logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count))
|
||||||
if (self.__worker_count > 0):
|
if (self.__worker_count > 0):
|
||||||
retries_left -= 1
|
retries_left -= 1
|
||||||
else:
|
else:
|
||||||
retries_left = 0
|
retries_left = 0
|
||||||
|
|
||||||
sleep(wait_for_workers_period)
|
sleep(wait_for_workers_period)
|
||||||
|
|
||||||
|
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
if (self.__worker_count > 0):
|
if (self.__worker_count > 0):
|
||||||
logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count)
|
logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count)
|
||||||
clean_shutdown = False
|
clean_shutdown = False
|
||||||
else:
|
else:
|
||||||
clean_shutdown = True
|
clean_shutdown = True
|
||||||
|
|
||||||
logger.info("shutdown complete")
|
logger.info("shutdown complete")
|
||||||
|
|
||||||
return clean_shutdown
|
return clean_shutdown
|
||||||
|
|
||||||
def punch_out(self):
|
def punch_out(self):
|
||||||
'''
|
'''
|
||||||
Called by worker to update worker count
|
Called by worker to update worker count
|
||||||
when the worker is shutting down
|
when the worker is shutting down
|
||||||
'''
|
'''
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
self.__worker_count -= 1
|
self.__worker_count -= 1
|
||||||
|
|
||||||
def __new_worker(self):
|
def __new_worker(self):
|
||||||
'''
|
'''
|
||||||
Adding a new worker thread to the thread pool
|
Adding a new worker thread to the thread pool
|
||||||
|
|
@ -214,58 +216,58 @@ class ThreadPool:
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
ThreadPool.Worker(self)
|
ThreadPool.Worker(self)
|
||||||
self.__worker_count += 1
|
self.__worker_count += 1
|
||||||
|
|
||||||
def worker_active(self):
|
def worker_active(self):
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
self.__active_worker_count = self.__active_worker_count + 1
|
self.__active_worker_count = self.__active_worker_count + 1
|
||||||
|
|
||||||
def worker_inactive(self):
|
def worker_inactive(self):
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
self.__active_worker_count = self.__active_worker_count - 1
|
self.__active_worker_count = self.__active_worker_count - 1
|
||||||
|
|
||||||
def add_job(self, function, args = None, return_callback=None):
|
def add_job(self, function, args = None, return_callback=None):
|
||||||
'''
|
'''
|
||||||
Put new job into queue
|
Put new job into queue
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if (not callable(function)):
|
if (not callable(function)):
|
||||||
raise TypeError("function is not a callable")
|
raise TypeError("function is not a callable")
|
||||||
if (not ( args == None or isinstance(args, list))):
|
if (not ( args == None or isinstance(args, list))):
|
||||||
raise TypeError("args is not a list")
|
raise TypeError("args is not a list")
|
||||||
if (not (return_callback == None or callable(return_callback))):
|
if (not (return_callback == None or callable(return_callback))):
|
||||||
raise TypeError("return_callback is not a callable")
|
raise TypeError("return_callback is not a callable")
|
||||||
|
|
||||||
if (args == None):
|
if (args == None):
|
||||||
args = []
|
args = []
|
||||||
|
|
||||||
job = ThreadPool.Job(function, args, return_callback)
|
job = ThreadPool.Job(function, args, return_callback)
|
||||||
|
|
||||||
with self.__worker_count_lock:
|
with self.__worker_count_lock:
|
||||||
if (self.__shutting_down):
|
if (self.__shutting_down):
|
||||||
raise AddJobException("ThreadPool is shutting down")
|
raise AddJobException("ThreadPool is shutting down")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
start_new_worker = False
|
start_new_worker = False
|
||||||
if (self.__worker_count < self.__max_workers):
|
if (self.__worker_count < self.__max_workers):
|
||||||
if (self.__active_worker_count == self.__worker_count):
|
if (self.__active_worker_count == self.__worker_count):
|
||||||
start_new_worker = True
|
start_new_worker = True
|
||||||
|
|
||||||
self.__jobs.put(job)
|
self.__jobs.put(job)
|
||||||
|
|
||||||
if (start_new_worker):
|
if (start_new_worker):
|
||||||
self.__new_worker()
|
self.__new_worker()
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
raise AddJobException("Could not add job")
|
raise AddJobException("Could not add job")
|
||||||
|
|
||||||
|
|
||||||
def get_job(self):
|
def get_job(self):
|
||||||
'''
|
'''
|
||||||
Retrieve next job from queue
|
Retrieve next job from queue
|
||||||
workers die (and should) when
|
workers die (and should) when
|
||||||
returning None
|
returning None
|
||||||
'''
|
'''
|
||||||
|
|
||||||
job = None
|
job = None
|
||||||
try:
|
try:
|
||||||
if (self.__kill_workers_after < 0):
|
if (self.__kill_workers_after < 0):
|
||||||
|
|
@ -276,5 +278,5 @@ class ThreadPool:
|
||||||
job = self.__jobs.get(True, self.__kill_workers_after)
|
job = self.__jobs.get(True, self.__kill_workers_after)
|
||||||
except Empty:
|
except Empty:
|
||||||
job = None
|
job = None
|
||||||
|
|
||||||
return job
|
return job
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
from ThreadPool import ThreadPool, ThreadPoolMixIn
|
from .ThreadPool import ThreadPool, ThreadPoolMixIn
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
from filesize import size
|
|
||||||
from filesize import traditional, alternative, verbose, iec, si
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
'''
|
|
||||||
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
|
|
||||||
9
src/trimage/tools.py
Normal file
9
src/trimage/tools.py
Normal file
|
|
@ -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)
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import errno
|
import errno
|
||||||
|
|
@ -11,12 +12,12 @@ from shutil import copy
|
||||||
from subprocess import call, PIPE
|
from subprocess import call, PIPE
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
from PyQt4.QtCore import *
|
from PyQt5.QtCore import *
|
||||||
from PyQt4.QtGui import *
|
from PyQt5.QtGui import *
|
||||||
from filesize import *
|
from PyQt5.QtWidgets import *
|
||||||
from imghdr import what as determinetype
|
|
||||||
|
|
||||||
from Queue import Queue
|
from tools import human_readable_size
|
||||||
|
from queue import Queue
|
||||||
from ThreadPool import ThreadPool
|
from ThreadPool import ThreadPool
|
||||||
from multiprocessing import cpu_count
|
from multiprocessing import cpu_count
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ from ui import Ui_trimage
|
||||||
VERSION = "1.0.5"
|
VERSION = "1.0.5"
|
||||||
|
|
||||||
|
|
||||||
class StartQT4(QMainWindow):
|
class StartQT5(QMainWindow):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
|
|
@ -40,7 +41,8 @@ class StartQT4(QMainWindow):
|
||||||
QCoreApplication.setOrganizationDomain("trimage.org")
|
QCoreApplication.setOrganizationDomain("trimage.org")
|
||||||
QCoreApplication.setApplicationName("Trimage")
|
QCoreApplication.setApplicationName("Trimage")
|
||||||
self.settings = QSettings()
|
self.settings = QSettings()
|
||||||
self.restoreGeometry(self.settings.value("geometry").toByteArray())
|
if self.settings.value("geometry"):
|
||||||
|
self.restoreGeometry(self.settings.value("geometry"))
|
||||||
|
|
||||||
# check if apps are installed
|
# check if apps are installed
|
||||||
if self.checkapps():
|
if self.checkapps():
|
||||||
|
|
@ -61,17 +63,12 @@ class StartQT4(QMainWindow):
|
||||||
self.thread = Worker()
|
self.thread = Worker()
|
||||||
|
|
||||||
# connect signals with slots
|
# connect signals with slots
|
||||||
QObject.connect(self.ui.addfiles, SIGNAL("clicked()"),
|
self.ui.addfiles.clicked.connect(self.file_dialog)
|
||||||
self.file_dialog)
|
self.ui.recompress.clicked.connect(self.recompress_files)
|
||||||
QObject.connect(self.ui.recompress, SIGNAL("clicked()"),
|
self.quit_shortcut.activated.connect(self.close)
|
||||||
self.recompress_files)
|
self.ui.processedfiles.drop_event_signal.connect(self.file_drop)
|
||||||
QObject.connect(self.quit_shortcut, SIGNAL("activated()"),
|
self.thread.finished.connect(self.update_table)
|
||||||
qApp, SLOT('quit()'))
|
self.thread.update_ui_signal.connect(self.update_table)
|
||||||
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")))
|
self.compressing_icon = QIcon(QPixmap(self.ui.get_image("pixmaps/compressing.gif")))
|
||||||
|
|
||||||
|
|
@ -103,14 +100,14 @@ class StartQT4(QMainWindow):
|
||||||
|
|
||||||
# make sure we quit after processing finished if using cli
|
# make sure we quit after processing finished if using cli
|
||||||
if options.filename or options.directory:
|
if options.filename or options.directory:
|
||||||
QObject.connect(self.thread, SIGNAL("finished()"), quit)
|
self.thread.finished.connect(quit)
|
||||||
self.cli = True
|
self.cli = True
|
||||||
|
|
||||||
# send to correct function
|
# send to correct function
|
||||||
if options.filename:
|
if options.filename:
|
||||||
self.file_from_cmd(options.filename.decode("utf-8"))
|
self.file_from_cmd(options.filename)
|
||||||
if options.directory:
|
if options.directory:
|
||||||
self.dir_from_cmd(options.directory.decode("utf-8"))
|
self.dir_from_cmd(options.directory)
|
||||||
|
|
||||||
self.verbose = options.verbose
|
self.verbose = options.verbose
|
||||||
|
|
||||||
|
|
@ -143,8 +140,9 @@ class StartQT4(QMainWindow):
|
||||||
def file_dialog(self):
|
def file_dialog(self):
|
||||||
"""Open a file dialog and send the selected images to compress_file."""
|
"""Open a file dialog and send the selected images to compress_file."""
|
||||||
fd = QFileDialog(self)
|
fd = QFileDialog(self)
|
||||||
fd.restoreState(self.settings.value("fdstate").toByteArray())
|
if (self.settings.value("fdstate")):
|
||||||
directory = self.settings.value("directory", QVariant("")).toString()
|
fd.restoreState(self.settings.value("fdstate"))
|
||||||
|
directory = self.settings.value("directory", QVariant(""))
|
||||||
fd.setDirectory(directory)
|
fd.setDirectory(directory)
|
||||||
|
|
||||||
images = fd.getOpenFileNames(self,
|
images = fd.getOpenFileNames(self,
|
||||||
|
|
@ -154,9 +152,10 @@ class StartQT4(QMainWindow):
|
||||||
"Image files (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG)")
|
"Image files (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG)")
|
||||||
|
|
||||||
self.settings.setValue("fdstate", QVariant(fd.saveState()))
|
self.settings.setValue("fdstate", QVariant(fd.saveState()))
|
||||||
|
images = images[0]
|
||||||
if images:
|
if images:
|
||||||
self.settings.setValue("directory", QVariant(path.dirname(unicode(images[0]))))
|
self.settings.setValue("directory", QVariant(path.dirname(images[0])))
|
||||||
self.delegator([unicode(fullpath) for fullpath in images])
|
self.delegator([fullpath for fullpath in images])
|
||||||
|
|
||||||
def recompress_files(self):
|
def recompress_files(self):
|
||||||
"""Send each file in the current file list to compress_file again."""
|
"""Send each file in the current file list to compress_file again."""
|
||||||
|
|
@ -173,15 +172,15 @@ class StartQT4(QMainWindow):
|
||||||
delegatorlist = []
|
delegatorlist = []
|
||||||
for fullpath in images:
|
for fullpath in images:
|
||||||
try: # recompress images already in the list
|
try: # recompress images already in the list
|
||||||
image = (i.image for i in self.imagelist
|
image = next(i.image for i in self.imagelist
|
||||||
if i.image.fullpath == fullpath).next()
|
if i.image.fullpath == fullpath)
|
||||||
if image.compressed:
|
if image.compressed:
|
||||||
image.reset()
|
image.reset()
|
||||||
image.recompression = True
|
image.recompression = True
|
||||||
delegatorlist.append(image)
|
delegatorlist.append(image)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
if not path.isdir(fullpath):
|
if not path.isdir(fullpath):
|
||||||
self. add_image(fullpath, delegatorlist)
|
self.add_image(fullpath, delegatorlist)
|
||||||
else:
|
else:
|
||||||
self.walk(fullpath, delegatorlist)
|
self.walk(fullpath, delegatorlist)
|
||||||
|
|
||||||
|
|
@ -214,7 +213,7 @@ class StartQT4(QMainWindow):
|
||||||
self.systemtray.trayIcon.setToolTip("Trimage image compressor (" + str(len(self.imagelist)) + " files)")
|
self.systemtray.trayIcon.setToolTip("Trimage image compressor (" + str(len(self.imagelist)) + " files)")
|
||||||
self.setWindowTitle("Trimage image compressor (" + str(len(self.imagelist)) + " files)")
|
self.setWindowTitle("Trimage image compressor (" + str(len(self.imagelist)) + " files)")
|
||||||
else:
|
else:
|
||||||
print >> sys.stderr, u"[error] %s not a supported image file and/or not writeable" % image.fullpath
|
print("[error] {} not a supported image file and/or not writable".format(image.fullpath), file=sys.stderr)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
UI Functions
|
UI Functions
|
||||||
|
|
@ -264,22 +263,22 @@ class StartQT4(QMainWindow):
|
||||||
retcode = self.safe_call("jpegoptim" + exe + " --version")
|
retcode = self.safe_call("jpegoptim" + exe + " --version")
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
status = True
|
status = True
|
||||||
sys.stderr.write("[error] please install jpegoptim")
|
print("[error] please install jpegoptim", file=sys.stderr)
|
||||||
|
|
||||||
retcode = self.safe_call("optipng" + exe + " -v")
|
retcode = self.safe_call("optipng" + exe + " -v")
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
status = True
|
status = True
|
||||||
sys.stderr.write("[error] please install optipng")
|
print("[error] please install optipng", file=sys.stderr)
|
||||||
|
|
||||||
retcode = self.safe_call("advpng" + exe + " --version")
|
retcode = self.safe_call("advpng" + exe + " --version")
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
status = True
|
status = True
|
||||||
sys.stderr.write("[error] please install advancecomp")
|
print("[error] please install advancecomp", file=sys.stderr)
|
||||||
|
|
||||||
retcode = self.safe_call("pngcrush" + exe + " -version")
|
retcode = self.safe_call("pngcrush" + exe + " -version")
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
status = True
|
status = True
|
||||||
sys.stderr.write("[error] please install pngcrush")
|
print("[error] please install pngcrush", file=sys.stderr)
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def safe_call(self, command):
|
def safe_call(self, command):
|
||||||
|
|
@ -287,7 +286,7 @@ class StartQT4(QMainWindow):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
return call(command, shell=True, stdout=PIPE)
|
return call(command, shell=True, stdout=PIPE)
|
||||||
except OSError, e:
|
except OSError as e:
|
||||||
if e.errno == errno.EINTR:
|
if e.errno == errno.EINTR:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
|
@ -357,9 +356,9 @@ class ImageRow:
|
||||||
self.image = image
|
self.image = image
|
||||||
d = {
|
d = {
|
||||||
'shortname': lambda i: self.statusStr() % i.shortname,
|
'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 "",
|
if i.compressed else "",
|
||||||
'newfilesizestr': lambda i: size(i.newfilesize, system=alternative)
|
'newfilesizestr': lambda i: human_readable_size(i.newfilesize)
|
||||||
if i.compressed else "",
|
if i.compressed else "",
|
||||||
'ratiostr': lambda i:
|
'ratiostr': lambda i:
|
||||||
"%.1f%%" % (100 - (float(i.newfilesize) / i.oldfilesize * 100))
|
"%.1f%%" % (100 - (float(i.newfilesize) / i.oldfilesize * 100))
|
||||||
|
|
@ -399,7 +398,9 @@ class Image:
|
||||||
self.reset()
|
self.reset()
|
||||||
self.fullpath = fullpath
|
self.fullpath = fullpath
|
||||||
if path.isfile(self.fullpath) and access(self.fullpath, WRITEABLE):
|
if path.isfile(self.fullpath) and access(self.fullpath, WRITEABLE):
|
||||||
self.filetype = determinetype(self.fullpath)
|
self.filetype = path.splitext(self.fullpath)[1][1:]
|
||||||
|
if self.filetype == "jpg":
|
||||||
|
self.filetype = "jpeg"
|
||||||
if self.filetype in ["jpeg", "png"]:
|
if self.filetype in ["jpeg", "png"]:
|
||||||
oldfile = QFileInfo(self.fullpath)
|
oldfile = QFileInfo(self.fullpath)
|
||||||
self.shortname = oldfile.fileName()
|
self.shortname = oldfile.fileName()
|
||||||
|
|
@ -407,15 +408,6 @@ class Image:
|
||||||
self.icon = QIcon(self.fullpath)
|
self.icon = QIcon(self.fullpath)
|
||||||
self.valid = True
|
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):
|
def reset(self):
|
||||||
self.failed = False
|
self.failed = False
|
||||||
self.compressed = False
|
self.compressed = False
|
||||||
|
|
@ -431,8 +423,8 @@ class Image:
|
||||||
self.compressing = True
|
self.compressing = True
|
||||||
exe = ".exe" if (sys.platform == "win32") else ""
|
exe = ".exe" if (sys.platform == "win32") else ""
|
||||||
runString = {
|
runString = {
|
||||||
"jpeg": u"jpegoptim" + exe + " -f --strip-all '%(file)s'",
|
"jpeg": "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'"
|
"png": "optipng" + exe + " -force -o7 '%(file)s'&&advpng" + exe + " -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'"
|
||||||
}
|
}
|
||||||
# Create a backup file
|
# Create a backup file
|
||||||
copy(self.fullpath, self.fullpath + '~')
|
copy(self.fullpath, self.fullpath + '~')
|
||||||
|
|
@ -461,6 +453,8 @@ class Image:
|
||||||
|
|
||||||
class Worker(QThread):
|
class Worker(QThread):
|
||||||
|
|
||||||
|
update_ui_signal = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QThread.__init__(self, parent)
|
QThread.__init__(self, parent)
|
||||||
self.toDisplay = Queue()
|
self.toDisplay = Queue()
|
||||||
|
|
@ -488,7 +482,7 @@ class Worker(QThread):
|
||||||
tp._ThreadPool__jobs.empty()):
|
tp._ThreadPool__jobs.empty()):
|
||||||
image = self.toDisplay.get()
|
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 not self.showapp and self.verbose: # we work via the commandline
|
||||||
if image.retcode == 0:
|
if image.retcode == 0:
|
||||||
|
|
@ -497,7 +491,7 @@ class Worker(QThread):
|
||||||
+ ir['oldfilesizestr'] + ", New Size: "
|
+ ir['oldfilesizestr'] + ", New Size: "
|
||||||
+ ir['newfilesizestr'] + ", Ratio: " + ir['ratiostr'])
|
+ ir['newfilesizestr'] + ", Ratio: " + ir['ratiostr'])
|
||||||
else:
|
else:
|
||||||
print >> sys.stderr, u"[error] %s could not be compressed" % image.fullpath
|
print("[error] {} could not be compressed".format(image.fullpath), file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
class Systray(QWidget):
|
class Systray(QWidget):
|
||||||
|
|
@ -511,15 +505,14 @@ class Systray(QWidget):
|
||||||
|
|
||||||
def createActions(self):
|
def createActions(self):
|
||||||
self.quitAction = QAction(self.tr("&Quit"), self)
|
self.quitAction = QAction(self.tr("&Quit"), self)
|
||||||
QObject.connect(self.quitAction, SIGNAL("triggered()"),
|
self.quitAction.triggered.connect(self.parent.close)
|
||||||
qApp, SLOT("quit()"))
|
|
||||||
|
|
||||||
self.addFiles = QAction(self.tr("&Add and compress"), self)
|
self.addFiles = QAction(self.tr("&Add and compress"), self)
|
||||||
icon = QIcon()
|
icon = QIcon()
|
||||||
icon.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/list-add.png"))),
|
icon.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/list-add.png"))),
|
||||||
QIcon.Normal, QIcon.Off)
|
QIcon.Normal, QIcon.Off)
|
||||||
self.addFiles.setIcon(icon)
|
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)
|
self.recompress = QAction(self.tr("&Recompress"), self)
|
||||||
icon2 = QIcon()
|
icon2 = QIcon()
|
||||||
|
|
@ -527,10 +520,11 @@ class Systray(QWidget):
|
||||||
QIcon.Normal, QIcon.Off)
|
QIcon.Normal, QIcon.Off)
|
||||||
self.recompress.setIcon(icon2)
|
self.recompress.setIcon(icon2)
|
||||||
self.recompress.setDisabled(True)
|
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)
|
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):
|
def createTrayIcon(self):
|
||||||
self.trayIconMenu = QMenu(self)
|
self.trayIconMenu = QMenu(self)
|
||||||
|
|
@ -550,7 +544,7 @@ class Systray(QWidget):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
myapp = StartQT4()
|
myapp = StartQT5()
|
||||||
|
|
||||||
if myapp.showapp:
|
if myapp.showapp:
|
||||||
myapp.show()
|
myapp.show()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
from PyQt4.QtCore import *
|
#!/usr/bin/env python3
|
||||||
from PyQt4.QtGui import *
|
|
||||||
|
from PyQt5.QtCore import *
|
||||||
|
from PyQt5.QtGui import *
|
||||||
|
from PyQt5.QtWidgets import *
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
class TrimageTableView(QTableView):
|
class TrimageTableView(QTableView):
|
||||||
|
|
||||||
|
drop_event_signal = pyqtSignal(list)
|
||||||
|
|
||||||
"""Init the table drop event."""
|
"""Init the table drop event."""
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(TrimageTableView, self).__init__(parent)
|
super(TrimageTableView, self).__init__(parent)
|
||||||
|
|
@ -21,12 +27,12 @@ class TrimageTableView(QTableView):
|
||||||
event.accept()
|
event.accept()
|
||||||
filelist = []
|
filelist = []
|
||||||
for url in event.mimeData().urls():
|
for url in event.mimeData().urls():
|
||||||
filelist.append(unicode(url.toLocalFile()))
|
filelist.append(url.toLocalFile())
|
||||||
|
|
||||||
self.emit(SIGNAL("fileDropEvent"), (filelist))
|
self.drop_event_signal.emit(filelist)
|
||||||
|
|
||||||
|
|
||||||
class Ui_trimage(object):
|
class Ui_trimage():
|
||||||
def get_image(self, image):
|
def get_image(self, image):
|
||||||
""" Get the correct link to the images used in the UI """
|
""" Get the correct link to the images used in the UI """
|
||||||
imagelink = path.join(path.dirname(path.dirname(path.realpath(__file__))), "trimage/" + image)
|
imagelink = path.join(path.dirname(path.dirname(path.realpath(__file__))), "trimage/" + image)
|
||||||
|
|
@ -44,7 +50,7 @@ class Ui_trimage(object):
|
||||||
self.centralwidget.setObjectName("centralwidget")
|
self.centralwidget.setObjectName("centralwidget")
|
||||||
|
|
||||||
self.gridLayout_2 = QGridLayout(self.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.setSpacing(0)
|
||||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||||
|
|
||||||
|
|
@ -60,7 +66,7 @@ class Ui_trimage(object):
|
||||||
|
|
||||||
self.verticalLayout = QVBoxLayout(self.widget)
|
self.verticalLayout = QVBoxLayout(self.widget)
|
||||||
self.verticalLayout.setSpacing(0)
|
self.verticalLayout.setSpacing(0)
|
||||||
self.verticalLayout.setMargin(0)
|
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.verticalLayout.setObjectName("verticalLayout")
|
self.verticalLayout.setObjectName("verticalLayout")
|
||||||
|
|
||||||
self.frame = QFrame(self.widget)
|
self.frame = QFrame(self.widget)
|
||||||
|
|
@ -68,12 +74,12 @@ class Ui_trimage(object):
|
||||||
|
|
||||||
self.verticalLayout_2 = QVBoxLayout(self.frame)
|
self.verticalLayout_2 = QVBoxLayout(self.frame)
|
||||||
self.verticalLayout_2.setSpacing(0)
|
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.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||||
|
|
||||||
self.horizontalLayout = QHBoxLayout()
|
self.horizontalLayout = QHBoxLayout()
|
||||||
self.horizontalLayout.setSpacing(0)
|
self.horizontalLayout.setSpacing(0)
|
||||||
self.horizontalLayout.setMargin(10)
|
self.horizontalLayout.setContentsMargins(10, 10, 10, 10)
|
||||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||||
|
|
||||||
self.addfiles = QPushButton(self.frame)
|
self.addfiles = QPushButton(self.frame)
|
||||||
|
|
@ -93,7 +99,7 @@ class Ui_trimage(object):
|
||||||
font.setPointSize(8)
|
font.setPointSize(8)
|
||||||
self.label.setFont(font)
|
self.label.setFont(font)
|
||||||
self.label.setFrameShadow(QFrame.Plain)
|
self.label.setFrameShadow(QFrame.Plain)
|
||||||
self.label.setMargin(1)
|
self.label.setContentsMargins(1, 1, 1, 1)
|
||||||
self.label.setIndent(10)
|
self.label.setIndent(10)
|
||||||
self.label.setObjectName("label")
|
self.label.setObjectName("label")
|
||||||
self.horizontalLayout.addWidget(self.label)
|
self.horizontalLayout.addWidget(self.label)
|
||||||
|
|
@ -143,24 +149,22 @@ class Ui_trimage(object):
|
||||||
def retranslateUi(self, trimage):
|
def retranslateUi(self, trimage):
|
||||||
""" Fill in the texts for all UI elements """
|
""" Fill in the texts for all UI elements """
|
||||||
trimage.setWindowTitle(QApplication.translate("trimage",
|
trimage.setWindowTitle(QApplication.translate("trimage",
|
||||||
"Trimage image compressor", None, QApplication.UnicodeUTF8))
|
"Trimage image compressor", None))
|
||||||
self.addfiles.setToolTip(QApplication.translate("trimage",
|
self.addfiles.setToolTip(QApplication.translate("trimage",
|
||||||
"Add file to the compression list", None,
|
"Add file to the compression list", None))
|
||||||
QApplication.UnicodeUTF8))
|
|
||||||
self.addfiles.setText(QApplication.translate("trimage",
|
self.addfiles.setText(QApplication.translate("trimage",
|
||||||
"&Add and compress", None, QApplication.UnicodeUTF8))
|
"&Add and compress", None))
|
||||||
self.addfiles.setShortcut(QApplication.translate("trimage",
|
self.addfiles.setShortcut(QApplication.translate("trimage",
|
||||||
"Alt+A", None, QApplication.UnicodeUTF8))
|
"Alt+A", None))
|
||||||
self.label.setText(QApplication.translate("trimage",
|
self.label.setText(QApplication.translate("trimage",
|
||||||
"Drag and drop images onto the table", None,
|
"Drag and drop images onto the table", None))
|
||||||
QApplication.UnicodeUTF8))
|
|
||||||
self.recompress.setToolTip(QApplication.translate("trimage",
|
self.recompress.setToolTip(QApplication.translate("trimage",
|
||||||
"Recompress all images", None, QApplication.UnicodeUTF8))
|
"Recompress all images", None))
|
||||||
self.recompress.setText(QApplication.translate("trimage",
|
self.recompress.setText(QApplication.translate("trimage",
|
||||||
"&Recompress", None, QApplication.UnicodeUTF8))
|
"&Recompress", None))
|
||||||
self.recompress.setShortcut(QApplication.translate("trimage",
|
self.recompress.setShortcut(QApplication.translate("trimage",
|
||||||
"Alt+R", None, QApplication.UnicodeUTF8))
|
"Alt+R", None))
|
||||||
self.processedfiles.setToolTip(QApplication.translate("trimage",
|
self.processedfiles.setToolTip(QApplication.translate("trimage",
|
||||||
"Drag files in here", None, QApplication.UnicodeUTF8))
|
"Drag files in here", None))
|
||||||
self.processedfiles.setWhatsThis(QApplication.translate("trimage",
|
self.processedfiles.setWhatsThis(QApplication.translate("trimage",
|
||||||
"Drag files in here", None, QApplication.UnicodeUTF8))
|
"Drag files in here", None))
|
||||||
|
|
|
||||||
3
trimage
3
trimage
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
#coding: utf-8
|
|
||||||
#
|
#
|
||||||
#Copyright (c) 2010 Kilian Valkhof, Paul Chaplin, Tarnay Kálmán
|
#Copyright (c) 2010 Kilian Valkhof, Paul Chaplin, Tarnay Kálmán
|
||||||
#
|
#
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue