Compare commits

...

183 commits

Author SHA1 Message Date
Kilian Valkhof
ad74684272
Merge pull request #83 from Mte90/patch-2
Fix incomplete code of #77
2021-03-10 14:41:44 +01:00
Daniele Scasciafratte
031adf6054
Fix incomplete code of #77
I forgot to change the various `%s` in the pull request with the rest of the function or it will be print "Compressing %s".
2021-03-10 12:58:46 +01:00
Kilian Valkhof
f35be9c750
Merge pull request #82 from Mte90/patch-1
Fix #77
2021-03-08 14:03:34 +01:00
Daniele Scasciafratte
636b5a9750
Fix #77
This fix https://github.com/Kilian/Trimage/issues/77
As debian user this fix the issue for me
2021-03-04 19:26:55 +01:00
Kilian Valkhof
c21089f97b
Merge pull request #74 from carsonreinke/fix-typeerror
Missing format specifier for arguments of image status
2020-09-30 13:21:24 +02:00
Kilian Valkhof
f37c8b6ffc
Merge pull request #76 from featherbear/master
Remove unreachable shutdown code
2020-09-30 13:19:37 +02:00
Andrew Wong
8af532f25e Remove unreachable shutdown code
* Implementing `__del__` is discouraged
  * Imports (i.e. `import logging`) are cleaned up, leading to NoneType related errors
* When running via GUI mode, the shutdown code is never reached; we can let Python's internal GC clean everything
2020-09-30 19:22:46 +10:00
Carson Reinke
493a0e18d2 Missing format specifier for arguments of image status
Fixes https://github.com/Kilian/Trimage/issues/73
2020-06-23 15:30:34 -04:00
Kilian Valkhof
224f6b3503
Merge pull request #71 from kalmi/macos
Add macOS instructions to website
2020-04-24 09:35:43 +02:00
Kálmán Tarnay
c3244e19a6 add macos instructions to website 2020-04-23 17:44:12 +02:00
Kilian Valkhof
269e052ba4
Update README.md 2019-12-20 16:51:10 +01:00
Kilian Valkhof
49272e0d95
Merge pull request #66 from hosiet/patch-1
Fix trimage.desktop grammar
2019-12-04 09:52:17 +01:00
Boyuan Yang
1342503b54
Fix trimage.desktop grammar
According to https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s04.html , Keywords should be split by semicolons, not commas.
2019-12-03 17:00:44 -05:00
Kilian Valkhof
0cbbfcdfe8
Merge pull request #64 from Huluti/remove-win32-support
Remove win32 bytes following d6118b5
2019-10-14 17:00:36 +02:00
Hugo Posnic
9d6edd3847 Remove win32 bytes following d6118b5 2019-10-03 17:05:33 +02:00
kilian
9bdd44a4e4 further refinement/updates of debian folder 2019-03-12 12:36:32 +01:00
kilian
8376c4a238 update version number to 1.0.6 in various places 2019-03-12 12:15:49 +01:00
kilian
54d4b74172 update debian folder 2019-03-12 12:03:15 +01:00
kilian
e3b5146977 update website 2019-03-12 11:49:45 +01:00
Kilian Valkhof
f2ff0448c4
Merge pull request #57 from Huluti/master
Fix #28 + Little changes to setup.py
2019-03-12 11:42:21 +01:00
Huluti
5f5757f575 Fix #28 2019-03-07 16:45:55 +01:00
Huluti
a3f417c43e setup.py: add a missing dependency in the long description 2019-03-07 16:10:19 +01:00
Huluti
d6118b520b Remove Windows options from setup.py 2019-03-07 16:09:21 +01:00
Huluti
f5ff57ff48 Just one return line fix 2019-03-07 15:59:05 +01:00
Kilian Valkhof
d8d207438e
Merge pull request #56 from Huluti/fix41
Fix #41
2019-03-05 09:19:00 +01:00
Huluti
27b22807bc Fix #41 2019-03-04 21:46:25 +01:00
Kilian Valkhof
891bc9b73f
Merge pull request #53 from Huluti/master
A fix and some cosmetic changes
2019-02-25 10:45:47 +01:00
Huluti
5553e7fabe Fix a bug introcuded in 13f43bf: allow extensions even in upper case 2019-02-23 20:58:02 +01:00
Huluti
9ea961e87c Remove some extra spaces 2019-02-23 20:54:27 +01:00
Huluti
c040b63595 Move two functions in the tools.py file 2019-02-23 20:48:49 +01:00
Huluti
b829b6ac5f Minor cosmetics 2019-02-22 21:23:37 +01:00
Kilian Valkhof
be7fe35ba9
Merge pull request #52 from Huluti/cleaning
Just some cleaning
2019-02-22 10:03:00 +01:00
Hugo Posnic
915a360a09 Uniformize comments and docstrings syntax 2019-02-21 16:37:03 +01:00
Hugo Posnic
06aad62cb6 Simpler check of dependencies 2019-02-21 16:34:07 +01:00
Hugo Posnic
fa94b546fb Reorder imports (PEP-8) 2019-02-21 16:25:15 +01:00
Kilian Valkhof
1ecff8b3d9
Merge pull request #50 from Huluti/simplify-structure
Simplify the structure
2019-02-21 16:01:58 +01:00
Kilian Valkhof
9ee9548d47
Merge pull request #49 from Huluti/update-readme
Update README
2019-02-21 16:00:24 +01:00
Hugo Posnic
4e702d3f3a Simplify the structure 2019-02-21 15:34:59 +01:00
Hugo Posnic
006256e08b Update README 2019-02-21 15:26:20 +01:00
Kilian Valkhof
117a1a36cc
Merge pull request #47 from Huluti/master
First try to adopt Python3 and PyQt5
2019-02-21 10:41:35 +01:00
Hugo Posnic
1e602b2a77 Remove the need to use the hurry.filesize package 2019-02-20 17:10:03 +01:00
Hugo Posnic
460161b6ee Update generators 2019-02-20 16:30:20 +01:00
Hugo Posnic
030684fe60 Fix two little errors 2019-02-20 16:21:09 +01:00
Hugo Posnic
b2713d5485 Update gitignore 2019-02-20 16:11:49 +01:00
Hugo Posnic
95512644b6 Update README 2019-02-20 15:38:19 +01:00
Hugo Posnic
fb1c463a03 Fix filetype after last commit 2019-02-20 15:37:35 +01:00
Hugo Posnic
d2c0a7905b Add an other check to avoid an other TypeError 2019-02-20 15:28:09 +01:00
Hugo Posnic
13f43bfa2f Simplify way to check if an image is valid
determinetype functions has some defaults
2019-02-20 15:27:45 +01:00
Hugo Posnic
f7531a1095 Replace sys.stderr.write by prints 2019-02-20 15:16:28 +01:00
Hugo Posnic
02a92463e5 Test geometry setting to avoid a TypeError 2019-02-20 14:49:00 +01:00
Hugo Posnic
c38cf92f25 Fix imports 2018-04-09 19:19:41 +02:00
Hugo Posnic
26878908c0 Ue Python3 shebangs 2018-04-09 19:17:39 +02:00
Hugo Posnic
42008757f2 Redesign todo 2017-11-26 11:53:11 +01:00
Hugo Posnic
8e415cb451 Update debian control file to Python 3 and Qt5 2017-11-25 17:08:49 +01:00
Hugo Posnic
23c3243209 Remove two remaining "u" chars 2017-11-19 17:48:13 +01:00
Hugo Posnic
3a19f3cef8 Don't need "(object)" for classes in Python3 2017-11-18 20:18:48 +01:00
Hugo Posnic
59b3d8c94d Python 3 use utf8 by default 2017-11-18 18:05:28 +01:00
Hugo Posnic
573dcab3d2 Remove decode methods (Python 3 is utf8 by default) 2017-11-18 18:04:15 +01:00
Hugo Posnic
d7a0f84f64 Adopt the new signal system for remaining items 2017-11-18 17:10:23 +01:00
Hugo Posnic
797cc433b3 Readme file in markdown 2017-11-18 16:40:05 +01:00
Hugo Posnic
461ae9fd57 Reintroduce missing changes 2017-11-18 16:25:49 +01:00
Hugo Posnic
648f78c139 Remove unuseful file anymore (due too reintroduced packages) 2017-11-18 16:20:21 +01:00
Hugo Posnic
352ba03d02 Revert to initial gitignore 2017-11-18 16:19:21 +01:00
Hugo Posnic
278ef269cf Reintroduce threadpool and filesize packages 2017-11-18 16:16:59 +01:00
Hugo Posnic
25836634af setup.py: change requires from pyqt4 to pyqt5 2017-11-18 16:12:44 +01:00
Hugo Posnic
a24ef15ff5 Fix bug introduced by the first commit with list of images 2017-11-18 15:50:51 +01:00
Hugo Posnic
063d2b8adb Replace some old signals 2017-11-18 15:45:04 +01:00
Hugo Posnic
420c945f94 Just add a return line after the shebang 2017-11-18 15:34:22 +01:00
Hugo Posnic
bc8c665d40 Remove unuseful unicode call 2017-11-18 15:32:09 +01:00
Hugo Posnic
e71c22b519 Improve the .gitignore 2017-11-18 15:27:02 +01:00
Hugo Posnic
69fb5c9c88 Move todo in root for more visibility and convert it to markdown 2017-11-18 15:25:11 +01:00
Hugo Posnic
623922d12d Port quit signal to new system 2017-11-18 15:16:57 +01:00
Hugo Posnic
a20f7a5beb Fix version in setup.py 2017-11-18 15:09:25 +01:00
Hugo Posnic
a3d9735ef8 Fix canceling open image 2017-11-18 15:06:47 +01:00
Hugo Posnic
cd80b8c386 Replace setMargin with setContentsMargins 2017-11-18 14:57:02 +01:00
Hugo Posnic
8eca530275 First try to adopt Python3 and PyQt5 2017-11-18 14:44:11 +01:00
Paul Chaplin
e47888e067 Added note that your EXIF will go away if you use Trimage. 2013-01-26 15:35:56 +00:00
Kilian Valkhof
8503c851b3 add ubuntu button to website 2012-06-15 12:51:30 +02:00
Kilian Valkhof
3b99c65a8c fix indentation error for non-systemtray environments
this patch by Daniel Clemente and fixes an indentation error
for non-systemtray environments.
2011-12-21 11:02:13 +01:00
Kilian Valkhof
a3bc8da5e6 update link to sencss 2011-11-06 19:36:00 +01:00
Kilian Valkhof
8a8ffa66ef add doc page, add it's author to the index.html,. fixes issue #17 2011-11-06 18:05:23 +01:00
Kilian Valkhof
f17cbf980a Add man page, by Kyrill Detinov 2011-07-17 23:35:32 +02:00
Kilian Valkhof
44171bc991 add links to opensuse 2011-06-01 20:02:59 +02:00
Kilian Valkhof
7bee242cf4 update website 2011-05-26 13:31:31 +02:00
Kilian Valkhof
20a4c4e4ec desktop file no longer incorrectly claims a GTK app. Fixed Debian bug #616293 2011-03-03 11:45:18 +01:00
Kilian Valkhof
8227aee846 Merge branch 'bistory-master' 2010-12-29 13:49:08 +01:00
Thomas Lété
6ca6082e19 Avoid recursion in version control repositories. 2010-12-23 10:54:55 -08:00
Kilian Valkhof
8c74e8b0df update debian stuff, add mandriva to site, add arch logo 2010-11-28 19:53:52 +01:00
Kilian
b9a10eed29 add debian instructions to trimage website 2010-11-23 23:32:03 +01:00
Kilian Valkhof
07f5dd0c83 add maveric as supported ubuntu distro 2010-09-26 15:45:13 +02:00
Kilian Valkhof
e460faca76 update to version 1.0.5, clean up code slightly, update website and requirements 2010-09-26 15:26:11 +02:00
Thomas Lété
4c034722c7 Changed the backup file from *.backup to *~ 2010-09-24 06:46:53 -07:00
Thomas
83ea31ed22 Huge performance improvement when adding a large number of files.
Added a check to avoid compression when the file is not writeable.
2010-09-23 06:03:59 -07:00
Thomas
b27fec1a02 Don't erase anymore files that are bigger than the original one. 2010-09-23 04:55:57 -07:00
Thomas
fbb92b153d Add the ability to drop entire directories. 2010-09-22 09:12:20 -07:00
Kilian Valkhof
bf26c6bb43 bump to version 1.0.4 2010-09-17 20:50:39 +02:00
Kilian Valkhof
9d25bc738b don't show the system tray icon if you're using the commandline 2010-09-17 20:37:52 +02:00
Kilian Valkhof
cdf9bca4fa add flattr button :) 2010-08-09 11:44:20 +02:00
Kilian Valkhof
966536bb9a build in some more checks against systray for osx 2010-06-27 12:22:08 +02:00
Kilian Valkhof
671f4163a8 update version to 1.0.3, update site and changelog 2010-06-12 14:10:34 +02:00
Kilian Valkhof
8a719be207 fix for environments without a system tray 2010-06-12 14:03:31 +02:00
Kilian Valkhof
f181fdcd00 fix spelling mistake in longdesc 2010-06-12 14:02:46 +02:00
Kilian Valkhof
646abfca85 switch to dh instead of cdbs 2010-06-12 13:54:42 +02:00
Kilian Valkhof
bb9754a24e add all versions to the changelog 2010-06-12 13:35:24 +02:00
Kilian Valkhof
d2a1c1e7d0 add license info for upstream as well 2010-06-12 13:28:04 +02:00
Kilian Valkhof
8715233259 add copyright info for filesize.py, update homepage link in control 2010-06-12 13:23:08 +02:00
Kilian Valkhof
454d5e6d9f switch to python-support 2010-06-03 19:33:04 +02:00
Kilian Valkhof
34e4f6581f update debian package rules 2010-05-31 20:11:52 +02:00
Kilian Valkhof
83d5aebe66 update arch linux instructions 2010-05-31 19:09:00 +02:00
Kilian Valkhof
ee102f2289 update version number on site 2010-05-31 19:08:36 +02:00
Kilian Valkhof
c243524eaa update version to 1.0.2 2010-05-31 19:01:22 +02:00
Kilian Valkhof
e4a7a1a254 fix qsettings for qt4.4 2010-05-31 14:33:54 +02:00
Kilian
2596905266 save file dialog state, directory, and set app info for settings 2010-05-31 13:02:15 +02:00
Kilian
de35d5967e save geometry on quit and set on launch 2010-05-31 11:25:06 +02:00
Kilian
ea4350661a update todo 2010-05-30 20:21:53 +02:00
Kilian
52ccd5a1de add seperators to the tray icon menu 2010-05-30 20:20:04 +02:00
Kilian Valkhof
c9ae552994 bump version 2010-05-26 15:21:25 +02:00
Kilian Valkhof
cf37894afc remove the BOM from trimage to fix error in lucid 2010-05-26 15:19:56 +02:00
Kilian Valkhof
8e8cac3390 update site and changelog 2010-05-21 13:02:39 +02:00
Kilian Valkhof
2c2ac98b5d Merge branch 'master' of git://github.com/kalmi/Trimage into development 2010-04-27 11:27:51 +02:00
Kilian Valkhof
407806f187 update todo 2010-04-27 11:27:40 +02:00
Kálmán Tarnay
54680e1bc6 chmod +x trimage src/trimage/trimage.py 2010-04-26 17:36:30 +02:00
Kálmán Tarnay
4c5caf0d9c Merge remote branch 'development/development' 2010-04-25 21:13:47 +02:00
Kilian Valkhof
f7988ab5bd get directory ready for settings 2010-04-23 05:41:05 +08:00
Paul Chaplin
d03c0dfbd8 Added Unicode BOM to file to declare encoding; Python assumes ASCII and chokes unless told. 2010-04-23 05:40:10 +08:00
Kilian Valkhof
c6cb2dc321 update changelog 2010-04-23 05:40:09 +08:00
Kilian Valkhof
1be7cc83af add the total number of files to the title and the systray tooltip 2010-04-21 18:20:01 +02:00
Kilian Valkhof
45410e7b4b update todo, pep8 changes 2010-04-21 18:09:25 +02:00
Kilian Valkhof
8a89a44c9e get directory ready for settings 2010-04-21 17:49:14 +02:00
Kilian Valkhof
7e8a0b7cd6 add systray tooltip 2010-04-21 17:31:08 +02:00
Kilian Valkhof
a0edd4f003 implement a show/hide function in the systray menu 2010-04-21 17:23:44 +02:00
Kilian Valkhof
ba0bb856d2 disable recompress systray button 2010-04-21 14:05:53 +02:00
Kilian Valkhof
773efc6870 implement a system tray with basic functionality 2010-04-21 13:28:52 +02:00
Kilian Valkhof
7cf707d5a3 Merge branch 'master' of git://github.com/kalmi/Trimage
Conflicts:
	src/trimage/trimage.py
