3 # compress-pngs.py - Compress PNGs
5 # By Gerald Combs <gerald@wireshark.org
7 # SPDX-License-Identifier: GPL-2.0-or-later
9 '''Run various compression and optimization utilities on one or more PNGs'''
12 import concurrent
.futures
17 PNG_FILE_ARG
= '%PNG_FILE_ARG%'
19 def get_compressors():
20 # Add *lossless* compressors here.
22 # https://github.com/shssoichiro/oxipng
23 'oxipng': { 'args': ['--opt', 'max', '--strip', 'safe', PNG_FILE_ARG
] },
24 # http://optipng.sourceforge.net/
25 'optipng': { 'args': ['-o3', '-quiet', PNG_FILE_ARG
] },
26 # https://github.com/amadvance/advancecomp
27 'advpng': { 'args': ['--recompress', '--shrink-insane', PNG_FILE_ARG
] },
28 # https://github.com/amadvance/advancecomp
29 'advdef': { 'args': ['--recompress', '--shrink-insane', PNG_FILE_ARG
] },
30 # https://pmt.sourceforge.io/pngcrush/
31 'pngcrush': { 'args': ['-q', '-ow', '-brute', '-reduce', '-noforce', PNG_FILE_ARG
, 'pngcrush.$$$$.png'] },
32 # https://github.com/fhanau/Efficient-Compression-Tool
33 'ect': { 'args': ['-5', '--mt-deflate', '--mt-file', '-strip', PNG_FILE_ARG
]}
35 for compressor
in compressors
:
36 compressor_path
= shutil
.which(compressor
)
38 compressors
[compressor
]['path'] = compressor_path
42 def compress_png(png_file
, compressors
):
43 for compressor
in compressors
:
44 if not compressors
[compressor
].get('path', False):
47 args
= compressors
[compressor
]['args']
48 args
= [arg
.replace(PNG_FILE_ARG
, png_file
) for arg
in args
]
51 compress_proc
= subprocess
.run([compressor
] + args
)
53 print('{} returned {}:'.format(compressor
, compress_proc
.returncode
))
57 parser
= argparse
.ArgumentParser(description
='Compress PNGs')
58 parser
.add_argument('--list', action
='store_true',
59 help='List available compressors')
60 parser
.add_argument('png_files', nargs
='*', metavar
='png file', help='Files to compress')
61 args
= parser
.parse_args()
63 compressors
= get_compressors()
66 for compressor
in compressors
:
67 if 'path' in compressors
[compressor
]:
71 sys
.stderr
.write('No compressors found\n')
75 for compressor
in compressors
:
76 path
= compressors
[compressor
].get('path', 'Not found')
77 print('{}: {}'.format(compressor
, path
))
80 with concurrent
.futures
.ProcessPoolExecutor() as executor
:
82 for png_file
in args
.png_files
:
83 print('Compressing {}'.format(png_file
))
84 futures
.append(executor
.submit(compress_png
, png_file
, compressors
))
85 concurrent
.futures
.wait(futures
)
88 if __name__
== '__main__':