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