2010-04-21 12:23:38 +02:00
Kálmán Tarnay
a936903b00 delete now unused hurry.filesize (I forgot it in the previous commit) 2010-04-20 23:42:00 +02:00
Kálmán Tarnay
d282bcf8e9 py2exe support
Usage: setup.py py2exe
You must be able to start trimage.py before even attempting to use py2exe.
It generates a standalone executable.
That standalone exacutable starts up only if the following files can be found on the path: jpegoptim.exe optipng.exe advpng.exe (they must return 0)
Compression does not work yet and the Windows versions of these tools are a lot slower than on Linux. The binaries I found were pretty old. They should most probably be recompiled to make use of new cpu features. Or we should find other tools.

trimage.hurry.filename -> trimage.filename
because py2exe couldn't deal with it
2010-04-20 23:29:24 +02:00
Kilian Valkhof
36644c5bf4 Merge branch 'master' of git://github.com/kalmi/Trimage 2010-04-12 18:25:24 +02:00
Kálmán Tarnay
464337d033 removed "check for double files when adding" 2010-04-12 03:59:58 +02:00
Kálmán Tarnay
cdd11c92ee ThreadPool is a module now and it is in setup.py 2010-04-12 03:53:31 +02:00
Kilian Valkhof
348f97fabc no multiprocessing, no subversion bump 2010-04-05 15:21:54 +02:00
Paul Chaplin
8e781f5409 Added Unicode BOM to file to declare encoding; Python assumes ASCII and chokes unless told. 2010-04-02 17:47:07 +01:00
Kilian Valkhof
e77f6e5a59 Merge branch 'parallel' 2010-04-02 15:10:50 +02:00
Kilian Valkhof
451269d960 update changelog 2010-04-02 15:10:44 +02:00
Kilian Valkhof
1106ca482c pep8 rewriting, version update, todo editing 2010-04-02 13:51:19 +02:00
Kilian Valkhof
0f5092e10b Merge branch 'master' of git://github.com/kalmi/Trimage into parallel
Conflicts:
	src/trimage/trimage.py
2010-04-02 12:57:59 +02:00
Kálmán Tarnay
29be211a9d license 2010-04-02 07:27:16 +02:00
Kálmán Tarnay
51691351d3 swapped my first name and last name 2010-04-02 07:25:10 +02:00
Kálmán Tarnay
cf9d98027c changed hint text for recompress button to indicate that it recompresses all 2010-04-02 07:24:00 +02:00
Kálmán Tarnay
97956d7e14 removed multiprocessing from todo 2010-04-02 07:22:50 +02:00
Kálmán Tarnay
5782698dda Lots of fixes/changes there
- ImageRow
 - Recompress fixed
 - Adding a file again recompresses it
 - Status indicators
 - cli working again
 - more crash resistant
