trimage-1.0.5/0000755000175000017500000000000011451672644012457 5ustar kiliankiliantrimage-1.0.5/src/0000755000175000017500000000000011352206746013242 5ustar kiliankiliantrimage-1.0.5/website/0000755000175000017500000000000011447647147014126 5ustar kiliankiliantrimage-1.0.5/desktop/0000755000175000017500000000000011352215436014120 5ustar kiliankiliantrimage-1.0.5/resources/0000755000175000017500000000000011447645016014467 5ustar kiliankiliantrimage-1.0.5/src/trimage/0000755000175000017500000000000011450670710014665 5ustar kiliankiliantrimage-1.0.5/src/trimage/pixmaps/0000755000175000017500000000000011444732471016354 5ustar kiliankiliantrimage-1.0.5/src/trimage/filesize/0000755000175000017500000000000011450670710016477 5ustar kiliankiliantrimage-1.0.5/src/trimage/ThreadPool/0000755000175000017500000000000011450670710016726 5ustar kiliankiliantrimage-1.0.5/0000755000175000017500000000000011463304026012444 5ustar kiliankiliantrimage-1.0.5/src/0000755000175000017500000000000011352206746013242 5ustar kiliankiliantrimage-1.0.5/website/0000755000175000017500000000000011447647147014126 5ustar kiliankiliantrimage-1.0.5/desktop/0000755000175000017500000000000011352215436014120 5ustar kiliankiliantrimage-1.0.5/resources/0000755000175000017500000000000011447645016014467 5ustar kiliankiliantrimage-1.0.5/src/trimage/0000755000175000017500000000000011450670710014665 5ustar kiliankiliantrimage-1.0.5/src/trimage/pixmaps/0000755000175000017500000000000011444732471016354 5ustar kiliankiliantrimage-1.0.5/src/trimage/filesize/0000755000175000017500000000000011450670710016477 5ustar kiliankiliantrimage-1.0.5/src/trimage/ThreadPool/0000755000175000017500000000000011450670710016726 5ustar kiliankiliantrimage-1.0.5/0000755000175000017500000000000011463304616012451 5ustar kiliankiliantrimage-1.0.5/src/0000755000175000017500000000000011352206746013242 5ustar kiliankiliantrimage-1.0.5/website/0000755000175000017500000000000011447647147014126 5ustar kiliankiliantrimage-1.0.5/desktop/0000755000175000017500000000000011352215436014120 5ustar kiliankiliantrimage-1.0.5/resources/0000755000175000017500000000000011447645016014467 5ustar kiliankiliantrimage-1.0.5/src/trimage/0000755000175000017500000000000011450670710014665 5ustar kiliankiliantrimage-1.0.5/src/trimage/pixmaps/0000755000175000017500000000000011444732471016354 5ustar kiliankiliantrimage-1.0.5/src/trimage/filesize/0000755000175000017500000000000011450670710016477 5ustar kiliankiliantrimage-1.0.5/src/trimage/ThreadPool/0000755000175000017500000000000011450670710016726 5ustar kiliankiliantrimage-1.0.5/setup.py0000644000175000017500000000307011400765347014166 0ustar kiliankilian#!/usr/bin/env python import sys win=(sys.platform == "win32") if win: import py2exe sys.path.append("src/trimage") from distutils.core import setup setup(name = "trimage", version = "1.0.2", 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", package_dir = {'trimage' : 'src/trimage'}, packages = ["trimage", "trimage.filesize", "trimage.ThreadPool",], package_data = {"trimage" : ["pixmaps/*.*"] }, data_files=[('share/icons/hicolor/scalable/apps', ['desktop/trimage.svg']), ('share/applications', ['desktop/trimage.desktop'])], scripts = ["trimage"], long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via optipng, advpng and jpegoptim, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""", requires = ["PyQt4 (>=4.4)"], #for py2exe windows=[r'src\trimage\trimage.py'], zipfile=None, options={"py2exe":{ "optimize":2, "compressed":1, "bundle_files":1, "includes":["sip",], "excludes":['email'], }, }, ) trimage-1.0.5/MANIFEST.in0000644000175000017500000000020111352215436014176 0ustar kiliankilianinclude COPYING MANIFEST MANIFEST.in README trimage recursive-include desktop *.svg *.desktop recursive-include src/ *.py *.png trimage-1.0.5/COPYING0000644000175000017500000000206111352215436013501 0ustar kiliankilianCopyright (c) 2010 Kilian Valkhof, Paul Chaplin 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. trimage-1.0.5/MANIFEST0000644000175000017500000000063511352211434013576 0ustar kiliankilianCOPYING MANIFEST MANIFEST.in README setup.py trimage desktop/trimage.desktop desktop/trimage.svg src/trimage/__init__.py src/trimage/trimage.py src/trimage/ui.py src/trimage/hurry/__init__.py src/trimage/hurry/filesize/__init__.py src/trimage/hurry/filesize/filesize.py src/trimage/hurry/filesize/tests.py src/trimage/pixmaps/list-add.png src/trimage/pixmaps/trimage-icon.png src/trimage/pixmaps/view-refresh.png trimage-1.0.5/trimage0000644000175000017500000000157011377217701014031 0ustar kiliankilian#!/usr/bin/env python #coding: utf-8 # #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:]) trimage-1.0.5/README0000644000175000017500000000137411404674273013342 0ustar kiliankilianTrimage image compressor A cross-platform tool for optimizing PNG and JPG files. Trimage is a cross-platform GUI and command-line interface to optimize image files via "optipng":http://optipng.sourceforge.net/, "advpng":http://advancemame.sourceforge.net/comp-readme.html and "jpegoptim":http://www.kokkonen.net/tjko/projects.html, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by "imageoptim":http://imageoptim.pornel.net/. All image files are losslessly compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options. Visit "Trimage.org":http://trimage.org for more information trimage-1.0.5/website/trimage-icon.png0000644000175000017500000002001111355354336017175 0ustar kiliankilianPNG  IHDRdgvsBIT|d pHYsd_tEXtSoftwarewww.inkscape.org<IDATxy%W}?}7o7},-4&dLJƐKl;ƶRʔc!5.la@!!!4AћyOs97Bho֚sIf>>t~d 1ppp)\q^񹡯U5\~=0 <@#pw׶G~r?t9@ 6 |sU@^*˄;~x=Yҷ^/(TRBdԔr@X^zYXz#H='/`7^ܵ56K m\]E>"b;گkoxѫ$I7*b7c:'f[hOU)W]`/D)8 ^_ Wm}E+!I\ F}jx?-dugDH !V+LJԦ=t_`B1/. 1N޿Dm1 gEȃnm6bR!('j>6JJ\)⋆ԓ$9cRl*%>8E[DJe ,/Gez8wv=:Mf{ީJJ\0ZƯB5O 0gŌ0q3"9!ʶQY(ۆ&ۑk ysH$?0# Vn@zqv7<#5G$תr9$ʶZuu7Gݟ{ C$i^[D Gt. ^V#K"J Q]Xuh|)G`l Fcӏ1|{Qҹ^JO΅(90(ꑘ=ֈ`|b Gl7 I$&NNhT7iu: b+]_`$ }q[?9FRtE&Ub z*MނF柤:{e )װnv$IfS$Irtч !CyqzvКX[Xj3XNL_sK}80_N5Tm^$IUzl[1&]{%ph д΁i #B&> 3il맚C:4"Kifᅩ#oDZn!5N݀h$b%UtR fwD-n9L7l_3  _x7 Z4XR-xD{VWa%lTjRf+5/:SUVJl6:݇Y%M3H$gk=|foBJ?}zِsRЩF80\=TUjCg<.L^j~$P:hcRwDD4*BN^jppSg&f-iAD?% t _6{lnÎ5GƥFMΛÛLT^OHE v ;Tf~5t) (ʹAhBwK|mo=!%p7aP[3YX7q`CzMVrO9_GsdVԪ:nנ3%Âʉ )6Q+Ԍ_)G,\̍FlY20ࢃD|D.Vv/ ОYcWkc?G.lѨ)vIL"ᚹvjrҲizAx+]!>:8&"aѱ*Mua0Mȱ3Xg$=5;JcQP(P윭qd ?%Qb?LSՉ[d_o5b̲?6Osl i+PmUrL[Kwz>B2$|+H`Hؓ0HY>^ǸvC:(2g~W cҨ7!]US#BJT#.4DE{0|F .QE7;JZkL |Qvb;51}M$M͸fc>>O (QH?X{xVY!YߤYHyhrK="-'"\D$HYEaM[NJhՠ=>?PB`~?2JNU!ЇhDTz +W\X3F&qD>A N˼yn/tTS䶯p,bۅn0$I2 [j*Cy%Ժ( Lލ''x[T݈slvROg>9`fU3v\)XQq1E!h7hF0(}Q MeN% 8 uh'Kቻ"U&#<Ф Q>#~|2RYfmUvЬ FP{X ͱJbWt x}&1ߝ3jث(A AL\׊vtXt%+yMt`FTp#Ra~^7F*G` 3ibZОe~ɞ ّ ۮOsx]C`LN3K–AP] C)QvK*rjBE/6%Bѐ;ȄrA[VB(QqgL;k4 [th6L֙&:(w-su8qǑW/IyܫMEP)d' P^oɄٟh.>B}cu8ޫHA^tr^J`|(fa^Y*?Zj&,,7<+aj(kƖk)M#JUeQ#[sO}q#·_tߗ56^e5aڍ"*HIt[f; E0m'N/<20u+J)ʷ)ĀV}4??@oݠòyp li yYZ凍a| #hDº!VyRڵL2DW*Jd A w3v{7}T^\|͞N|w O%햜:/%Bgxb@Y6+$~ly\{X9X~ܾۧZjԀNyi$ 8Xj3ƷTXTU8n=ؗ %/!S?r-[Fhhkn"#axZΓ!hw`$'^UdMaoLEˍJ,q֗L3"w{'~i[?$I!3 yY|cۯhchH("{uR*D?:R!`˫@ T;^F7rT7i_,تf|U;ajvC1?Vc_DTd _H:ިGe4΀zP+cUD.j#o_o?V1-~1^*R^+ٜNhL#RY b7 : U 6uHכJC(R=`Z*)ox%L+ܙ<=W2(vZ,L Ōw"jx`x!Xa6FeJ#N)A zjjb0j-X>z{ȷ5$ ku˽)*3]̦#a1ډe`"WmiB=ʹ%4(UU"~ PUNEQc+%8ʯ?+ң7ue+v Fj)FZ͚5\ @.9ɇvu%\LRvaՌȃGd%Ϛ0 K 7~O kW069öپg[ mTHe\k@_|?#ٸӬ&^afa [vgˮ߲Zᬅ+ijJ'~^_TTs9pv 6ՙ߲;iq?S Ea "rp-?@ABCiDFGnKoMQsRTwxX[]aÄÆhjkćoƋqrǎȐvʔ|~̗ǽӆԇԈԉʿՋؒԨժٗ٘ڗڙ׮ۜݠݡڶߧ޽߽5E)tRNS@fIDAT(c` Lv^KIh5zǏgGk5Q@I$ @rrE#u1% &NrX,7-+z?~BwKpBngw .{^PtdOWu%s%5[ Y\c$''_dWЮ핌>OUU` 4B{YW>MVnN h*D Rerrf[Rd٥@9¼"9J)BҐq*(q5 •nV0gSzEaWєa<,ڀ6`ogS-q/i،<86JKN:k2v ܙ8$0)6 IENDB`trimage-1.0.5/website/windows.png0000644000175000017500000000325111355354335016317 0ustar kiliankilianPNG  IHDRoHsRGB pHYs  tIME /-tutEXtCommentCreated with GIMPWbKGDIDATHǵ[o\Wu{>̌g|ǁԤ@ȡ*+$.W ~J TԂk$uuc'{f2gaDž[B{WbDu8`  `URhaiPJpX F`s cc`J+B <8.I)a1p+iFDdOlw ?ܼo|G'ގ:H0I,<5iaB2_~z{1k-1sL+67iMNp/@#9PBUW2T_:^MZVj(QR&u2%[/7B4<ɻNL!mq4:̜++Q kK_!/c'AYRʄ\4O'/ؕ%/޿[&wtvkqz2òH,Ӛ6cOfxoy1ّgElVU5pq%2 Cr=kf`{}סZjꑟVIEO%a^LwCވb@og?H+18liʌ\޵tfR{2ʾX?a8Ȫ2Plk҄&P^LD ($W @| ڟȀDL'e0wA_S^Iy. L8OKVVp37J6[~OG}]/j OE4*cJg:EFV:?{~mjA}>~wǃ;QʥzgBQ~/pix'#B.(4xpyդNPť&kgbǡt^Oe\$J)e+hi %2 [  LNE7BtIENDB`trimage-1.0.5/website/image.png0000644000175000017500000005642611355354341015720 0ustar kiliankilianPNG  IHDRn%f)\IDATxyPy+Y3qq2M&v:uNfq?mS'3mɒ@%˲l qB> !^CH)~Z^kKqhyf>j>O88888888888888888888888888888888888888:"/lyaa "OY'¶?0 0 <;V%^&C|xI:I侎{{iheaa_kxxxYo3qLU,v#[cay."jE5[ ۏ=Q^2}[g])8=gkn9aag&4IMXrter$o?U(ۖ-p6q؝8y % 0 0τ 뻠nSOk''%qrygpHݍ(}uPfڢ (ޓR'[G92T@iR6^4=r,&g025rOM܁_ޟnYj1>I"~]1EXNQRWY$Jk$WҜ:R?f%V0$~AN&B [t8EF]T ](u_KՋH{Gh^\6 cDĭuةEigJy[h2 v}_j0tڽDp"lm4眂wNhYuMYyv70TlrK{gBXH'+xrm"?چc^7P wVO2#s N&[JrAT$i56jplZ+ӷ -VGIL:l܌ީnUy7_ 5 0QEvM`B_m'?M]/˱\ץ>>JFT!#"(%I(׸)q{:x瘒6[__?w~L*`V܄7IxơZҥoywA}pAHq+Hv-!zՀB ZT `woE>TAT {oI3/U^7qN陃#d_ z^sU|Z4lِex]S1,cv2+mS$ vTKW3w1<ڌO<5pLvYgcPxk,zR.8fnF)Y$,ʹ^ R4֢޵LsCP)rp<7 -4ll__ j@,j+TT9$ z~OX/ $Q8G%5cJܞ5:jfX~Ow_&~g5q ܣR܉v_G!Ā9$ޞG mq!j<|4>j0}3q;˕p#~(pI$9hac=5أ*kHS\^7ᨇ[Sk=%IP]a I#p"Iک\W]a&Af<8zRĭ ^xj ŰwAhY~H5vѝ邛"یj$RPA^^hd0ĸ"ʯ%z-*\suVZGBʷX#g%!EI;pZ݊=ng}ɻpNE|,j瑣_>ʻvp ˨\F=^ c>v#$)PTѧ-!x RMT(|I2s-C걏DCå3-FEl]èn@YL]# ,$08 TV atPep޸fԍ/`bjsh(ǁ$o뉸 /Z/ OadtC p0(=YXpg+Tt } ^-[h")̭C ‡/KM}5؟ЍΩyNR/3y&CfQt]~-1K|7FHCqum_  Yq ^{y2"t/z}4 >BFP?D&ʛ n'\jx31uܵiǚlw˃xN-f ߕxHiħ杇' t:6Ïrӛ|2 |H@ '4jWG x ;<ҰYFǀCPX_` D|.QN3G;h.TQ:4Bl.[0na!7(nyxE&Y.ŷ!2ӯol>Ul긅kn. 8ruZ{M!mguorh'7g \vpyEjI B0fC L=Q-{26vȰ!Y[;[H$<6  95[xn&n'.(?D^HBz4 _uK |܅G`l<3a#`55]ܓ9ҫ(3wfmҀԝ 9kܞDXN1E8Y`<|^F@T2\yaagBرR1fB'n 0 0gC~ aa]{i8- c:h$[4&34qɒMK0˶7ܼ`% n  e"Bi\ҖR ^YOɺ؄mZCyQο='m!"/kF&g,EZI3,V:vvR*E̟-8v3#ir5-`y W !JR;pB[/ٗ>AfgSF}8^t9MIUH(QaoF3Үj-ok9BD੧sbc{͢9OtliR\퐤6ÌsLU/.FX_h4lwnMg̹ߗ}MES/A,yu BBao_€\XxX)]%x/mdw <ЈcN? "0ivLt<>sGRQ+Jj{qV!6s.q?nBרn+cMDˈuj<*=Nˍ!Lu7aXU0ݞۃF>q$Sw>9G;=w S0>.RjU~XTpS( dN''}x);JDȺ`@O<\;Z#Jb0'<0ESU Q僷s oS1(mu$LÙ0w!*1olezR/n.uۢ(S̙SLaޙHI#wܔv?{1, `;cnO>5FրR~yeAZpUSÓ1`$ף:hГx afs&W69P+O8`7uWcSf$4zќxiu]GBpysDvNSu2EΙ6*y|SedwBF CY {0,"' ᦈ/Ƴl_r2 9V|۠,vI&Ũ} c8Sm&_wbЖ.XU,f]ik pL7eX}SEc_&XP6oWYk9T5#VU5+Nyc\ӠCG+qOQA؏LYI`ϴȊ{s=>\mw0OB5 WnS ߂)LW?潻)7 739?y}% YRRЅ30՛dP$I_yetR٘Ʊ+D FѡRcM{-VZ$/_ٚdb4bg3{vIғslN-i\uodah"ljtmߏy?gah zit͟q(à73xkMS?] SQF[zjOwܔzY8х-Y6quh8E$nzj o?%ܦ,;ŏ=dɮpJJmwkjVa_iD)Y_!n8Zps67n6(dY3Z6Ҳ'|3YyDk3Xv0%YC2$4 z/ȯʍn+H$x}͵ʺ6ft8n4R__Ӟi]=,]Ì0G2ٝeҗpdnnHTsWu\]AQ%Ӌ;XkT%rYGByRM⢱ޝ%e6.( ͘\*!b<& rNTIsr\}}p. U(n8?e՛9k<=l^>xBaߓe}VҬ}x٫7U?uFKA+rRnoߖIl;ϫڣڬz|!Th鼩XE)_{0,o3YΦ#~&cQEdÅ ~~b,EPm=f) ^xs|rm9 [nQ]mi6!q95ŒhhUf%<)Բgdg%f$x۽ŒJakxq3|7%;hLm#<sGg ޾?{x)L0Y$+K4>Fv=4;2* 1[mOGImbUj# \zWa0Ʃ3O<֕6clgL3K ד Ă,bBqlם~p"ָ­C<{{;z3d\6򉻲(NЧ'3u^˚K<Gx1<˹۸~nvd&OK3¥gM& b..N8_ӍocX88 c}n͟['1u.aȔ$M\?5_{xדqmkǮ\B`Nݏ-oG}=:uYGBpyg$=]Ky/#(O `w2{8IXRó-5Gx{K㖞el=4epFi뿶WԚM,fs!6[`a,DWI:C3d8^3Ƃk.ڞqBZkZ "V h-7_Yq3 z>vV˂ɼ+$W[ +`y|~ք3ais"'Vݼy?Deee `0i3^6b/\+*e[Ee$nɀr%2! 5o**]q "?9.TEgΎ8/?96^a+69׊Zv Y\5E& tbN\Lf,ړ"a)rlZ\HiFP$Y[M@ h=4?o*9h40~b݁P#0k1m;cxwgoImXfmMW>̔Oϰ $^@'#yS䤆i­ʊ?DYрDO\#lHLub[Q^&mpR ]q@t\" DFrで|]sxyY^Xq?;ǔAi>X~X]M#7'['6G#Xc#o$y ˶nY'ܒRᕐ7.^ !8,'pO>FCq ;}8rp%*Ą 6G#WִQoA7@кh~T8*+#IIAh%.\Pʗ/]JrJ{RL+n5ղZ!AyS[rznڬLү]%5%Yr $&rZjRfQ3J|h!Zp7@&Z-7MF,!$;r2@S%&c-Ln6o7ADg۷lM <()rRnJiiZi:m/D9IIGFn?R':|W J_{Ib|T.n@@ IrNj5My1Ϩ[^~%J$ym/D9I^ l1 =G~+w r@ xySpk'@ AM @7!@ p@ &;,3mw[GL #!OQnskk_mm[>nZUQU "ϐI*i7w vaܙww~s9k& 7nAA$  nAA$HAAp#  TE]tE]tхR(/w`Yjvwp{  h+JJnaPVZxڊ֖n~MMhjlDzmܾ.n-AAG[~*d_~ur>ݽ+6+&,cѠA3.n-  mՊeooZ[[n*e˄ z4W}(V];%4j˄ z4BCu8P>Gv L,޽A=}(pZmn՜,9yWs ,6W>A$xAMg;X=vwM 왟xzr{`n;9IG#Ёf[f#V _NO~%  E`֠|z+zn]s ~PR)d0?f7k)v=^ƙS'pSqixˆA>vp7lPEG6EWC |s:nX=v U؟"tV\3Z9LL`*XP+#Sw]㱽Ȅftu*WmkKx~"1VemIѯ}tnχxB:q#5|綯@fm?nlm:%Ӷ{Xdwpknj$ e^->/ RYMuP "(h⃣e v4LIءUl45A#`p8=QA=yc6"A{ v'AeC{=OPdn}(5 rFa  ]{LLȤ3k30:26z!+ s 1٘9h(LbQQv }ҧx75Qau8 'FaYn {(o,SZX+3'cҤ1&<sv缄cbʺ[8U!+^ _@"xZkwwgk)RV ?N0P'ܸ wp =^ĐH$x)eQMS0G*-^Ki*}hM=\!5rJ!gr ,TZ3mY@+`9?aΛ07Cu4 {❴j7>ZOyt#ڭg֞CVlo}1HSJ퍈ǶbGs8?s%À3eُ5hՈF{*`֥q5=Z3fBa,,EK1,\gM4 ~I[LJð亹q>³Rx?Xl,dmBq,B!۩~-틤omP$y 1 !J4:6c4ӣPYF-qv_W$܈.n2_,,`]Cj3GXy6#b0'A~qX^Ă/DhT >H ^+cLx"c5EuPܒ(~4Wc5dy?`rf7dJ- Χ՜A4*6cG10d;n3QD$Ja:$(އ xÙ*#Ouvda;QʴgTEl\sh/PضSvE<1mFE߅7Vb9Zs~Dxis~9Za_f#R6 tz!+ (–P !b?k %$a+BJe6&`ۆhȓOCev8}bFb,âp4Palt Bo;9r XgcŪoX(x_EZс/tJ pMyoHĉ|2mX-ngeRύӸBƩjGMMjΎ4l;J)їEk3j94l`zG7Bh{Ƌ 5Ff*kR$"$h_70("bT5lxý>} hۜ$ U>o}aօCWp/䏾X}B,( X+2)qen,!n&΍0?!I^ =o3iH)+ .\ L&-!tgEyslmp3 n\TpRViǬ#% 2`I^nO_8Y%PH~'!%9;PbhO_>pQQ{ :G7Bh~.ƶ7GO<1xk[Lu֢` L䢑HjW؂٣!S0L,>UȞAHx8mDyY*!+@,/Ѐ3UȘ)oE' Xgz#pXֈC@Wa1cDŐȆ"(?7; rEL+*lgV$ ,Rpvv~xʀʴ1~CFqv gkqk z4BCu8P>D#ҕh/f,9]3X 28|#Þ.;Ϊu1 z4BCu8P>[KѡpۗeĞKRb 6SJKƚXm)zf4 z4BCu8P>GA ^LA, zAع^ztxKes݄, $lH%Fj%2 xiLՖ`khE@*PQ$B.K6{vw=@LrFZA3{f;dy=([EQEQX|q;qEQEQ%>_֮Z((狛8TDDDDTTDDDDTDDDDTTDDDD{GMDDDD;n*n""""*n"""":*"TADD>vkye z:U>f{,~ ~b#uͱ%{z&yɟ lRS/2u )lkc0$k}l5G/{q5\3e0iM-&`wL-Tu?/zmv>qՠsKAw_K(IVFLg`A9?.XTD3c7I,>؎M% Lfm̵(X],5RFC:x YDzI& ̚eN4XQL^*cZGp=ҾSōSrc ա|#qS޾}Sϟ%eн3̌*^8FzggP6)7Lfgy<øY1%y:<ۘuc FXXv I+W~fY;EϚˁv¢61?t'-w{y؟J?[seٷKH5&hz ֝j=z=n$綝# <=9»ߤQigNqsK/ʛSܾ9dL->Մpj&gUyY%}ajVN ~Kq8M D͔g<ؠDfokg 5TDl ]KRzX3:CqK!qqzd/ƒ00m$\=ҕ #.Zu;8xy<IH!GopA^oyDs23daq;[z8~Xڮya׭}evߜ;&u~D-M Fů7Ӵ uZa5l եel/pB7uۚ&r%r7E;i"FFmB/I̪`SBGY;KPWe~0uplBfǴ9PcBZ^pBᓼ$@BT%e)Z?͜RZZb]Sܜ tXΛX(nl<}~F>>)n]G׺B4ZPq"i?PAo6BYˠ@XzxwrmQHTr2t}&=pNAF%KKﹻJF%BGgRY%u,?tydAB yV\-GCX>YH1m>/\Ӧ f O)"'V23Uܾx%IP GWJ?w,3Ry$]n^ǭ]CDʻ)򥒞%5{* ٮcES=N1yIII!)udR 0kX-X|DgI cuT9 kyfx:d({X{LNɔeG B{`LaQ̼ͧ6f ?#ssF>VShG$s;ƭ}*"""&"""&"""&"""e_vƫIzZ_7`*AMejqttV]펝GejTɰjuVA_DBHHBM8GEE8ܪP9{o~߽bX,FqcX,ŢX,bQ(n,bXIWp|8pއzڍ%;BwN։Ϗ7 ! a?*EB e۾9\uύō?g<󠸽!Bؘ`F!lL̃0ōDa7Bac"̃yP(n< qs9֌.Scz{0og?d߆-(D WA&S#=eXMJ:蹈K`wˢaHV@lḍ[ު=JAIY|7>+'B9l>'K*Ϙ wʧlx:)n7By$iTX``H'Wz4>EAr ۬Ap${IH)j;jVdܹ04˕q E7{̆d>P4 SAX3ߟ,OI\?c1&yڶb0?k>8|Z6UOִ5!NΆa)D׉H]$Nrq딥 ݻwqܾ}tvQW6t&_k0U;aGu HrǬ_+l[`I@6Z}Wv[m=yg8,EDaՙqeXpR(R\h 7B($aZ-41H\/B` 26-=,zFAj D[rQg̣)}Z}6*3Dvb<D~юXĩ~ Wg"Òyyk̋-qn b,gt "ommm'[{C?x2woq1!GҎ;x !8Jbb*x쬮))z|+RZNׯB78vKG^FȀ x~;aYx괿/P QC^.w 9 \^.Ă@"N(S*E"Z  oB[I$ =!B½$ۻOegGls9B4yfogfϳYɇCMMBBBB.L ɇ\$$ 7 0I> qMAA@msV ]>q r>AMZ  "nr,TAsEix<B#  6MuChmia``Y 6ĭ(!o.AEAAAm숛!jG%妵χir,TAq+Gee%mmFzNuttϻo$'U^. ! m|zVfs}1j)yQ@g}VepRp=ml6ldD@!4MZ('"J3 'c#.6yua@Jq(f+8EܾOqucuٳ3j[k{!q>ꦏytoۅ}A!Wܴ~ZEaOJ(hރtZ\O(D 5{,ۺAq0XYyeO8ituyQu@Ҷԧ99ӷ^-=΀j4 vǝ<}؇.qB8)2QKu3q40|jVQ9e.U4)O^4@=w2'Q? a ܳNc^Q1>gيSmŭ Bɷc8zK։&3I(@{ax$cr e.&n3l)Br܍\{k;c ;;3lXqC6} 'Yجs ƻ`4*n*<9h/N+{ύB4zw:IE.'+!؎&:N?G m<)Nvef'ߘWq4{9G \VQZ(meTUL"yw 2[#u@(n|$B/b.{ES\\ƺ~t% g"/1dfHWNZ)5Wg?%vqqޏsX#ۮV1 q33$%8BŶn $7ɘv |w7S=Լ$;Ո33aH>4*k&bX$NktJv^\)a5*ٌk4sgzKef8qs jbi ~qFEkWJu`Haz>y؋Js&WxOZg6SpUZcJC~ac&UJǖy$o.nZݕ ]׸[{M5טWqSkiuuCJ᲋) Ynu(:ą!H>tT܆\43|E&h[D xB֟RAm3xL 8ޠ]q*TT2凉يqpg-yKN[1G0⊛$Ln@}!5`8j“|z9ΨNvJJ(H>\qr- #8&21q}C1gM3 !`ss,MHZc%0L?1C fX+5r6rtTʙIDAA-W(} \FJQ;da PɴәjOŨ5RwA7' ]FKaa|~AA1'n&ݯ.wȂ HM*n sNҮ:e ,2$Z=v4uN{ܼemVjygSd *E. s{/S\N28s 0yD7YBBB*nnn}&G9Q(7 7IHHHHHHHAp3 C$xa?HU%&D!&&$1!&DD!&&D!h¯ׅ"H!?Xpg䞻7Y$Ib?D⇀n:޿|< TZquuRuDʷ\  ē00=kVhnKcBH'˅/l}g<]?ߝϬa,/ui֤[IҪ|fEg@X+1g[G[{IDGX<=蔗xPj=d,,H5@w:3IPet=ݺu7oLJkK --4S_WG] hnnNBsJʹb[0m'y VU=89D>Z.>wy,joPМGYD3b^j*G3dŏꇇ2'}mK'&gC\a ۊk-9؇xf%+=#Ni¸  9%#PVZ ]jN21̻Ju>N'־"BM(^Hsq< SM;{Xt 90׵"w|>@ZqѣY_fV<8<*+أDzЋn)6+)f4Cq6DLQ /P/RIZpO7S74=5l:mSXnk{lLqOp  Q2h]uv"5-ˣHO\~l|8c<5C{n1yHfÖOˊ}TdNa{)ʫ|aGp3t7Q1XDE3˚ hH?ń87N^(g8BT8euV<2(C98f.죰Qx.e0(׺: i=}-:=;?bX+㿮fTּ}ӏyg|ij!ۡjfR'Ws'E39ǀw=&LJ.QʇnӧMbମ6jY brFFam2i2[!-tt+Z']\5[ #WM!3!׷LbgPoL50J͎`6pܝ7Wss~ߗT;mm`k]'cT=.cu[7VHSՄ}-%Ac2GCsrR jƑS蜓YG`JD kc.tr/1nt[KUI+H$~o9.$w|^OomN-EuxiZ )))$'0+~"/$? # vV hk"HH?VXNccOJ5yI"HH?DkV0 iC&%F !,FPM]IJAą!+TPz-{Mų^n.Λo_S}q=Gπp[~6o߼nW/o ߿ynxn3>}l0]sqrߵ ~{ҳ qn[nZ'N`3ngLTR-2Lʩ@gr֚&nm- d,ɉ.9XN\l&CJGrc/\ udIC֖BHC1j7׾/CȮ@Je,/}(jn-͒Pe.RF2@#*n,n-Ies)dOzRzCDQI)=7W!dB@&xA nLRI7Ǜ%Zf;*kٽ?٧r˔IENDB`trimage-1.0.5/website/index.html0000644000175000017500000001601211447647147016123 0ustar kiliankilian Trimage (lossless) image compressor

