summaryrefslogtreecommitdiffstats
path: root/tools/compress-pngs.py
blob: ed3e32a52564360f84073b38acf3cc328f7ca929 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python3
#
# compress-pngs.py - Compress PNGs
#
# By Gerald Combs <gerald@wireshark.org
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
'''Run various compression and optimization utilities on one or more PNGs'''

import argparse
import concurrent.futures
import shutil
import subprocess
import sys

PNG_FILE_ARG = '%PNG_FILE_ARG%'

def get_compressors():
    # Add *lossless* compressors here.
    compressors = {
        # https://github.com/shssoichiro/oxipng
        'oxipng': { 'args': ['--opt', 'max', '--strip', 'safe', PNG_FILE_ARG] },
        # http://optipng.sourceforge.net/
        'optipng': { 'args': ['-o3', '-quiet', PNG_FILE_ARG] },
        # https://github.com/amadvance/advancecomp
        'advpng': { 'args': ['--recompress', '--shrink-insane', PNG_FILE_ARG] },
        # https://github.com/amadvance/advancecomp
        'advdef': { 'args': ['--recompress', '--shrink-insane', PNG_FILE_ARG] },
        # https://pmt.sourceforge.io/pngcrush/
        'pngcrush': { 'args': ['-q', '-ow', '-brute', '-reduce', '-noforce', PNG_FILE_ARG, 'pngcrush.$$$$.png'] },
        # https://github.com/fhanau/Efficient-Compression-Tool
        'ect': { 'args': ['-5', '--mt-deflate', '--mt-file', '-strip', PNG_FILE_ARG]}
    }
    for compressor in compressors:
        compressor_path = shutil.which(compressor)
        if compressor_path:
            compressors[compressor]['path'] = compressor_path
    return compressors


def compress_png(png_file, compressors):
    for compressor in compressors:
        if not compressors[compressor].get('path', False):
            continue

        args = compressors[compressor]['args']
        args = [arg.replace(PNG_FILE_ARG, png_file) for arg in args]

        try:
            compress_proc = subprocess.run([compressor] + args)
        except Exception:
            print('{} returned {}:'.format(compressor, compress_proc.returncode))


def main():
    parser = argparse.ArgumentParser(description='Compress PNGs')
    parser.add_argument('--list', action='store_true',
                        help='List available compressors')
    parser.add_argument('png_files', nargs='*', metavar='png file', help='Files to compress')
    args = parser.parse_args()

    compressors = get_compressors()

    c_count = 0
    for compressor in compressors:
        if 'path' in compressors[compressor]:
            c_count += 1

    if c_count < 1:
        sys.stderr.write('No compressors found\n')
        sys.exit(1)

    if args.list:
        for compressor in compressors:
            path = compressors[compressor].get('path', 'Not found')
            print('{}: {}'.format(compressor, path))
        sys.exit(0)

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = []
        for png_file in args.png_files:
            print('Compressing {}'.format(png_file))
            futures.append(executor.submit(compress_png, png_file, compressors))
        concurrent.futures.wait(futures)


if __name__ == '__main__':
    main()