2010-04-02 07:18:18 +02:00
Kilian Valkhof
a5da8a9a58 Merge branch 'parallel' of git://github.com/kalmi/Trimage into parallel 2010-04-01 13:13:44 +02:00
Kálmán Tarnay
2c9599813a temporary workaround for pythonthreadpool bug:
http://code.google.com/p/pythonthreadpool/issues/detail?id=5
2010-04-01 00:41:12 +02:00
Kálmán Tarnay
305198e09e ThreadPool.py r15
bugfix release from ThreadPool.py's author
http://code.google.com/p/pythonthreadpool/issues/detail?id=4
2010-03-31 22:54:56 +02:00
Kilian Valkhof
c9126ae196 compress image, remove unneeded code, add more docstrings 2010-03-31 17:44:29 +02:00
Kálmán Tarnay
a3a31a7e93 Parallel processing 2010-03-30 02:21:00 +02:00
Kálmán Tarnay
ecfc4af6a3 "and" relationship between the two png compressor commands 2010-03-30 00:26:29 +02:00
Kálmán Tarnay
b0b0dd9244 OOP Image 2010-03-29 23:05:39 +02:00
Kálmán Tarnay
9fafa54f2e Tasks can no longer be lost
How to reproduce:
   - add 2 images
   - before any of them finishes add 2 more images
   - some of them will never finish
Cli always exits nicely (without ugly sleep)
2010-03-29 16:47:54 +02:00
Kálmán Tarnay
a89a8a3302 Compressed the images of the website with Trimage 2010-03-29 14:21:50 +02:00
Kilian Valkhof
022df47501 update docstrings 2010-03-29 13:15:06 +02:00
Kilian Valkhof
cbd4d76b6e fix file dialog error with wrong type, update version 2010-03-29 13:09:24 +02:00
Kilian Valkhof
591536fa9e Merge branch 'master' of git://github.com/kalmi/Trimage 2010-03-29 12:46:45 +02:00
Kilian Valkhof
b26ff08e96 spelling error 2010-03-29 12:45:56 +02:00
Kálmán Tarnay
1862f6f019 - use imghdr module to determine filetype instead of extension
- (The imghdr module is in the standard library,
     so this creates no extra dependency.)
- store filenames in unicode internally
  - got rid of QString and QStringList
  - got rid of unicodize
- fix crash if directory didn't end with slash
- added time.sleep(0.1) before quit inside Worker.run
  to avoid a segfault when no images were processed
  (For example an empty directory was passed to trimage)
  (This segfault was present even before I touched anything)
  (A better "fix" would be not even initializing the GUI when
   there is no need)
2010-03-29 04:11:05 +02:00
Kilian Valkhof
a506c0fee4 fix handling of unicode characters in filenames, bump version to 1.0.0b2 2010-03-28 16:11:50 +02:00
Kilian Valkhof
e382b579ec fix typo for mac OSX version 2010-03-24 13:13:13 +01:00
Kilian Valkhof
19308292c0 implement Menno's fix for Mac OSX 2010-03-24 12:51:26 +01:00
Kilian Valkhof
70181f97ef update website 2010-03-24 11:51:02 +01:00
Kilian Valkhof
8dcec263ff massive overhaul of directory to make it work nicer with .deb generation 2010-03-23 20:48:17 +01:00
Kilian Valkhof
41672b8a39 update desktop file 2010-03-23 18:19:43 +01:00
Kilian Valkhof
c797699ff7 install in share instead of local/share 2010-03-23 17:57:44 +01:00
Kilian Valkhof
a24d69a178 add cfg file and desktop file 2010-03-23 17:46:04 +01:00
Kilian Valkhof
7efecb7b89 work in progress 2010-03-23 17:45:51 +01:00
Kilian Valkhof
3f26595198 show images as well 2010-03-23 15:57:54 +01:00
Kilian Valkhof
d79a5c498b install trimage as module 2010-03-23 15:23:50 +01:00
Kilian Valkhof
6a00b2e25c move runner to own folder 2010-03-23 14:59:33 +01:00
Kilian Valkhof
e96327edbd add a runner file 2010-03-23 14:56:30 +01:00
Kilian Valkhof
27d3fd782c more setup tweaks 2010-03-23 14:45:24 +01:00
Kilian Valkhof
9826e6cd97 more setup work 2010-03-23 14:34:52 +01:00
Kilian Valkhof
bf763ec335 more setup tweaks 2010-03-23 14:21:01 +01:00
Kilian Valkhof
c0dceb88ad more work in progress on installable trimage 2010-03-23 14:00:09 +01:00
Kilian Valkhof
b86c5aa8cd work in progress of creating an installable version 2010-03-23 13:09:52 +01:00
Kilian Valkhof
a283ce8481 resize icon 2010-03-22 22:41:04 +01:00
48 changed files with 1865 additions and 716 deletions

116
.gitignore vendored
View file

@ -1,2 +1,116 @@
*.pyc
# 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/

3
MANIFEST.in Normal file
View file

@ -0,0 +1,3 @@
include COPYING MANIFEST MANIFEST.in README.md bin/trimage
recursive-include desktop *.svg *.desktop
recursive-include trimage/ *.py *.png

44
README.md Normal file
View file

