bro.py revision 291d21cc2ffe042fc3134d935e4f810184e82138
1#! /usr/bin/env python
2"""bro %s -- compression/decompression utility using the Brotli algorithm."""
3
4from __future__ import print_function
5import argparse
6import sys
7import os
8import brotli
9import platform
10
11
12# default values of encoder parameters
13DEFAULT_PARAMS = {
14    'mode': brotli.MODE_GENERIC,
15    'quality': 11,
16    'lgwin': 22,
17    'lgblock': 0,
18}
19
20
21def get_binary_stdio(stream):
22    """ Return the specified standard input, output or errors stream as a
23    'raw' buffer object suitable for reading/writing binary data from/to it.
24    """
25    assert stream in ['stdin', 'stdout', 'stderr'], "invalid stream name"
26    stdio = getattr(sys, stream)
27    if sys.version_info[0] < 3:
28        if sys.platform == 'win32':
29            # set I/O stream binary flag on python2.x (Windows)
30            runtime = platform.python_implementation()
31            if runtime == "PyPy":
32                # the msvcrt trick doesn't work in pypy, so I use fdopen
33                mode = "rb" if stream == "stdin" else "wb"
34                stdio = os.fdopen(stdio.fileno(), mode, 0)
35            else:
36                # this works with CPython -- untested on other implementations
37                import msvcrt
38                msvcrt.setmode(stdio.fileno(), os.O_BINARY)
39        return stdio
40    else:
41        # get 'buffer' attribute to read/write binary data on python3.x
42        if hasattr(stdio, 'buffer'):
43            return stdio.buffer
44        else:
45            orig_stdio = getattr(sys, "__%s__" % stream)
46            return orig_stdio.buffer
47
48
49def main(args=None):
50
51    parser = argparse.ArgumentParser(
52        prog='bro.py',
53        description="Compression/decompression utility using the Brotli algorithm.")
54    parser.add_argument('--version', action='version', version=brotli.__version__)
55    parser.add_argument('-i', '--input', metavar='FILE', type=str, dest='infile',
56                        help='Input file', default=None)
57    parser.add_argument('-o', '--output', metavar='FILE', type=str, dest='outfile',
58                        help='Output file', default=None)
59    parser.add_argument('-f', '--force', action='store_true',
60                        help='Overwrite existing output file', default=False)
61    parser.add_argument('-d', '--decompress', action='store_true',
62                        help='Decompress input file', default=False)
63    params = parser.add_argument_group('optional encoder parameters')
64    params.add_argument('-m', '--mode', metavar="MODE", type=int, choices=[0, 1, 2],
65                        help='The compression mode can be 0 for generic input, '
66                        '1 for UTF-8 encoded text, or 2 for WOFF 2.0 font data. '
67                        'Defaults to 0.')
68    params.add_argument('-q', '--quality', metavar="QUALITY", type=int,
69                        choices=list(range(0, 12)),
70                        help='Controls the compression-speed vs compression-density '
71                        'tradeoff. The higher the quality, the slower the '
72                        'compression. Range is 0 to 11. Defaults to 11.')
73    params.add_argument('--lgwin', metavar="LGWIN", type=int,
74                        choices=list(range(10, 25)),
75                        help='Base 2 logarithm of the sliding window size. Range is '
76                        '10 to 24. Defaults to 22.')
77    params.add_argument('--lgblock', metavar="LGBLOCK", type=int,
78                        choices=[0] + list(range(16, 25)),
79                        help='Base 2 logarithm of the maximum input block size. '
80                        'Range is 16 to 24. If set to 0, the value will be set based '
81                        'on the quality. Defaults to 0.')
82    # set default values using global DEFAULT_PARAMS dictionary
83    parser.set_defaults(**DEFAULT_PARAMS)
84
85    options = parser.parse_args(args=args)
86
87    if options.infile:
88        if not os.path.isfile(options.infile):
89            parser.error('file "%s" not found' % options.infile)
90        with open(options.infile, "rb") as infile:
91            data = infile.read()
92    else:
93        if sys.stdin.isatty():
94            # interactive console, just quit
95            parser.error('no input')
96        infile = get_binary_stdio('stdin')
97        data = infile.read()
98
99    if options.outfile:
100        if os.path.isfile(options.outfile) and not options.force:
101            parser.error('output file exists')
102        outfile = open(options.outfile, "wb")
103    else:
104        outfile = get_binary_stdio('stdout')
105
106    try:
107        if options.decompress:
108            data = brotli.decompress(data)
109        else:
110            data = brotli.compress(
111                data, mode=options.mode, quality=options.quality,
112                lgwin=options.lgwin, lgblock=options.lgblock)
113    except brotli.error as e:
114        parser.exit(1,'bro: error: %s: %s' % (e, options.infile or 'sys.stdin'))
115
116    outfile.write(data)
117    outfile.close()
118
119
120if __name__ == '__main__':
121    main()
122