Trimage image compressor – 1.0.5

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

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

Trimage in action

a screenshot of Trimage

Download

Ubuntu

If you're on maveric, karmic or lucid, type the following commands into your console:

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

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

Arch Linux

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

Other *nix

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

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

Mac

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

Windows

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

Repositories

Git: Trimage is primarily developed on GitHub.

Bzr: Trimage is also available on Launchpad.

Trimage is MIT licenced. We encourage contributions via GitHub.

Thanks

The following people helped develop Trimage:

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

Donate

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

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

Requirements

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

Command line options

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

Help

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

Planned features

Version 1.1.0 final:

  • Expand command line options
  • General refactoring

Version 1.1.1 final:

  • Use multiprocessing instead of threads

Beyond that

  • Deletion of rows in the table view
  • Integration with online services such as punypng
  • Suggest something
trimage-1.0.5/website/linux.png0000644000175000017500000000222311355354335015762 0ustar kiliankilianPNG  IHDRr ߔZIDATxڵ{L[UmBˣx`*0cl &&D,h0,&0bM!830qy` 5}pͽKoi~9K[LLjZ>`EEE~PBF#.AHHRM %%%xp: 0{ߵ&F&e>ZHqqpuf%b2 }zm q/_E`<4!\P9s_n a,aN 9`0P]] Nw.GZj_'xpzzhooGRR4BngXDFF"!!A~ي/)66dM477K:RRR^MقHX^^GO^/KCɤB9H>j!e||\/7#]O8Tu4{zzS'>>^{ooelPp8齱Z, yxQ|{j\"g6>L8آf#_¨#k7/=[ƾe TrwuWN0q0wQ `u`/3*8>Zathi$S8OH7gK̚[q amL2SށU1 [OF)Ū0@$0SF&qSh;F\p7 28l^+U `(M`(  Xp%444VUk""!͔kF44~qHw礓r]n~Fӥ&'M/z-IEfdCuv& >[XjDg=If{0QL"yaz{򝬒_ 4f['[_{rY&/]& 厒3 dI$r&BKRd |NRѿ8 e w?u&\iyiYIENDB`trimage-1.0.5/website/mac.png0000644000175000017500000000157311355354335015372 0ustar kiliankilianPNG  IHDRE/wPLTE|rݓܼTzQe[,.3 Kr4Df䪴KMSujBkHRi󄊜imefh4;K`դ잾legkxSs]tx飬V~Ѿe}񆆇`xzoptFo[]bI_ͷW}TzOwSv#%*qح>Q{vn׎ꕞp挐쯼48tRNS@fIDATx} OPaZ)v*7)C˔:!.& l`T@ES0,iONsmO{RbA凥N6&ey}O[!<|ݬE,(GDZP9JQ~e8XLglW(}TDjӴE))4FYҶXj@0i@d"XwP:^EC< image/svg+xml trimage-1.0.5/desktop/trimage.desktop0000755000175000017500000000033311352215436017145 0ustar kiliankilian[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=GNOME;GTK;Graphics; StartupNotify=true trimage-1.0.5/resources/window.ui0000644000175000017500000001707411355366332016345 0ustar kiliankilian Kilian Valkhof trimage 0 0 600 170 Trimage image compressor 0 0 true 1 1 0 0 0 0 0 10 9 PointingHandCursor Add file to the compression list &Add and compress list-add.pnglist-add.png Alt+A 8 QFrame::Plain Drag and drop images onto the table 1 10 Qt::Horizontal 498 20 9 PointingHandCursor Recompress all images &Recompress view-refresh.pngview-refresh.png Alt+R false true true Drag files in here Drag files in here QFrame::NoFrame QFrame::Plain 0 0 Qt::ScrollBarAlwaysOff true true QAbstractItemView::DropOnly true Qt::ElideRight true Qt::NoPen true false false false false 10 10 true true true trimage-1.0.5/resources/trimage.svg0000644000175000017500000002244611352215436016642 0ustar kiliankilian image/svg+xml trimage-1.0.5/resources/todo0000644000175000017500000000312711447645016015362 0ustar kiliankilian========================================== todo app wise - general refactoring - sys.exit(1) for errors -- how to handle? Not good to simply sys.exit() from any random part of code (can leave things in a mess) - consider context managers for handling compression, so as to keep operations atomic and/or rollback-able - add a recursive option on the command-line for use with -d - make -f accept a list of files - make the current verbose be "normal", and make -verbose print the commandline app prints as well - find a way to specify the version once for everywhere - notification area drag/drop widget -> probably need gtk for gnome todo else - figure out how to make mac and win versions (someone else :) <- via gui2exe =========================================== later versions: animate compressing.gif allow selection/deletion of rows from table (and subsequently the imagelist) punypng api? http://www.gracepointafterfive.com/punypng/api imagemagick/graphicsmagick? always on top option intelligently recompress, i.e. go through the list of files, recompress each until no more gains are seen (and a sensible number-of-tries limit isn't exceeded), and flag that file as fully-optimised. Repeat for each file in the list, until all are done. Saves pointlessly trying to optimise files. Consider the case of a directory of 100 files, already optimised once. Recompressing maximally compresses 90. Recompressing again would currently try to recompress all 100, when only 10 would be worthy of trying to compress further. trimage-1.0.5/src/trimage/ui.py0000644000175000017500000001540011363617647015671 0ustar kiliankilianfrom PyQt4.QtCore import * from PyQt4.QtGui import * from os import path class TrimageTableView(QTableView): """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().hasUrls: event.accept() else: event.ignore() def dragMoveEvent(self, event): event.accept() def dropEvent(self, event): event.accept() filelist = [] for url in event.mimeData().urls(): filelist.append(unicode(url.toLocalFile())) self.emit(SIGNAL("fileDropEvent"), (filelist)) class Ui_trimage(object): 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) 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.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.widget = QWidget(self.centralwidget) self.widget.setEnabled(True) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.widget.sizePolicy().hasHeightForWidth()) self.widget.setSizePolicy(sizePolicy) self.widget.setObjectName("widget") self.verticalLayout = QVBoxLayout(self.widget) self.verticalLayout.setSpacing(0) self.verticalLayout.setMargin(0) self.verticalLayout.setObjectName("verticalLayout") self.frame = QFrame(self.widget) self.frame.setObjectName("frame") self.verticalLayout_2 = QVBoxLayout(self.frame) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setMargin(0) self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) self.horizontalLayout.setMargin(10) self.horizontalLayout.setObjectName("horizontalLayout") self.addfiles = QPushButton(self.frame) font = QFont() font.setPointSize(9) self.addfiles.setFont(font) self.addfiles.setCursor(Qt.PointingHandCursor) icon = QIcon() 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) self.horizontalLayout.addWidget(self.addfiles) self.label = QLabel(self.frame) font = QFont() font.setPointSize(8) self.label.setFont(font) self.label.setFrameShadow(QFrame.Plain) self.label.setMargin(1) self.label.setIndent(10) self.label.setObjectName("label") self.horizontalLayout.addWidget(self.label) spacerItem = QSpacerItem(498, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.recompress = QPushButton(self.frame) font = QFont() font.setPointSize(9) self.recompress.setFont(font) self.recompress.setCursor(Qt.PointingHandCursor) icon1 = QIcon() icon1.addPixmap(QPixmap(self.get_image("pixmaps/view-refresh.png")), QIcon.Normal, QIcon.Off) self.recompress.setIcon(icon1) self.recompress.setCheckable(False) self.recompress.setObjectName("recompress") self.horizontalLayout.addWidget(self.recompress) self.verticalLayout_2.addLayout(self.horizontalLayout) self.processedfiles = TrimageTableView(self.frame) self.processedfiles.setEnabled(True) self.processedfiles.setFrameShape(QFrame.NoFrame) self.processedfiles.setFrameShadow(QFrame.Plain) self.processedfiles.setLineWidth(0) self.processedfiles.setMidLineWidth(0) self.processedfiles.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.processedfiles.setTabKeyNavigation(True) self.processedfiles.setAlternatingRowColors(True) self.processedfiles.setTextElideMode(Qt.ElideRight) self.processedfiles.setShowGrid(True) self.processedfiles.setGridStyle(Qt.NoPen) self.processedfiles.setSortingEnabled(False) self.processedfiles.setObjectName("processedfiles") self.processedfiles.resizeColumnsToContents() self.processedfiles.setSelectionMode(QAbstractItemView.NoSelection) self.verticalLayout_2.addWidget(self.processedfiles) self.verticalLayout.addWidget(self.frame) self.gridLayout_2.addWidget(self.widget, 0, 0, 1, 1) trimage.setCentralWidget(self.centralwidget) self.retranslateUi(trimage) 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)) self.addfiles.setToolTip(QApplication.translate("trimage", "Add file to the compression list", None, QApplication.UnicodeUTF8)) self.addfiles.setText(QApplication.translate("trimage", "&Add and compress", None, QApplication.UnicodeUTF8)) self.addfiles.setShortcut(QApplication.translate("trimage", "Alt+A", None, QApplication.UnicodeUTF8)) self.label.setText(QApplication.translate("trimage", "Drag and drop images onto the table", None, QApplication.UnicodeUTF8)) self.recompress.setToolTip(QApplication.translate("trimage", "Recompress all images", None, QApplication.UnicodeUTF8)) self.recompress.setText(QApplication.translate("trimage", "&Recompress", None, QApplication.UnicodeUTF8)) self.recompress.setShortcut(QApplication.translate("trimage", "Alt+R", None, QApplication.UnicodeUTF8)) self.processedfiles.setToolTip(QApplication.translate("trimage", "Drag files in here", None, QApplication.UnicodeUTF8)) self.processedfiles.setWhatsThis(QApplication.translate("trimage", "Drag files in here", None, QApplication.UnicodeUTF8)) trimage-1.0.5/src/trimage/__init__.py0000644000175000017500000000000011352215436016765 0ustar kiliankiliantrimage-1.0.5/src/trimage/trimage.py0000644000175000017500000004647411447645016016715 0ustar kiliankilian#!/usr/bin/python import time import sys import errno from os import listdir from os import path from os import remove from os import access from os import W_OK as WRITEABLE from shutil import copy from subprocess import call, PIPE from optparse import OptionParser from PyQt4.QtCore import * from PyQt4.QtGui import * from filesize import * from imghdr import what as determinetype from Queue import Queue from ThreadPool import ThreadPool from multiprocessing import cpu_count from ui import Ui_trimage VERSION = "1.0.5" 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 = [] QCoreApplication.setOrganizationName("Kilian Valkhof") QCoreApplication.setOrganizationDomain("trimage.org") QCoreApplication.setApplicationName("Trimage") self.settings = QSettings() self.restoreGeometry(self.settings.value("geometry").toByteArray()) # 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) 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): self.cli = False """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() # make sure we quit after processing finished if using cli if options.filename or options.directory: QObject.connect(self.thread, SIGNAL("finished()"), quit) self.cli = True # send to correct function if options.filename: self.file_from_cmd(options.filename.decode("utf-8")) if options.directory: self.dir_from_cmd(options.directory.decode("utf-8")) 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) fd.restoreState(self.settings.value("fdstate").toByteArray()) directory = self.settings.value("directory", QVariant("")).toString() 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(unicode(images[0])))) self.delegator([unicode(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 = (i.image for i in self.imagelist if i.image.fullpath == fullpath).next() 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 [".",".."]]: 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 >> sys.stderr, u"[error] %s not a supported image file and/or not writeable" % image.fullpath """ 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 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 checkapps(self): """Check if the required command line apps exist.""" exe = ".exe" if (sys.platform == "win32") else "" status = False retcode = self.safe_call("jpegoptim" + exe + " --version") if retcode != 0: status = True sys.stderr.write("[error] please install jpegoptim") retcode = self.safe_call("optipng" + exe + " -v") if retcode != 0: status = True sys.stderr.write("[error] please install optipng") retcode = self.safe_call("advpng" + exe + " --version") if retcode != 0: status = True sys.stderr.write("[error] please install advancecomp") retcode = self.safe_call("pngcrush" + exe + " -version") if retcode != 0: status = True sys.stderr.write("[error] please install pngcrush") return status def safe_call(self, command): """ cross-platform command-line check """ while True: try: return call(command, shell=True, stdout=PIPE) except OSError, e: if e.errno == errno.EINTR: continue else: raise 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 = { 'shortname': lambda i: self.statusStr() % i.shortname, 'oldfilesizestr': lambda i: size(i.oldfilesize, system=alternative) if i.compressed else "", 'newfilesizestr': lambda i: size(i.newfilesize, system=alternative) if i.compressed else "", 'ratiostr': lambda i: "%.1f%%" % (100 - (float(i.newfilesize) / i.oldfilesize * 100)) if i.compressed else "", 'icon': lambda i: i.icon if i.compressed else waitingIcon, 'fullpath': lambda i: i.fullpath, #only used by cli } names = ['shortname', '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: %s" if self.image.compressing: message = "Compressing %s..." return message if not self.image.compressed and self.image.recompression: return "Queued for recompression..." if not self.image.compressed: return "Queued..." return "%s" 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 if path.isfile(self.fullpath) and access(self.fullpath, WRITEABLE): self.filetype = determinetype(self.fullpath) if self.filetype in ["jpeg", "png"]: oldfile = QFileInfo(self.fullpath) self.shortname = oldfile.fileName() self.oldfilesize = oldfile.size() self.icon = QIcon(self.fullpath) self.valid = True #def _determinetype(self): # """ Determine the filetype of the file using imghdr. """ # filetype = determinetype(self.fullpath) # if filetype in ["jpeg", "png"]: # self.filetype = filetype # else: # self.filetype = None # return self.filetype def reset(self): self.failed = False self.compressed = False 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 exe = ".exe" if (sys.platform == "win32") else "" runString = { "jpeg": u"jpegoptim" + exe + " -f --strip-all '%(file)s'", "png": u"optipng" + exe + " -force -o7 '%(file)s'&&advpng" + exe + " -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s'" } # Create a backup file copy(self.fullpath, self.fullpath + '~') 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(self.fullpath + '~', self.fullpath) self.newfilesize = self.oldfilesize # Removes the backup file remove(self.fullpath + '~') else: self.failed = True self.compressing = False self.retcode = retcode return self class Worker(QThread): def __init__(self, parent=None): QThread.__init__(self, parent) self.toDisplay = Queue() self.threadpool = ThreadPool(max_workers=cpu_count()) def __del__(self): self.threadpool.shutdown() def compress_file(self, images, showapp, verbose, imagelist): """Start the worker thread.""" for image in images: #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.emit(SIGNAL("updateUi")) 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 >> sys.stderr, u"[error] %s could not be compressed" % image.fullpath 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) QObject.connect(self.quitAction, SIGNAL("triggered()"), qApp, SLOT("quit()")) self.addFiles = QAction(self.tr("&Add and compress"), self) icon = QIcon() icon.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/list-add.png"))), QIcon.Normal, QIcon.Off) self.addFiles.setIcon(icon) QObject.connect(self.addFiles, SIGNAL("triggered()"), self.parent.file_dialog) self.recompress = QAction(self.tr("&Recompress"), self) icon2 = QIcon() icon2.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/view-refresh.png"))), QIcon.Normal, QIcon.Off) self.recompress.setIcon(icon2) self.recompress.setDisabled(True) QObject.connect(self.addFiles, SIGNAL("triggered()"), self.parent.recompress_files) self.hideMain = QAction(self.tr("&Hide window"), self) QObject.connect(self.hideMain, SIGNAL("triggered()"), self.parent.hide_main_window) def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.addFiles) self.trayIconMenu.addAction(self.recompress) self.trayIconMenu.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 = StartQT4() if myapp.showapp: myapp.show() sys.exit(app.exec_()) trimage-1.0.5/src/trimage/pixmaps/compressing.gif0000644000175000017500000000347111352215436021374 0ustar kiliankilianGIF89aVg7)Hu! NETSCAPE2.0!Created with ajaxload.info! ,w  !DBAH¬aD@ ^AXP@"UQ# B\; 1 o:2$v@ $|,3 _# d53" s5 e!! ,v i@e9DAA/`ph$Ca%@ pHxFuSx# .݄YfL_" p 3BW ]|L \6{|z87[7!! ,x  e9DE"2r,qPj`8@8bH, *0- mFW9LPE3+ (B"  f{*BW_/ @_$~Kr7Ar7!! ,v 4e9!H"* Q/@-4ép4R+-pȧ`P(6᠝U/  *,)(+/]"lO/*Ak K]A~666!! ,l ie9"* -80H=N; TEqe UoK2_WZ݌V1jgWe@tuH//w`?f~#6#!! ,~ ,e9"* ; pR%#0` 'c(J@@/1i4`VBV u}"caNi/ ] ))-Lel  mi} me[+!! ,y Ie9"M6*¨"7E͖@G((L&pqj@Z %@wZ) pl( ԭqu*R&c `))( s_J>_\'Gm7$+!! ,w Ie9*, (*(B5[1 ZIah!GexzJ0e6@V|U4Dm%$͛p \Gx }@+| =+ 1- Ea5l)+!! ,y )䨞'AKڍ,E\(l&;5 5D03a0--ÃpH4V % i p[R"| #  6iZwcw*!! ,y )䨞,K*0 a;׋аY8b`4n ¨Bbbx,( Ƚ  % >  2*i* /:+$v*!! ,u )䨞l[$ Jq[q 3`Q[5:IX!0rAD8 CvHPfiiQAP@pC %D PQ46  iciNj0w )#!! ,y ). q ,G Jr(J8 C*B,&< h W~-`, ,>; 8RN<, <1T] c' qk$ @)#!;trimage-1.0.5/src/trimage/pixmaps/trimage-icon.png0000644000175000017500000001067611363622645021453 0ustar kiliankilianPNG  IHDR@@iqsRGB pHYs5tIME ýobKGD>IDATxݛ{]Uuǿksw&w&Lf& IVdS[`QQ,EB)|P+R|R>'EZ)F" y}Ԁ93w=ܳ~(‡zZhZj/3%p9yXuEk}Ƚ*xYuYuU<+>kGA~1cbUu}"+NRRjZ{}^p:\hHk}iDYuKOR `Ub,e/kZM m.Z y؛vGHݧh_.RVeXc΢>$0^0(ޗGk*/!pD}^ry}S@!tN~T"=(o+ Z>!Q@+Pa|jPk-{ ZqJ:W*Cq ~N~"*PZ 4bhVYX ǂ|XQ(6Tx ` "ƪz 4wZ#hu]_jOĒ90ds1e@rb%ccwߪ@+X uoR~;$^|hO<Z> * Q%9 U5a!.õֵ12Y҃&@[,w: kЮBͱ:p Z| WIoE*}`Eq鮋4 )XllV0.Մz8O*3^Ahn$N)C{"r. WU\MŻj76jzĚEFV޺1+u< Fط % 6u̽(hϲks2\ 8Zd3­tِQ>D$ 50m @ꎙ R> 30և?}JZ7O3Q3vX%Y}o1f f201SSeȼEzg,Xb&_FSXϽ"7sjL M4b& L05SfTj?۹O`r)^#(%H^kj8dʲyq.>b߃0" B_t<+hjxӔW{TA+[Dw8 ݷ`K &36#W30dg2 m _aޫ G_~ElOO0 JX2kT+5%! }PkZEs`u$71camЭ ܔ<J9p.l^02PƸ@a(2c@VQod`O# 4:`DWu5۪|}煓W Hh.^Z|6xX"IRƮ LzF4jnF_]fBNJn& p lzGֹQ&`ςT"b:͚!L.gV5b9Chœa3[Ö/W4Ԇ0& Lf$pQP)\aAPR"PJY#an+b:g\7P%SbueC&wP\ SFzp2u)]2Knzc8`~_jDQ\8f^.Q`bng/D=P# ʎrC$hE{ǂ91Vw!0T"A( bvU1>QY]!;u"onl{V)湝`xFzQ$,r?i$ lq^X7(lWhdB\z s ã;g)sX⣽IgWR'FsBa)?QtLЃŰ02hTXwSs `j.wK28$A?9!C,3Z5aQ4b"nvL h:IezS`|o 0Q)XC:õbɮ_<5E%}[0P eOD18&eƫLe|g* m_HFUja #LQ^( FGOg[NCI37-*z{,ed1͉9͋K_19XaZĆR`gʅSQ/D.D*S Q{DžxM$7:*s$1IpFncWdPIW\C‹.ߌ_/"jU0VRÜʅX w)$I. F~~i?4Pi#D Tܼ2Cc|G_ss-E"A4=eлÜ)b=V@7fm%~o|*[.mS,͚Χ (Xmys@mG4*R=2vw@4bop dI|rhYJ+:RĂӆ'.݃xP+Wb[-kfdwc7Uuv=x8I׹f u|2ɯ&1I7r0rGŞQ R.$oř^rh2w $9x>py{۾L3O6Le{(_F%B+f, ~C]QAϚAA4{"5$I>>!zx o6?LOMe y:ZyQU4"4 %;$ȥFKjQi'I^xtbw?O=l6?RځG6:UЪMUbNU+t]Z cfy:$}I"lj_|oko'|c<=%ͼuGȂKrW/{)L IlN`ƀ_򫟚;/^7|˸Gw)ө՟w45z{9/:[ua/QnG= CZIO#e$$IX7=5ymȥ;SO:/<[oI2c3nT]0]āRTŀ;$9Ӂx=߼FṼs_e1l{q)һ:|peCi=IxԽr#v+8gɏ\ws+o`*5{t֕g=6m@fsoK/&g7m|:,~u8?la*&ngwo#w$yg$$KgzM?vM|sg~],+]oG[ =Y`<$$9-I]O5񾻲n_g3NWO7̻O{p?$k $:I$Y4び3w>lv?#p'IENDB`trimage-1.0.5/src/trimage/pixmaps/view-refresh.png0000644000175000017500000000371511363622644021476 0ustar kiliankilianPNG  IHDR szzsBIT|dIDATXWYl2g Nyꕎg=w`3qZD2WPe3e`]+?p]-39ylU*ڦ<ǝgD`]ӑ:]B +EЯA8R4l' &K7FR80-]ǁƾx'[6i kjWW~:ŕD Ip e֏p(!q}>uvmu$c ~>E'lMK`]s6~[2H q$ %A )KDW9\IMT{L5!?n v2 (w3~шoV (B̯!AخH$ؒ`.guZ[SDepn3~Ԧpk[Ӝ10"Ix~\t~{T"mH-$Iu;e 2Wt-|};Gww#9=V G.`+jyR WIŜI,[,݆=/J9څEDŽ~_GO.Jp㯬\YY;OMmHIg0~ij{ya Ne ɠ‚ VG y7p1v j\;QPP]QXs`fB2 ˁ|87ܾV>Tn)ؤ"` ņ`v8wO8O)f{AWi ӊ $qd*8&ә.[Lt]{r:֭]ͮ GCu`*8CѺ'^]tPís&~r%0g;uAA*tǎQ}v׼YʮJH-?-72x[[gF~Un}Ka|̈p7.qVI#4ޞr\g;?-.sju[ǬU0Yinc^ K{Uնf5!PPeqsr:X*):cs6' ?y6&85~%x7)i?*3ZOYkZ,+c%P3^ D3]BtBtXJ.S/}<|o/79n;ZOF{7T&<>'ϗԁ7#c'3g?p܄+%tU W!%o bi]Eg\n8u7*ϋ[}w<"ƭdF XkƬ̱ k;P)@HDDEH G93TD0ܽ| +]E %`rwcO>66IENDB`trimage-1.0.5/src/trimage/filesize/filesize.py0000644000175000017500000000426011404666220020665 0ustar kiliankilian''' hurry.filesize @author: Martijn Faassen, Startifact @license: ZPL 2.1 ''' traditional = [ (1024 ** 5, 'P'), (1024 ** 4, 'T'), (1024 ** 3, 'G'), (1024 ** 2, 'M'), (1024 ** 1, 'K'), (1024 ** 0, 'B'), ] alternative = [ (1024 ** 5, ' PB'), (1024 ** 4, ' TB'), (1024 ** 3, ' GB'), (1024 ** 2, ' MB'), (1024 ** 1, ' KB'), (1024 ** 0, (' byte', ' bytes')), ] verbose = [ (1024 ** 5, (' petabyte', ' petabytes')), (1024 ** 4, (' terabyte', ' terabytes')), (1024 ** 3, (' gigabyte', ' gigabytes')), (1024 ** 2, (' megabyte', ' megabytes')), (1024 ** 1, (' kilobyte', ' kilobytes')), (1024 ** 0, (' byte', ' bytes')), ] iec = [ (1024 ** 5, 'Pi'), (1024 ** 4, 'Ti'), (1024 ** 3, 'Gi'), (1024 ** 2, 'Mi'), (1024 ** 1, 'Ki'), (1024 ** 0, ''), ] si = [ (1000 ** 5, 'P'), (1000 ** 4, 'T'), (1000 ** 3, 'G'), (1000 ** 2, 'M'), (1000 ** 1, 'K'), (1000 ** 0, 'B'), ] def size(bytes, system=traditional): """Human-readable file size. Using the traditional system, where a factor of 1024 is used:: >>> size(10) '10B' >>> size(100) '100B' >>> size(1000) '1000B' >>> size(2000) '1K' >>> size(10000) '9K' >>> size(20000) '19K' >>> size(100000) '97K' >>> size(200000) '195K' >>> size(1000000) '976K' >>> size(2000000) '1M' Using the SI system, with a factor 1000:: >>> size(10, system=si) '10B' >>> size(100, system=si) '100B' >>> size(1000, system=si) '1K' >>> size(2000, system=si) '2K' >>> size(10000, system=si) '10K' >>> size(20000, system=si) '20K' >>> size(100000, system=si) '100K' >>> size(200000, system=si) '200K' >>> size(1000000, system=si) '1M' >>> size(2000000, system=si) '2M' """ for factor, suffix in system: if bytes >= factor: break amount = int(bytes/factor) if isinstance(suffix, tuple): singular, multiple = suffix if amount == 1: suffix = singular else: suffix = multiple return str(amount) + suffix trimage-1.0.5/src/trimage/filesize/__init__.py0000644000175000017500000000013211363551006020603 0ustar kiliankilianfrom filesize import size from filesize import traditional, alternative, verbose, iec, si trimage-1.0.5/src/trimage/filesize/README.txt0000644000175000017500000000220611404666104020176 0ustar kiliankilianhurry.filesize ============== hurry.filesize a simple Python library that can take a number of bytes and returns a human-readable string with the size in it, in kilobytes (K), megabytes (M), etc. The default system it uses is "traditional", where multipliers of 1024 increase the unit size:: >>> from hurry.filesize import size >>> size(1024) '1K' An alternative, slightly more verbose system:: >>> from hurry.filesize import alternative >>> size(1, system=alternative) '1 byte' >>> size(10, system=alternative) '10 bytes' >>> size(1024, system=alternative) '1 KB' A verbose system:: >>> from hurry.filesize import verbose >>> size(10, system=verbose) '10 bytes' >>> size(1024, system=verbose) '1 kilobyte' >>> size(2000, system=verbose) '1 kilobyte' >>> size(3000, system=verbose) '2 kilobytes' >>> size(1024 * 1024, system=verbose) '1 megabyte' >>> size(1024 * 1024 * 3, system=verbose) '3 megabytes' You can also use the SI system, where multipliers of 1000 increase the unit size:: >>> from hurry.filesize import si >>> size(1000, system=si) '1K' http://pypi.python.org/pypi/hurry.filesize trimage-1.0.5/src/trimage/filesize/PKG-INFO0000644000175000017500000000400711156034006017570 0ustar kiliankilianMetadata-Version: 1.0 Name: hurry.filesize Version: 0.9 Summary: A simple Python library for human readable file sizes (or anything sized in bytes). Home-page: UNKNOWN Author: Martijn Faassen, Startifact Author-email: faassen@startifact.com License: ZPL 2.1 Description: hurry.filesize ============== hurry.filesize a simple Python library that can take a number of bytes and returns a human-readable string with the size in it, in kilobytes (K), megabytes (M), etc. The default system it uses is "traditional", where multipliers of 1024 increase the unit size:: >>> from hurry.filesize import size >>> size(1024) '1K' An alternative, slightly more verbose system:: >>> from hurry.filesize import alternative >>> size(1, system=alternative) '1 byte' >>> size(10, system=alternative) '10 bytes' >>> size(1024, system=alternative) '1 KB' A verbose system:: >>> from hurry.filesize import verbose >>> size(10, system=verbose) '10 bytes' >>> size(1024, system=verbose) '1 kilobyte' >>> size(2000, system=verbose) '1 kilobyte' >>> size(3000, system=verbose) '2 kilobytes' >>> size(1024 * 1024, system=verbose) '1 megabyte' >>> size(1024 * 1024 * 3, system=verbose) '3 megabytes' You can also use the SI system, where multipliers of 1000 increase the unit size:: >>> from hurry.filesize import si >>> size(1000, system=si) '1K' Changes ======= 0.9 (2009-03-11) ---------------- * Initial public release. Download ======== Keywords: file size bytes Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules trimage-1.0.5/src/trimage/ThreadPool/__init__.py0000644000175000017500000000006211360644565021046 0ustar kiliankilianfrom ThreadPool import ThreadPool, ThreadPoolMixIntrimage-1.0.5/src/trimage/ThreadPool/ThreadPool.py0000644000175000017500000002246011360644565021356 0ustar kiliankilian''' ThreadPool Implementation @author: Morten Holdflod Moeller - morten@holdflod.dk @license: LGPL v3 ''' from __future__ import with_statement from threading import Thread, RLock from time import sleep from Queue import Queue, Empty import logging import sys class NullHandler(logging.Handler): def emit(self, record): pass h = sys.stderr logging.getLogger('threadpool').addHandler(h) logging.getLogger('threadpool.worker').addHandler(h) class ThreadPoolMixIn: """Mix-in class to handle each request in a new thread from the ThreadPool.""" def __init__(self, threadpool=None): if (threadpool == None): threadpool = ThreadPool() self.__private_threadpool = True else: self.__private_threadpool = False self.__threadpool = threadpool def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. """ try: self.finish_request(request, client_address) self.close_request(request) except: self.handle_error(request, client_address) #IGNORE:W0702 self.close_request(request) def process_request(self, request, client_address): self.__threadpool.add_job(self.process_request_thread, [request, client_address]) def shutdown(self): if (self.__private_threadpool): self.__threadpool.shutdown() class AddJobException(Exception): ''' Exceptoion raised when a Job could not be added to the queue ''' def __init__(self, msg): Exception.__init__(self, msg) class ThreadPool: ''' The class implementing the ThreadPool. Instantiate and add jobs using add_job(func, args_list) ''' class Job: #IGNORE:R0903 ''' Class encapsulating a job to be handled by ThreadPool workers ''' def __init__(self, function, args, return_callback=None): self.callable = function self.arguments = args self.return_callback = return_callback def execute(self): ''' Called to execute the function ''' try: return_value = self.callable(*self.arguments) #IGNORE:W0142 except Exception, excep: #IGNORE:W0703 logger = logging.getLogger("threadpool.worker") logger.warning("A job in the ThreadPool raised an exception: " + excep) #else do nothing cause we don't know what to do... return try: if (self.return_callback != None): self.return_callback(return_value) except Exception, _: #IGNORE:W0703 everything could go wrong... logger = logging.getLogger('threadpool') logger.warning('Error while delivering return value to callback function') class Worker(Thread): ''' A worker thread handling jobs in the thread pool job queue ''' def __init__(self, pool): Thread.__init__(self) if (not isinstance(pool, ThreadPool)): raise TypeError("pool is not a ThreadPool instance") self.pool = pool self.alive = True self.start() def run(self): ''' The workers main-loop getting jobs from queue and executing them ''' while self.alive: #print self.pool.__active_worker_count, self.pool.__worker_count job = self.pool.get_job() if (job != None): self.pool.worker_active() job.execute() self.pool.worker_inactive() else: self.alive = False self.pool.punch_out() def __init__(self, max_workers = 5, kill_workers_after = 3): if (not isinstance(max_workers, int)): raise TypeError("max_workers is not an int") if (max_workers < 1): raise ValueError('max_workers must be >= 1') if (not isinstance(kill_workers_after, int)): raise TypeError("kill_workers_after is not an int") self.__max_workers = max_workers self.__kill_workers_after = kill_workers_after # This Queue is assumed Thread Safe self.__jobs = Queue() self.__worker_count_lock = RLock() self.__worker_count = 0 self.__active_worker_count = 0 self.__shutting_down = False logger = logging.getLogger('threadpool') logger.info('started') def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5): if (not isinstance(clean_shutdown_reties, int)): raise TypeError("clean_shutdown_reties is not an int") if (not clean_shutdown_reties >= 0): raise ValueError('clean_shutdown_reties must be >= 0') if (not isinstance(wait_for_workers_period, int)): raise TypeError("wait_for_workers_period is not an int") if (not wait_for_workers_period >= 0): raise ValueError('wait_for_workers_period must be >= 0') logger = logging.getLogger("threadpool") logger.info("shutting down") with self.__worker_count_lock: self.__shutting_down = True self.__max_workers = 0 self.__kill_workers_after = 0 retries_left = clean_shutdown_reties while (retries_left > 0): with self.__worker_count_lock: logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count)) if (self.__worker_count > 0): retries_left -= 1 else: retries_left = 0 sleep(wait_for_workers_period) with self.__worker_count_lock: if (self.__worker_count > 0): logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count) clean_shutdown = False else: clean_shutdown = True logger.info("shutdown complete") return clean_shutdown def punch_out(self): ''' Called by worker to update worker count when the worker is shutting down ''' with self.__worker_count_lock: self.__worker_count -= 1 def __new_worker(self): ''' Adding a new worker thread to the thread pool ''' with self.__worker_count_lock: ThreadPool.Worker(self) self.__worker_count += 1 def worker_active(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count + 1 def worker_inactive(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count - 1 def add_job(self, function, args = None, return_callback=None): ''' Put new job into queue ''' if (not callable(function)): raise TypeError("function is not a callable") if (not ( args == None or isinstance(args, list))): raise TypeError("args is not a list") if (not (return_callback == None or callable(return_callback))): raise TypeError("return_callback is not a callable") if (args == None): args = [] job = ThreadPool.Job(function, args, return_callback) with self.__worker_count_lock: if (self.__shutting_down): raise AddJobException("ThreadPool is shutting down") try: start_new_worker = False if (self.__worker_count < self.__max_workers): if (self.__active_worker_count == self.__worker_count): start_new_worker = True self.__jobs.put(job) if (start_new_worker): self.__new_worker() except Exception: raise AddJobException("Could not add job") def get_job(self): ''' Retrieve next job from queue workers die (and should) when returning None ''' job = None try: if (self.__kill_workers_after < 0): job = self.__jobs.get(True) elif (self.__kill_workers_after == 0): job = self.__jobs.get(False) else: job = self.__jobs.get(True, self.__kill_workers_after) except Empty: job = None return job