@ -0,0 +1,44 @@
### Made by [@kilianvalkhof](https://twitter.com/kilianvalkhof)
#### Other projects:
- 💻 [Polypane](https://polypane.app) - Develop responsive websites and apps twice as fast on multiple screens at once
- 🖌️ [Superposition](https://superposition.design) - Kickstart your design system by extracting design tokens from your website
- 🗒️ [FromScratch](https://fromscratch.rocks) - A smart but simple autosaving scratchpad
---
# Trimage image compressor
A cross-platform tool for optimizing PNG and JPG files.
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.
## Installation instructions
Visit [Trimage.org](http://trimage.org) to install Trimage as a package.
## Building instructions
### Prerequisites
- PyQt5
- advpng
- jpegoptim
- optipng
- pngcrush
### Build from source
Build and install by running:
python setup.py build
sudo python setup.py install

View file

@ -1,7 +0,0 @@
h2. 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 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.
Visit "Trimage.org":http://trimage.org for more information

17
TODO.md Normal file
View 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

23
bin/trimage Normal file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env python3
#
#Copyright (c) 2010 Kilian Valkhof, Paul Chaplin, Tarnay Kálmán
#
#Permission is hereby granted, free of charge, to any person
#obtaining a copy of this software and associated documentation
#files (the "Software"), to deal in the Software without
#restriction, including without limitation the rights to use,
#copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the
#Software is furnished to do so, subject to the following
#conditions:
#
#The above copyright notice and this permission notice shall be
#included in all copies or substantial portions of the Software.
import os, sys
import subprocess
import trimage
if __name__ == "__main__":
path = os.path.join(os.path.dirname(trimage.__file__), "trimage.py")
subprocess.call([sys.executable, path] + sys.argv[1:])

80
debian/changelog vendored Normal file
View file

@ -0,0 +1,80 @@
trimage (1.0.6-0ubuntu1) bionic; urgency=low
* Migrate to python 3 and python-qt 5
* fix bug with hanging thread
* remove hurry.filesize package
-- Kilian Valkhof <kilian@kilianvalkhof.com> Tue, 12 Mar 2019 11:54:00 +0200
trimage (1.0.5-0ubuntu1) jaunty; urgency=low
* prevent images from becoming larger after recompression
* prevent images from being written when not writeable
* allow dragging of entire directories
* performance increases for large numbers of files
-- Kilian Valkhof <kilian@kilianvalkhof.com> Sun, 26 Sep 2010 15:24:04 +0200
trimage (1.0.4-0ubuntu1) jaunty; urgency=low
* prevent systemtray from starting if a CLI compression is done
* preliminary OSX work
-- Kilian Valkhof <kilian@kilianvalkhof.com> Fri, 17 Sep 2010 20:45:16 +0200
trimage (1.0.3-0ubuntu1) jaunty; urgency=low
* fix for environments without supported system tray
* add copyright info for filesize.py
* update various debian configurations
-- Kilian Valkhof <kilian@kilianvalkhof.com> Sat, 12 Jun 2010 14:04:35 +0200
trimage (1.0.2-0ubuntu1) jaunty; urgency=low
* save window geometry and file directory upon closing
* prettier systray menu
* Switch to dpkg-source 3.0(quilt) format
-- Kilian Valkhof <kilian@kilianvalkhof.com> Thu, 03 Jun 2010 13:01:30 +0200
trimage (1.0.1b2-0ubuntu1) jaunty; urgency=low
* use a threadpool for images
* more robust file handling
* re-adding images now results in recompressing them
* compressing message now shows filename
* wider array of status messages in the table
* add a notification area icon
* remove BOM
-- Kilian Valkhof <kilian@kdesktop> Mon, 29 Mar 2010 13:08:18 +0200
trimage (1.0.1b-0ubuntu1) jaunty; urgency=low
* use a threadpool for images
* more robust file handling
* re-adding images now results in recompressing them
* compressing message now shows filename
* wider array of status messages in the table
* add a notification area icon
-- Kilian Valkhof <kilian@kdesktop> Mon, 29 Mar 2010 13:08:18 +0200
trimage (1.0.0b3-0ubuntu1) jaunty; urgency=low
* better image filetype determination
* use unicode strings everywhere
* fix bug where Trimage would crash if a directory didn't end in a slash
* avoid a segfault when parsing empty directories
* ../../debian/changelog
-- Kilian Valkhof <kilian@kdesktop> Mon, 29 Mar 2010 13:08:18 +0200
trimage (1.0.0b2-0ubuntu1) jaunty; urgency=low
* correct parsing of unicode characters in filenames
* changelog
-- Kilian Valkhof <kilian@kdesktop> Sun, 28 Mar 2010 16:09:45 +0200
trimage (1.0.0b-0ubuntu1) jaunty; urgency=low
* Trimage image compressor
-- Kilian Valkhof <help@trimage.org> Tue, 23 Mar 2010 20:18:17 +0100

1
debian/compat vendored Normal file
View file

@ -0,0 +1 @@
11

18
debian/control vendored Normal file
View file

@ -0,0 +1,18 @@
Source: trimage
Section: graphics
Priority: optional
Maintainer: Kilian Valkhof <kilian@kilianvalkhof.com>
Build-Depends: debhelper (>=7), python3
Standards-Version: 4.3.0
Homepage: http://trimage.org
Package: trimage
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, python-pyqt5 (>=5.7), optipng (>=0.6.2.1), advancecomp (>=1.15), jpegoptim (>=1.2.2), pngcrush (>=1.6.7)
Description: GUI and command-line interface to optimize image files
Trimage is a cross-platform GUI and command-line interface to optimize image
files via optipng, advpng, pngcrush and jpegoptim, depending on the filetype
(currently, PNG and JPG files are supported). 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.

212
debian/copyright vendored Normal file
View file

@ -0,0 +1,212 @@
This package was debianized by:
Kilian Valkhof <help@trimage.org> on Tue, 23 Mar 2010 20:18:17 +0100
Upstream Author:
Kilian Valkhof
Copyright:
Copyright (C) 2010 Kilian Valkhof, Paul Chaplin
License:
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
The Debian packaging is:
Copyright (C) 2010 Kilian Valkhof, Paul Chaplin
and is licensed under the MIT license, see above.
ThreadPool is:
Copyright (c) Morten Holdflod Moeller
License:
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

1
debian/docs vendored Normal file
View file

@ -0,0 +1 @@
README

3
debian/rules vendored Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/make -f
%:
dh $@

2
debian/source/format vendored Normal file
View file

@ -0,0 +1,2 @@
3.0 (quilt)

10
desktop/trimage.desktop Normal file
View file

@ -0,0 +1,10 @@
[Desktop Entry]
Name=Trimage image compressor
Comment=A cross-platform tool for optimizing PNG and JPG files.
Terminal=false
Icon=trimage
Type=Application
Exec=trimage
Categories=Application;Qt;Graphics;
StartupNotify=true
Keywords=compression;compressor;images;jpg;jpeg;png;web;

View file

@ -9,13 +9,14 @@
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
width="524.81012"
height="541.92767"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="logo-trimage.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.0">
<defs
id="defs4">
<linearGradient
@ -113,7 +114,7 @@
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="299.62219"
inkscape:cy="722.03051"
inkscape:cy="479.5939"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
@ -135,10 +136,11 @@
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
id="layer1"
transform="translate(-72.611421,-111.20599)">
<path
sodipodi:type="star"
style="fill:url(#linearGradient3183);fill-opacity:1;stroke:none;stroke-width:2.20967632;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:url(#linearGradient3183);fill-opacity:1;stroke:none;stroke-width:2.20967627;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3155"
sodipodi:sides="3"
sodipodi:cx="11.428571"
@ -157,7 +159,7 @@
inkscape:export-ydpi="11.166932" />
<path
sodipodi:type="star"
style="fill:#ffffff;fill-opacity:0.45833333999999998;stroke:none;stroke-width:3.01696083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:0.45833333;stroke:none;stroke-width:3.01696086;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3157"
sodipodi:sides="3"
sodipodi:cx="11.428571"
@ -176,7 +178,7 @@
inkscape:export-ydpi="11.166932" />
<path
sodipodi:type="star"
style="fill:#ffffff;fill-opacity:0.62500000000000000;stroke:none;stroke-width:4.66924454;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:0.625;stroke:none;stroke-width:4.66924477;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3159"
sodipodi:sides="3"
sodipodi:cx="11.428571"
@ -207,14 +209,14 @@
sodipodi:cx="11.428571"
sodipodi:sides="3"
id="path3185"
style="fill:url(#radialGradient3187);fill-opacity:1;stroke:none;stroke-width:2.20967632;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:url(#radialGradient3187);fill-opacity:1;stroke:none;stroke-width:2.20967627;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:type="star"
inkscape:export-filename="/home/kilian/workspace/trimage/trimage-icon.png"
inkscape:export-xdpi="11.166932"
inkscape:export-ydpi="11.166932" />
<path
sodipodi:type="star"
style="fill:url(#radialGradient3199);fill-opacity:1;stroke:#454545;stroke-width:8.28628619;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
style="fill:url(#radialGradient3199);fill-opacity:1;stroke:#454545;stroke-width:8.28628635;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3197"
sodipodi:sides="3"
sodipodi:cx="11.428571"

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 B

46
doc/trimage.1 Normal file
View file

@ -0,0 +1,46 @@
.\" Copyright (C) 2011 Kyrill Detinov <lazy.kent@opensuse.org>
.\"
.\" This manual page is distributed under the terms
.\" of the GNU Free Documentation License version 1.3.
.\"
.TH TRIMAGE "1" "2019-03-12" "trimage 1.0.6" "User Commands"
.SH NAME
trimage \- losslessly optimizing png and jpeg liles
.SH SYNOPSIS
.B trimage
.RI [ options ]
.SH DESCRIPTION
Front\-end to compress png and jpeg images via optipng, advpng, pngcrush
and jpegoptim.
.SH OPTIONS
.TP
\fB\-d\fI directory\fR, \fB\-\-directory\fR=\fIdirectory\fR
Compresses images in directory.
.TP
\fB\-f\fI filename\fR, \fB\-\-file\fR=\fIfilename\fR
Compresses image.
.TP
\fB\-h\fR, \fB\-\-help\fR
Show help message.
.TP
\fB\-q\fR, \fB\-\-quiet\fR
Quiet mode.
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Verbose mode (default).
.TP
\fB\-\-version\fR
Show program version number.
.SH "SEE ALSO"
.BR advpng (1),
.BR jpegoptim (1),
.BR opt-png (1),
.BR opt-jpg (1),
.BR pngcrush (1),
.BR pngrecolor (1),
.BR pngstrip (1)

View file

@ -1,7 +0,0 @@
# this is a namespace package
try:
import pkg_resources
pkg_resources.declare_namespace(__name__)
except ImportError:
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)

View file

@ -1,47 +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'

View file

@ -1,4 +0,0 @@
from hurry.filesize.filesize import size
from hurry.filesize.filesize import traditional, alternative, verbose, iec, si

View file

@ -1,110 +0,0 @@
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

View file

@ -1,7 +0,0 @@
import unittest, doctest
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('README.txt'),
doctest.DocTestSuite('hurry.filesize.filesize'),
))

237
resources/trimage.svg Normal file
View file

@ -0,0 +1,237 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="524.81012"
height="541.92767"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="logo-trimage.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.0">
<defs
id="defs4">
<linearGradient
id="linearGradient3207">
<stop
style="stop-color:#ffffff;stop-opacity:0.51401869"
offset="0"
id="stop3209" />
<stop
style="stop-color:#76b9fb;stop-opacity:0;"
offset="1"
id="stop3211" />
</linearGradient>
<linearGradient
id="linearGradient3189">
<stop
id="stop3191"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3193"
offset="1"
style="stop-color:#76b9fb;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3177">
<stop
style="stop-color:#2166ce;stop-opacity:1;"
offset="0"
id="stop3179" />
<stop
style="stop-color:#76b9fb;stop-opacity:1;"
offset="1"
id="stop3181" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3177"
id="linearGradient3183"
x1="-113.18141"
y1="94.068687"
x2="87.293686"
y2="196.29851"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3189"
id="radialGradient3187"
cx="-65.175232"
cy="257.47021"
fx="-65.175232"
fy="257.47021"
r="147.35561"
gradientTransform="matrix(0.9973701,7.2477038e-2,-8.1815483e-2,1.1258782,20.893644,-31.286202)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3189"
id="radialGradient3199"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.5134757,2.4437577e-2,-2.7551034e-2,0.5788946,65.578572,65.206227)"
cx="111.03341"
cy="166.65665"
fx="111.03341"
fy="166.65665"
r="147.35561" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3207"
id="radialGradient3205"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.5552136,-0.6613875,0.7169659,0.6018699,37.072783,80.283257)"
cx="56.787258"
cy="73.974876"
fx="56.787258"
fy="73.974876"
r="147.35561" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="299.62219"
inkscape:cy="479.5939"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1180"
inkscape:window-x="0"
inkscape:window-y="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-72.611421,-111.20599)">
<path
sodipodi:type="star"
style="fill:url(#linearGradient3183);fill-opacity:1;stroke:none;stroke-width:2.20967627;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3155"
sodipodi:sides="3"
sodipodi:cx="11.428571"
sodipodi:cy="175.21933"
sodipodi:r1="168.57143"
sodipodi:r2="84.285713"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:flatsided="true"
inkscape:rounded="0.11"
inkscape:randomized="0"
d="M 11.428575,343.79076 C -20.688595,343.79076 -150.61715,118.74791 -134.55857,90.933621 C -118.49998,63.119335 141.35712,63.119328 157.41571,90.933613 C 173.47429,118.7479 43.545746,343.79076 11.428575,343.79076 z"
transform="matrix(1.385337,-1.1652199,1.1652199,1.385337,160.77379,177.08667)"
inkscape:export-filename="/home/kilian/workspace/trimage/trimage-icon.png"
inkscape:export-xdpi="11.166932"
inkscape:export-ydpi="11.166932" />
<path
sodipodi:type="star"
style="fill:#ffffff;fill-opacity:0.45833333;stroke:none;stroke-width:3.01696086;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3157"
sodipodi:sides="3"
sodipodi:cx="11.428571"
sodipodi:cy="175.21933"
sodipodi:r1="168.57143"
sodipodi:r2="84.285713"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:flatsided="true"
inkscape:rounded="0.11"
inkscape:randomized="0"
d="M 11.428575,343.79076 C -20.688595,343.79076 -150.61715,118.74791 -134.55857,90.933621 C -118.49998,63.119335 141.35712,63.119328 157.41571,90.933613 C 173.47429,118.7479 43.545746,343.79076 11.428575,343.79076 z"
transform="matrix(1.0146457,-0.853428,0.853428,1.0146457,272.20143,300.96357)"
inkscape:export-filename="/home/kilian/workspace/trimage/trimage-icon.png"
inkscape:export-xdpi="11.166932"
inkscape:export-ydpi="11.166932" />
<path
sodipodi:type="star"
style="fill:#ffffff;fill-opacity:0.625;stroke:none;stroke-width:4.66924477;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3159"
sodipodi:sides="3"
sodipodi:cx="11.428571"
sodipodi:cy="175.21933"
sodipodi:r1="168.57143"
sodipodi:r2="84.285713"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:flatsided="true"
inkscape:rounded="0.11"
inkscape:randomized="0"
d="M 11.428575,343.79076 C -20.688595,343.79076 -150.61715,118.74791 -134.55857,90.933621 C -118.49998,63.119335 141.35712,63.119328 157.41571,90.933613 C 173.47429,118.7479 43.545746,343.79076 11.428575,343.79076 z"
transform="matrix(0.6555978,-0.5514294,0.5514294,0.6555978,380.12917,420.94953)"
inkscape:export-filename="/home/kilian/workspace/trimage/trimage-icon.png"
inkscape:export-xdpi="11.166932"
inkscape:export-ydpi="11.166932" />
<path
transform="matrix(1.385337,-1.1652199,1.1652199,1.385337,160.77379,177.08667)"
d="M 11.428575,343.79076 C -20.688595,343.79076 -150.61715,118.74791 -134.55857,90.933621 C -118.49998,63.119335 141.35712,63.119328 157.41571,90.933613 C 173.47429,118.7479 43.545746,343.79076 11.428575,343.79076 z"
inkscape:randomized="0"
inkscape:rounded="0.11"
inkscape:flatsided="true"
sodipodi:arg2="2.6179939"
sodipodi:arg1="1.5707963"
sodipodi:r2="84.285713"
sodipodi:r1="168.57143"
sodipodi:cy="175.21933"
sodipodi:cx="11.428571"
sodipodi:sides="3"
id="path3185"
style="fill:url(#radialGradient3187);fill-opacity:1;stroke:none;stroke-width:2.20967627;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:type="star"
inkscape:export-filename="/home/kilian/workspace/trimage/trimage-icon.png"
inkscape:export-xdpi="11.166932"
inkscape:export-ydpi="11.166932" />
<path
sodipodi:type="star"
style="fill:url(#radialGradient3199);fill-opacity:1;stroke:#454545;stroke-width:8.28628635;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3197"
sodipodi:sides="3"
sodipodi:cx="11.428571"
sodipodi:cy="175.21933"
sodipodi:r1="168.57143"
sodipodi:r2="84.285713"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:flatsided="true"
inkscape:rounded="0.11"
inkscape:randomized="0"
d="M 11.428575,343.79076 C -20.688595,343.79076 -150.61715,118.74791 -134.55857,90.933621 C -118.49998,63.119335 141.35712,63.119328 157.41571,90.933613 C 173.47429,118.7479 43.545746,343.79076 11.428575,343.79076 z"
transform="matrix(1.385337,-1.1652199,1.1652199,1.385337,160.77379,177.08667)"
inkscape:export-filename="/home/kilian/workspace/trimage/trimage-icon.png"
inkscape:export-xdpi="11.166932"
inkscape:export-ydpi="11.166932" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View file

@ -127,7 +127,7 @@
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="toolTip">
<string>Recompress selected images</string>
<string>Recompress all images</string>
</property>
<property name="text">
<string>&amp;Recompress</string>

22
setup.py Normal file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python3
from distutils.core import setup
setup(name = "trimage",
version = "1.0.6",
description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files",
author = "Kilian Valkhof, Paul Chaplin",
author_email = "help@trimage.org",
url = "http://trimage.org",
license = "MIT license",
packages = ["trimage",
"trimage.ThreadPool"],
package_data = {"trimage" : ["pixmaps/*.*"] },
data_files=[('share/icons/hicolor/scalable/apps', ['desktop/trimage.svg']),
('share/applications', ['desktop/trimage.desktop']),
('share/man/man1', ['doc/trimage.1'])],
scripts = ["bin/trimage"],
long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via advpng, jpegoptim, optipng and pngcrush, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""",
requires = ["PyQt5"]
)

38
todo
View file

@ -1,38 +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
- verify that a *recompressed* file is smaller than the compressed one
todo else
- figure out dependencies for a .deb/how to make a .deb <- via launchpad
- figure out how to make mac and win versions (someone else :) <- via gui2exe
todo later
- use multiprocessing lib to take advantage of multicore/multi-CPU to compress
multiple files simultaneously (threads have issues in Python; see "GIL")
===========================================
later versions:
animate compressing.gif
allow selection/deletion of rows from table (and subsequently the imagelist)
check for double files when adding
punypng api? http://www.gracepointafterfive.com/punypng/api
imagemagick/graphicsmagick?
always on top option
notification area widget
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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -1,347 +0,0 @@
import sys
from os import listdir
from os import path
from subprocess import call, PIPE
from optparse import OptionParser
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from hurry.filesize import *
from ui import Ui_trimage
VERSION = "1.0.0"
class StartQT4(QMainWindow):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.ui = Ui_trimage()
self.ui.setupUi(self)
self.showapp = True
self.verbose = True
self.imagelist = []
# check if apps are installed
if self.checkapps():
quit()
#add quit shortcut
if hasattr(QKeySequence, "Quit"):
self.quit_shortcut = QShortcut(QKeySequence(QKeySequence.Quit),
self)
else:
self.quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
# disable recompress
self.ui.recompress.setEnabled(False)
#self.ui.recompress.hide()
# make a worker thread
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)
# activate command line options
self.commandline_options()
def commandline_options(self):
"""Set up the command line options."""
parser = OptionParser(version="%prog " + VERSION,
description="GUI front-end to compress png and jpg images via "
"optipng, advpng and jpegoptim")
parser.set_defaults(verbose=True)
parser.add_option("-v", "--verbose", action="store_true",
dest="verbose", help="Verbose mode (default)")
parser.add_option("-q", "--quiet", action="store_false",
dest="verbose", help="Quiet mode")
parser.add_option("-f", "--file", action="store", type="string",
dest="filename", help="compresses image and exit")
parser.add_option("-d", "--directory", action="store", type="string",
dest="directory", help="compresses images in directory and exit")
options, args = parser.parse_args()
# send to correct function
if options.filename:
self.file_from_cmd(options.filename)
if options.directory:
self.dir_from_cmd(options.directory)
self.verbose = options.verbose
"""
Input functions
"""
def dir_from_cmd(self, directory):
"""
Read the files in the directory and send all files to compress_file.
"""
self.showapp = False
dirpath = path.abspath(path.dirname(directory))
imagedir = listdir(directory)
filelist = QStringList()
for image in imagedir:
image = QString(path.join(dirpath, image))
filelist.append(image)
self.delegator(filelist)
def file_from_cmd(self, image):
"""Get the file and send it to compress_file"""
self.showapp = False
image = path.abspath(image)
filecmdlist = QStringList()
filecmdlist.append(image)
self.delegator(filecmdlist)
def file_drop(self, images):
"""
Get a file from the drag and drop handler and send it to compress_file.
"""
self.delegator(images)
def file_dialog(self):
"""Open a file dialog and send the selected images to compress_file."""
fd = QFileDialog(self)
images = fd.getOpenFileNames(self,
"Select one or more image files to compress",
"", # directory
# this is a fix for file dialog differentiating between cases
"Image files (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG)")
self.delegator(images)
def recompress_files(self):
"""Send each file in the current file list to compress_file again."""
newimagelist = []
for image in self.imagelist:
newimagelist.append(image[4])
self.imagelist = []
self.delegator(newimagelist)
"""
Compress functions
"""
def delegator(self, images):
"""
Recieve all images, check them and send them to the worker thread.
"""
delegatorlist = []
for image in images:
if self.checkname(image):
delegatorlist.append((image, QIcon(image)))
self.imagelist.append(("Compressing...", "", "", "", image,
QIcon(QPixmap("compressing.gif"))))
else:
sys.stderr.write("[error] %s not an image file" % image)
self.update_table()
self.thread.compress_file(delegatorlist, self.showapp, self.verbose,
self.imagelist)
"""
UI Functions
"""
def update_table(self):
"""Update the table view with the latest file data."""
tview = self.ui.processedfiles
# set table model
tmodel = TriTableModel(self, self.imagelist,
["Filename", "Old Size", "New Size", "Compressed"])
tview.setModel(tmodel)
# set minimum size of table
vh = tview.verticalHeader()
vh.setVisible(False)
# set horizontal header properties
hh = tview.horizontalHeader()
hh.setStretchLastSection(True)
# set all row heights
nrows = len(self.imagelist)
for row in range(nrows):
tview.setRowHeight(row, 25)
# set the second column to be longest
tview.setColumnWidth(0, 300)
# enable recompress button
self.enable_recompress()
"""
Helper functions
"""
def checkname(self, name):
"""Check if the file is a jpg or png."""
return path.splitext(str(name))[1].lower() in [".jpg", ".jpeg", ".png"]
def enable_recompress(self):
"""Enable the recompress button."""
self.ui.recompress.setEnabled(True)
def checkapps(self):
"""Check if the required command line apps exist."""
status = False
retcode = call("jpegoptim --version", shell=True, stdout=PIPE)
if retcode != 0:
status = True
sys.stderr.write("[error] please install jpegoptim")
retcode = call("optipng -v", shell=True, stdout=PIPE)
if retcode != 0:
status = True
sys.stderr.write("[error] please install optipng")
retcode = call("advpng --version", shell=True, stdout=PIPE)
if retcode != 0:
status = True
sys.stderr.write("[error] please install advancecomp")
return status
class TriTableModel(QAbstractTableModel):
def __init__(self, parent, imagelist, header, *args):
"""
@param parent Qt parent object.
@param imagelist A list of tuples.
@param header A list of strings.
"""
QAbstractTableModel.__init__(self, parent, *args)
self.imagelist = imagelist
self.header = header
def rowCount(self, parent):
"""Count the number of rows."""
return len(self.imagelist)
def columnCount(self, parent):
"""Count the number of columns."""
return len(self.header)
def data(self, index, role):
"""Fill the table with data."""
if not index.isValid():
return QVariant()
elif role == Qt.DisplayRole:
data = self.imagelist[index.row()][index.column()]
return QVariant(data)
elif index.column() == 0 and role == Qt.DecorationRole:
# decorate column 0 with an icon of the image itself
f_icon = self.imagelist[index.row()][5]
return QVariant(f_icon)
else:
return QVariant()
def headerData(self, col, orientation, role):
"""Fill the table headers."""
if orientation == Qt.Horizontal and (role == Qt.DisplayRole or
role == Qt.DecorationRole):
return QVariant(self.header[col])
return QVariant()
class Worker(QThread):
def __init__(self, parent=None):
QThread.__init__(self, parent)
self.exiting = False
def __del__(self):
self.exiting = True
self.wait()
def compress_file(self, images, showapp, verbose, imagelist):
"""Start the worker thread."""
self.images = images
self.showapp = showapp
self.verbose = verbose
self.imagelist = imagelist
self.start()
def run(self):
"""Compress the given file, get data from it and call update_table."""
for image in self.images:
#gather old file data
filename = str(image[0])
icon = image[1]
oldfile = QFileInfo(filename)
name = oldfile.fileName()
oldfilesize = oldfile.size()
oldfilesizestr = size(oldfilesize, system=alternative)
# get extention
extention = path.splitext(filename)[1]
#decide with tool to use
if extention in [".jpg", ".jpeg"]:
runString = "jpegoptim -f --strip-all '%(file)s'"
elif extention in [".png"]:
runString = ("optipng -force -o7 '%(file)s';"
"advpng -z4 '%(file)s'")
else:
sys.stderr.write("[error] %s not an image file" % filename)
try:
retcode = call(runString % {"file": filename}, shell=True,
stdout=PIPE)
runfile = retcode
except OSError as e:
runfile = e
if runfile == 0:
#gather new file data
newfile = QFile(filename)
newfilesize = newfile.size()
newfilesizestr = size(newfilesize, system=alternative)
#calculate ratio and make a nice string
ratio = 100 - (float(newfilesize) / float(oldfilesize) * 100)
ratiostr = "%.1f%%" % ratio
# append current image to list
for i, image in enumerate(self.imagelist):
if image[4] == filename:
self.imagelist.remove(image)
self.imagelist.insert(i, (name, oldfilesizestr,
newfilesizestr, ratiostr, filename, icon))
self.emit(SIGNAL("updateUi"))
if not self.showapp and self.verbose:
# we work via the commandline
print("File: " + filename + ", Old Size: "
+ oldfilesizestr + ", New Size: " + newfilesizestr
+ ", Ratio: " + ratiostr)
else:
sys.stderr.write("[error] %s" % runfile)
if not self.showapp:
#make sure the app quits after all images are done
quit()
if __name__ == "__main__":
app = QApplication(sys.argv)
myapp = StartQT4()
if myapp.showapp:
myapp.show()
sys.exit(app.exec_())

View file

@ -0,0 +1,275 @@
#!/usr/bin/env python3
'''
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.__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])
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

View file

@ -0,0 +1 @@
from .ThreadPool import ThreadPool, ThreadPoolMixIn

0
trimage/__init__.py Normal file
View file

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 676 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

45
trimage/tools.py Normal file
View file

@ -0,0 +1,45 @@
#!/usr/bin/env python3
import sys
import errno
from subprocess import call, PIPE
def check_dependencies():
"""Check if the required command line apps exist."""
status = True
dependencies = {
"jpegoptim": "--version",
"optipng": "-v",
"advpng": "--version",
"pngcrush": "-version"
}
for elt in dependencies:
retcode = safe_call(elt + " " + dependencies[elt])
if retcode != 0:
status = False
print("[error] please install {}".format(elt), file=sys.stderr)
return status
def safe_call(command):
"""Cross-platform command-line check."""
while True:
try:
return call(command, shell=True, stdout=PIPE)
except OSError as e:
if e.errno == errno.EINTR:
continue
else:
raise
def human_readable_size(num, suffix="B"):
"""Bytes to a readable size format"""
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, "Y", suffix)

497
trimage/trimage.py Normal file
View file

@ -0,0 +1,497 @@
#!/usr/bin/env python3
import time
import sys
from os import listdir, path, remove, access, W_OK
from shutil import copy
from optparse import OptionParser
from multiprocessing import cpu_count
from queue import Queue
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ThreadPool import ThreadPool
from ui import Ui_trimage
from tools import *
VERSION = "1.0.6"
class StartQt(QMainWindow):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.ui = Ui_trimage()
self.ui.setupUi(self)
self.showapp = True
self.verbose = True
self.imagelist = []
QCoreApplication.setOrganizationName("Kilian Valkhof")
QCoreApplication.setOrganizationDomain("trimage.org")
QCoreApplication.setApplicationName("Trimage")
self.settings = QSettings()
if self.settings.value("geometry"):
self.restoreGeometry(self.settings.value("geometry"))
# check if dependencies are installed
if not check_dependencies():
quit()
# add quit shortcut
if hasattr(QKeySequence, "Quit"):
self.quit_shortcut = QShortcut(QKeySequence(QKeySequence.Quit),
self)
else:
self.quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
# disable recompress
self.ui.recompress.setEnabled(False)
# make a worker thread
self.thread = Worker()
# connect signals with slots
self.ui.addfiles.clicked.connect(self.file_dialog)
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)
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")))
# activate command line options
self.commandline_options()
if QSystemTrayIcon.isSystemTrayAvailable() and not self.cli:
self.systemtray = Systray(self)
def commandline_options(self):
"""Set up the command line options."""
self.cli = False
parser = OptionParser(version="%prog " + VERSION,
description="GUI front-end to compress png and jpg images via "
"advpng, jpegoptim, optipng and pngcrush")
parser.set_defaults(verbose=True)
parser.add_option("-v", "--verbose", action="store_true",
dest="verbose", help="Verbose mode (default)")
parser.add_option("-q", "--quiet", action="store_false",
dest="verbose", help="Quiet mode")
parser.add_option("-f", "--file", action="store", type="string",
dest="filename", help="compresses image and exit")
parser.add_option("-d", "--directory", action="store", type="string",
dest="directory", help="compresses images in directory and exit")
options, args = parser.parse_args()
# make sure we quit after processing finished if using cli
if options.filename or options.directory:
self.thread.finished.connect(quit)
self.cli = True
# send to correct function
if options.filename:
self.file_from_cmd(options.filename)
if options.directory:
self.dir_from_cmd(options.directory)
self.verbose = options.verbose
"""
Input functions
"""
def dir_from_cmd(self, directory):
"""
Read the files in the directory and send all files to compress_file.
"""
self.showapp = False
dirpath = path.abspath(directory)
imagedir = listdir(directory)
filelist = [path.join(dirpath, image) for image in imagedir]
self.delegator(filelist)
def file_from_cmd(self, image):
"""Get the file and send it to compress_file"""
self.showapp = False
filelist = [path.abspath(image)]
self.delegator(filelist)
def file_drop(self, images):
"""
Get a file from the drag and drop handler and send it to compress_file.
"""
self.delegator(images)
def file_dialog(self):
"""Open a file dialog and send the selected images to compress_file."""
fd = QFileDialog(self)
if (self.settings.value("fdstate")):
fd.restoreState(self.settings.value("fdstate"))
directory = self.settings.value("directory", QVariant(""))
fd.setDirectory(directory)
images, _ = fd.getOpenFileNames(self,
"Select one or more image files to compress",
directory,
# this is a fix for file dialog differentiating between cases
"Image files (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG)")
self.settings.setValue("fdstate", QVariant(fd.saveState()))
if images:
self.settings.setValue("directory", QVariant(path.dirname(images[0])))
self.delegator([fullpath for fullpath in images])
def recompress_files(self):
"""Send each file in the current file list to compress_file again."""
self.delegator([row.image.fullpath for row in self.imagelist])
"""
Compress functions
"""
def delegator(self, images):
"""
Receive all images, check them and send them to the worker thread.
"""
delegatorlist = []
for fullpath in images:
try: # recompress images already in the list
image = next(i.image for i in self.imagelist
if i.image.fullpath == fullpath)
if image.compressed:
image.reset()
image.recompression = True
delegatorlist.append(image)
except StopIteration:
if not path.isdir(fullpath):
self.add_image(fullpath, delegatorlist)
else:
self.walk(fullpath, delegatorlist)
self.update_table()
self.thread.compress_file(delegatorlist, self.showapp, self.verbose,
self.imagelist)
def walk(self, dir, delegatorlist):
"""
Walks a directory, and executes a callback on each file.
"""
dir = path.abspath(dir)
for file in [file for file in listdir(dir) if not file in [".","..",".svn",".git",".hg",".bzr",".cvs"]]:
nfile = path.join(dir, file)
if path.isdir(nfile):
self.walk(nfile, delegatorlist)
else:
self.add_image(nfile, delegatorlist)
def add_image(self, fullpath, delegatorlist):
"""
Adds an image file to the delegator list and update the tray and the title of the window.
"""
image = Image(fullpath)
if image.valid:
delegatorlist.append(image)
self.imagelist.append(ImageRow(image, self.compressing_icon))
if QSystemTrayIcon.isSystemTrayAvailable() and not self.cli:
self.systemtray.trayIcon.setToolTip("Trimage image compressor (" + str(len(self.imagelist)) + " files)")
self.setWindowTitle("Trimage image compressor (" + str(len(self.imagelist)) + " files)")
else:
print("[error] {} not a supported image file and/or not writable".format(image.fullpath), file=sys.stderr)
"""
UI Functions
"""
def update_table(self):
"""Update the table view with the latest file data."""
tview = self.ui.processedfiles
# set table model
tmodel = TriTableModel(self, self.imagelist,
["Filename", "Old Size", "New Size", "Compressed"])
tview.setModel(tmodel)
# set minimum size of table
vh = tview.verticalHeader()
vh.setVisible(False)
# set horizontal header properties
hh = tview.horizontalHeader()
hh.setStretchLastSection(True)
# set all row heights
nrows = len(self.imagelist)
for row in range(nrows):
tview.setRowHeight(row, 25)
# set the second column to be longest
tview.setColumnWidth(0, 300)
# enable recompress button
self.enable_recompress()
def enable_recompress(self):
"""Enable the recompress button."""
self.ui.recompress.setEnabled(True)
if QSystemTrayIcon.isSystemTrayAvailable() and not self.cli:
self.systemtray.recompress.setEnabled(True)
def hide_main_window(self):
if self.isVisible():
self.hide()
if QSystemTrayIcon.isSystemTrayAvailable():
self.systemtray.hideMain.setText("&Show window")
else:
self.show()
if QSystemTrayIcon.isSystemTrayAvailable():
self.systemtray.hideMain.setText("&Hide window")
def closeEvent(self, event):
self.settings.setValue("geometry", QVariant(self.saveGeometry()))
event.accept()
class TriTableModel(QAbstractTableModel):
def __init__(self, parent, imagelist, header, *args):
"""
@param parent Qt parent object.
@param imagelist A list of tuples.
@param header A list of strings.
"""
QAbstractTableModel.__init__(self, parent, *args)
self.imagelist = imagelist
self.header = header
def rowCount(self, parent):
"""Count the number of rows."""
return len(self.imagelist)
def columnCount(self, parent):
"""Count the number of columns."""
return len(self.header)
def data(self, index, role):
"""Fill the table with data."""
if not index.isValid():
return QVariant()
elif role == Qt.DisplayRole:
data = self.imagelist[index.row()][index.column()]
return QVariant(data)
elif index.column() == 0 and role == Qt.DecorationRole:
# decorate column 0 with an icon of the image itself
f_icon = self.imagelist[index.row()][4]
return QVariant(f_icon)
else:
return QVariant()
def headerData(self, col, orientation, role):
"""Fill the table headers."""
if orientation == Qt.Horizontal and (role == Qt.DisplayRole or
role == Qt.DecorationRole):
return QVariant(self.header[col])
return QVariant()
class ImageRow:
def __init__(self, image, waitingIcon=None):
"""Build the information visible in the table image row."""
self.image = image
d = {
'filename_w_ext': lambda i: self.statusStr().format(i.filename_w_ext),
'oldfilesizestr': lambda i: human_readable_size(i.oldfilesize)
if i.compressed else "",
'newfilesizestr': lambda i: human_readable_size(i.newfilesize)
if i.compressed else "",
'ratiostr': lambda i:
"%.1f%%" % (100 - (float(i.newfilesize) / i.oldfilesize * 100))
if i.compressed else "",
'icon': lambda i: i.icon if i.compressed else waitingIcon,
'fullpath': lambda i: i.fullpath, #only used by cli
}
names = ['filename_w_ext', 'oldfilesizestr', 'newfilesizestr',
'ratiostr', 'icon']
for i, n in enumerate(names):
d[i] = d[n]
self.d = d
def statusStr(self):
"""Set the status message."""
if self.image.failed:
return "ERROR: {0}"
if self.image.compressing:
message = "Compressing {0}..."
return message
if not self.image.compressed and self.image.recompression:
return "Queued for recompression {0}..."
if not self.image.compressed:
return "Queued {0}..."
return "{0}"
def __getitem__(self, key):
return self.d[key](self.image)
class Image:
def __init__(self, fullpath):
"""Gather image information."""
self.valid = False
self.reset()
self.fullpath = fullpath
self.filename_w_ext = path.basename(self.fullpath)
self.filename, self.filetype = path.splitext(self.filename_w_ext)
if path.isfile(self.fullpath) and access(self.fullpath, W_OK):
self.filetype = self.filetype[1:].lower()
if self.filetype == "jpg":
self.filetype = "jpeg"
if self.filetype in ["jpeg", "png"]:
oldfile = QFileInfo(self.fullpath)
self.oldfilesize = oldfile.size()
self.icon = QIcon(self.fullpath)
self.valid = True
def reset(self):
self.failed = False
self.compressed = False
self.compressing = False
self.recompression = False
def compress(self):
"""Compress the image and return it to the thread."""
if not self.valid:
raise "Tried to compress invalid image (unsupported format or not \
file)"
self.reset()
self.compressing = True
runString = {
"jpeg": "jpegoptim -f --strip-all '%(file)s'",
"png": "optipng -force -o7 '%(file)s'&&advpng -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'"
}
# create a backup file
backupfullpath = '/tmp/' + self.filename_w_ext
copy(self.fullpath, backupfullpath)
try:
retcode = call(runString[self.filetype] % {"file": self.fullpath},
shell=True, stdout=PIPE)
except:
retcode = -1
if retcode == 0:
self.newfilesize = QFile(self.fullpath).size()
self.compressed = True
# checks the new file and copy the backup
if self.newfilesize >= self.oldfilesize:
copy(backupfullpath, self.fullpath)
self.newfilesize = self.oldfilesize
# removes the backup file
remove(backupfullpath)
else:
self.failed = True
self.compressing = False
self.retcode = retcode
return self
class Worker(QThread):
update_ui_signal = pyqtSignal()
def __init__(self, parent=None):
QThread.__init__(self, parent)
self.toDisplay = Queue()
self.threadpool = ThreadPool(max_workers=cpu_count())
def compress_file(self, images, showapp, verbose, imagelist):
"""Start the worker thread."""
for image in images:
#FIXME:http://code.google.com/p/pythonthreadpool/issues/detail?id=5
time.sleep(0.05)
self.threadpool.add_job(image.compress, None,
return_callback=self.toDisplay.put)
self.showapp = showapp
self.verbose = verbose
self.imagelist = imagelist
self.start()
def run(self):
"""Compress the given file, get data from it and call update_table."""
tp = self.threadpool
while self.showapp or not (tp._ThreadPool__active_worker_count == 0 and
tp._ThreadPool__jobs.empty()):
image = self.toDisplay.get()
self.update_ui_signal.emit()
if not self.showapp and self.verbose: # we work via the commandline
if image.retcode == 0:
ir = ImageRow(image)
print("File: " + ir['fullpath'] + ", Old Size: "
+ ir['oldfilesizestr'] + ", New Size: "
+ ir['newfilesizestr'] + ", Ratio: " + ir['ratiostr'])
else:
print("[error] {} could not be compressed".format(image.fullpath), file=sys.stderr)
class Systray(QWidget):
def __init__(self, parent):
QWidget.__init__(self)
self.parent = parent
self.createActions()
self.createTrayIcon()
self.trayIcon.show()
def createActions(self):
self.quitAction = QAction(self.tr("&Quit"), self)
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)
self.addFiles.triggered.connect(self.parent.file_dialog)
self.recompress = QAction(self.tr("&Recompress"), self)
icon2 = QIcon()
icon2.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/view-refresh.png"))),
QIcon.Normal, QIcon.Off)
self.recompress.setIcon(icon2)
self.recompress.setDisabled(True)
self.addFiles.triggered.connect(self.parent.recompress_files)
self.hideMain = QAction(self.tr("&Hide window"), self)
self.hideMain.triggered.connect(self.parent.hide_main_window)
def createTrayIcon(self):
self.trayIconMenu = QMenu(self)
self.trayIconMenu.addAction(self.addFiles)
self.trayIconMenu.addAction(self.recompress)
self.trayIconMenu.addSeparator()
self.trayIconMenu.addAction(self.hideMain)
self.trayIconMenu.addSeparator()
self.trayIconMenu.addAction(self.quitAction)
if QSystemTrayIcon.isSystemTrayAvailable():
self.trayIcon = QSystemTrayIcon(self)
self.trayIcon.setContextMenu(self.trayIconMenu)
self.trayIcon.setToolTip("Trimage image compressor")
self.trayIcon.setIcon(QIcon(self.parent.ui.get_image("pixmaps/trimage-icon.png")))
if __name__ == "__main__":
app = QApplication(sys.argv)
myapp = StartQt()
if myapp.showapp:
myapp.show()
sys.exit(app.exec_())

View file

@ -1,15 +1,23 @@
from PyQt4.QtCore import *
from PyQt4.QtGui import *
#!/usr/bin/env python3
from os import path
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class TrimageTableView(QTableView):
drop_event_signal = pyqtSignal(list)
"""Init the table drop event."""
def __init__(self, parent=None):
super(TrimageTableView, self).__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if event.mimeData().hasFormat("text/uri-list"):
if event.mimeData().hasUrls:
event.accept()
else:
event.ignore()
@ -18,23 +26,33 @@ class TrimageTableView(QTableView):
event.accept()
def dropEvent(self, event):
files = str(event.mimeData().data("text/uri-list")).strip().split()
for i, file in enumerate(files):
files[i] = QUrl(QString(file)).toLocalFile()
self.emit(SIGNAL("fileDropEvent"), (files))
event.accept()
filelist = []
for url in event.mimeData().urls():
filelist.append(url.toLocalFile())
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)
return imagelink
def setupUi(self, trimage):
"""Setup the entire UI."""
trimage.setObjectName("trimage")
trimage.resize(600, 170)
trimage.setWindowIcon(QIcon("trimage-icon.png"))
trimageIcon = QIcon(self.get_image("pixmaps/trimage-icon.png"))
trimage.setWindowIcon(trimageIcon)
self.centralwidget = QWidget(trimage)
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")
@ -50,7 +68,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)
@ -58,12 +76,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)
@ -72,7 +90,7 @@ class Ui_trimage(object):
self.addfiles.setFont(font)
self.addfiles.setCursor(Qt.PointingHandCursor)
icon = QIcon()
icon.addPixmap(QPixmap("list-add.png"), QIcon.Normal, QIcon.Off)
icon.addPixmap(QPixmap(self.get_image("pixmaps/list-add.png")), QIcon.Normal, QIcon.Off)
self.addfiles.setIcon(icon)
self.addfiles.setObjectName("addfiles")
self.addfiles.setAcceptDrops(True)
@ -83,7 +101,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)
@ -98,7 +116,7 @@ class Ui_trimage(object):
self.recompress.setCursor(Qt.PointingHandCursor)
icon1 = QIcon()
icon1.addPixmap(QPixmap("view-refresh.png"), QIcon.Normal, QIcon.Off)
icon1.addPixmap(QPixmap(self.get_image("pixmaps/view-refresh.png")), QIcon.Normal, QIcon.Off)
self.recompress.setIcon(icon1)
self.recompress.setCheckable(False)
@ -131,25 +149,24 @@ class Ui_trimage(object):
QMetaObject.connectSlotsByName(trimage)
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 selected 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))

BIN
website/arch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
website/debian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

@ -1,13 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title>Trimage image compressor</title>
<link href="http://github.com/Kilian/sencss/raw/master/source/sen.css" rel="stylesheet" type="text/css">
<meta charset="utf8" />
<title>Trimage (lossless) image compressor</title>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
rel="stylesheet"
type="text/css"
/>
<style>
body {
background:#fff
font-size:13px;
font-family:sans-serif;
}
#wrap {
position:relative;
@ -40,7 +45,8 @@
.tri {
-moz-column-count:3;
-webkit-column-count:3;
text-align:justify;
-moz-column-gap:2em;
-webkit-column-gap:2em;
}
img {
margin-bottom:1.5em;
@ -66,115 +72,155 @@
</style>
</head>
<body>
<div id="wrap">
<h1><img src="trimage-icon.png" alt=""> Trimage image compressor &ndash; 1.0.0b (beta)</h1>
<span class="subtitle">A cross-platform tool for optimizing PNG and JPG files.</span>
<p class="tri">Trimage is a cross-platform GUI and command-line interface to optimize image
files via <a href="http://optipng.sourceforge.net/">optipng</a>,
<a href="http://advancemame.sourceforge.net/comp-readme.html">advpng</a> and
<a href="http://www.kokkonen.net/tjko/projects.html">jpegoptim</a>, depending on the filetype
(currently, PNG and JPG files are supported). It was inspired by
<a href="http://imageoptim.pornel.net/">imageoptim</a>. 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.</p>
<body>
<div id="wrap">
<h1>
<img src="trimage-icon.png" alt="" /> Trimage image compressor &ndash;
1.0.6
</h1>
<span class="subtitle"
>A cross-platform tool for losslessly optimizing PNG and JPG files for
web.</span
>
<p class="tri">
Trimage is a cross-platform GUI and command-line interface to optimize
image files for websites, using
<a href="http://optipng.sourceforge.net/">optipng</a>,
<a href="http://pmt.sourceforge.net/pngcrush/">pngcrush</a>,
<a href="http://advancemame.sourceforge.net/comp-readme.html">advpng</a>
and <a href="http://www.kokkonen.net/tjko/projects.html">jpegoptim</a>,
depending on the filetype (currently, PNG and JPG files are supported).
It was inspired by
<a href="http://imageoptim.pornel.net/">imageoptim</a>. All image files
are losslessy compressed on the highest available compression levels,
and EXIF and other metadata is removed. Trimage gives you various input
functions to fit your own workflow: A regular file dialog, dragging and
dropping and various command line options.
</p>
<h2>Trimage in action</h2>
<img src="image.png" alt="a screenshot of Trimage">
<h2>Trimage in action</h2>
<img src="image.png" alt="a screenshot of Trimage" />
<div class="block2">
<h2>Download</h2>
<h3><img src="ubuntu.png" alt=""> Ubuntu</h3>
<ol>
<li><code>sudo add-apt-repository lp:trimage</code></li>
<li><code>sudo apt-get install trimage</code></li>
</ol>
<div class="block2">
<h2>Download</h2>
<h3><img src="linux.png" alt=""> Other *nix</h3>
<ol>
<li>Download the source via git or bzr (see repositories)</li>
<li>Make sure you have all the requirements installed (see requirements)</li>
<li>make trimage.py executable (<code>chmod +x trimage.py</code>)</li>
<li>Start by clicking on trimage.py or launching it with <code>./trimage.py</code></li>
</ol>
<p>Help us make .deb's, rpms's etc: <a href="mailto:help@trimage.org">contact us</a></p>
<h3><img src="mac.png" alt=""> Mac</h3>
<p>Trimage should be able to run on Mac. <a href="mailto:help@trimage.org">Help us with this</a></p>
<h3><img src="windows.png" alt=""> Windows</h3>
<p>Trimage should be able to run on Windows. <a href="mailto:help@trimage.org">Help us with this</a></p>
</div>
<div class="block">
<h2>Repositories</h2>
<p><strong>Git:</strong> Trimage is primarily developed on <a href="http://github.com/Kilian/Trimage">GitHub</a>.</p>
<p><strong>Bzr:</strong> Trimage is also available on <a href="https://launchpad.net/trimage">Launchpad</a>.</p>
<p>Trimage is MIT licenced. We encourage contributions via GitHub.</p>
</div>
<div class="block">
<h2>Thanks</h2>
<p>The following people helped develop Trimage:</p>
<ul>
<li>Neil Wallace</li>
<li>Jeroen Goudsmit</li>
</ul>
</div>
<div class="block">
<h2>Donate</h2>
<p>If you enjoy using Trimage, please buy us a coffee or a beer :)</p>
<a href='http://www.pledgie.com/campaigns/9607'><img alt='Click here to lend your support to: Trimage and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/9607.png?skin_name=chrome' border='0' /></a>
</div>
<div class="block">
<h2>Requirements</h2>
<ul>
<li>python <em>2.6</em></li>
<li>python-qt4 <em>4.4</em></li>
<li>optipng <em>0.6.2.1</em></li>
<li>advancecomp <em>1.15</em></li>
<li>jpegoptim <em>1.2.2</em></li>
</ul>
</div>
<h3><img src="debian.png" alt="" /> Debian (sid)</h3>
<p>Trimage is available in the official Debian Sid repositories:</p>
<ol>
<li><code>sudo apt-get install trimage</code></li>
</ol>
<div class="block2">
<h2>Command line options</h2>
<dl>
<dt>--version</dt>
<dd>show program's version number and exit</dd>
<dt>-h, --help</dt>
<dd>show this help message and exit</dd>
<dt>-v, --verbose</dt>
<dd>Verbose mode (default)</dd>
<dt>-q, --quiet</dt>
<dd>Quiet mode</dd>
<dt>-f FILENAME, --file=FILENAME</dt>
<dd>compresses image and exit</dd>
<dt>-d DIRECTORY, --directory=DIRECTORY</dt>
<dd>compresses images in directory and exit</dd>
</dl>
</div>
<div class="block">
<h2>Help</h2>
<p>Please use <a href="http://github.com/Kilian/Trimage">GitHub</a> for reporting bugs and email <a href="mailto:help@trimage.org">help@trimage.org</a> for general questions (though we are not a helpdesk!)</p>
</div>
<h3><img src="ubuntu.png" alt="" /> Ubuntu</h3>
<p>Trimage is available in the official repositories:</p>
<a href="https://apps.ubuntu.com/cat/applications/trimage/"
>Download for Ubuntu</a
>
<p>Alternatively:</p>
<ol>
<li><code>sudo apt-get install trimage</code></li>
</ol>
<h2>Planned features</h2>
<p>Version 1.0.0 final:</p>
<ul>
<li>Expand command line options</li>
<li>Make sure a compressed file is always smaller than the original one, or don't compress</li>
<li>General refactoring</li>
</ul>
<p>Version 1.1.0</p>
<ul>
<li>Use multiprocessing instead of threading</li>
</ul>
<p>Beyond that</p>
<ul>
<li>Deletion of rows in the table view</li>
<li>Notification area widget</li>
<li>Integration with online services such as punypng</li>
<li><a href="mailto:help@trimage.org">Suggest something</a></li>
</ul>
<p class="footer">Trimage is &copy; 2010 <a href="http://kilianvalkhof.com">Kilian Valkhof</a>, <a href="http://paulchaplin.com">Paul Chaplin</a>
</div>
<h3><img src="arch.png" alt="" /> Arch Linux</h3>
Trimage is available from AUR, to install, type:
<ol>
<li><code>yaourt -S trimage</code></li>
</ol>
<h3><img src="macos.png" alt="" /> macOS</h3><!-- Attribution for the image: VICDJES21 / CC BY-SA (https://creativecommons.org/licenses/by-sa/4.0) -->
Trimage is available from Homebrew, to install, type:
<ol>
<li><code>brew install trimage</code></li>
</ol>
<p>Launch by executing <code>trimage</code> in Terminal.app</p>
<h3><img src="linux.png" alt="" /> Other *nix</h3>
<ol>
<li>Download the source via git or bzr (see repositories)</li>
<li>
Make sure you have all the requirements installed (see requirements)
</li>
<li>Enter <code>python setup.py install</code> into your console</li>
<li>Launch by executing <code>trimage</code></li>
</ol>
<p>
Help us make .snaps, .appimages etc:
<a href="mailto:help@trimage.org">contact us</a> or
<a href="https://github.com/kilian/trimage/pulls"
>open a pull request</a
>
</p>
<!--
<h3><img src="windows.png" alt=""> Windows</h3>
<p>Trimage should be able to run on Windows. <a href="mailto:help@trimage.org">Help us with this</a></p>
-->
</div>
<div class="block">
<h2>Repositories</h2>
<p>
<strong>Git:</strong> Trimage is primarily developed on
<a href="http://github.com/Kilian/Trimage">GitHub</a>.
</p>
<p>
<strong>Bzr:</strong> Trimage is also available on
<a href="https://launchpad.net/trimage">Launchpad</a>.
</p>
<p>Trimage is MIT licenced. We encourage contributions via GitHub.</p>
</div>
<div class="block">
<h2>Thanks</h2>
<p>The following people helped develop Trimage:</p>
<ul>
<li><a href="https://hugo-posnic.fr/">Hugo Posnic</a></li>
<li>Neil Wallace</li>
<li>Jeroen Goudsmit</li>
<li>Tarnay Kálmán</li>
<li>Thomas Lété</li>
<li>Kyrill Detinov</li>
</ul>
</div>
<div class="block">
<h2>Requirements</h2>
<ul>
<li>python <em>3</em></li>
<li>python-qt5 <em>5</em></li>
<li>optipng <em>0.6.2.1</em></li>
<li>pngcrush <em>1.6.7</em></li>
<li>advancecomp <em>1.15</em></li>
<li>jpegoptim <em>1.2.2</em></li>
</ul>
</div>
<div class="block2">
<h2>Command line options</h2>
<dl>
<dt>--version</dt>
<dd>show program's version number and exit</dd>
<dt>-h, --help</dt>
<dd>show this help message and exit</dd>
<dt>-v, --verbose</dt>
<dd>Verbose mode (default)</dd>
<dt>-q, --quiet</dt>
<dd>Quiet mode</dd>
<dt>-f FILENAME, --file=FILENAME</dt>
<dd>compresses image and exit</dd>
<dt>-d DIRECTORY, --directory=DIRECTORY</dt>
<dd>compresses images in directory and exit</dd>
</dl>
</div>
<div class="block">
<h2>Help</h2>
<p>
Please use <a href="https://github.com/Kilian/Trimage">GitHub</a> for
reporting bugs and email
<a href="mailto:help@trimage.org">help@trimage.org</a> for general
questions (though we are not a helpdesk!)
</p>
</div>
<p class="footer">
Trimage is &copy; 2010&ndash;2019
<a href="http://kilianvalkhof.com">Kilian Valkhof</a>,
<a href="http://paulchaplin.com">Paul Chaplin</a>
</p>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 B

BIN
website/macos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB