bro.py revision 26fa459c
126fa459cSmrg#! /usr/bin/env python
226fa459cSmrg"""Compression/decompression utility using the Brotli algorithm."""
326fa459cSmrg
426fa459cSmrgfrom __future__ import print_function
526fa459cSmrgimport argparse
626fa459cSmrgimport sys
726fa459cSmrgimport os
826fa459cSmrgimport platform
926fa459cSmrg
1026fa459cSmrgimport brotli
1126fa459cSmrg
1226fa459cSmrg# default values of encoder parameters
1326fa459cSmrgDEFAULT_PARAMS = {
1426fa459cSmrg    'mode': brotli.MODE_GENERIC,
1526fa459cSmrg    'quality': 11,
1626fa459cSmrg    'lgwin': 22,
1726fa459cSmrg    'lgblock': 0,
1826fa459cSmrg}
1926fa459cSmrg
2026fa459cSmrg
2126fa459cSmrgdef get_binary_stdio(stream):
2226fa459cSmrg    """ Return the specified standard input, output or errors stream as a
2326fa459cSmrg    'raw' buffer object suitable for reading/writing binary data from/to it.
2426fa459cSmrg    """
2526fa459cSmrg    assert stream in ['stdin', 'stdout', 'stderr'], 'invalid stream name'
2626fa459cSmrg    stdio = getattr(sys, stream)
2726fa459cSmrg    if sys.version_info[0] < 3:
2826fa459cSmrg        if sys.platform == 'win32':
2926fa459cSmrg            # set I/O stream binary flag on python2.x (Windows)
3026fa459cSmrg            runtime = platform.python_implementation()
3126fa459cSmrg            if runtime == 'PyPy':
3226fa459cSmrg                # the msvcrt trick doesn't work in pypy, so I use fdopen
3326fa459cSmrg                mode = 'rb' if stream == 'stdin' else 'wb'
3426fa459cSmrg                stdio = os.fdopen(stdio.fileno(), mode, 0)
3526fa459cSmrg            else:
3626fa459cSmrg                # this works with CPython -- untested on other implementations
3726fa459cSmrg                import msvcrt
3826fa459cSmrg                msvcrt.setmode(stdio.fileno(), os.O_BINARY)
3926fa459cSmrg        return stdio
4026fa459cSmrg    else:
4126fa459cSmrg        # get 'buffer' attribute to read/write binary data on python3.x
4226fa459cSmrg        if hasattr(stdio, 'buffer'):
4326fa459cSmrg            return stdio.buffer
4426fa459cSmrg        else:
4526fa459cSmrg            orig_stdio = getattr(sys, '__%s__' % stream)
4626fa459cSmrg            return orig_stdio.buffer
4726fa459cSmrg
4826fa459cSmrg
4926fa459cSmrgdef main(args=None):
5026fa459cSmrg
5126fa459cSmrg    parser = argparse.ArgumentParser(
5226fa459cSmrg        prog=os.path.basename(__file__), description=__doc__)
5326fa459cSmrg    parser.add_argument(
5426fa459cSmrg        '--version', action='version', version=brotli.__version__)
5526fa459cSmrg    parser.add_argument(
5626fa459cSmrg        '-i',
5726fa459cSmrg        '--input',
5826fa459cSmrg        metavar='FILE',
5926fa459cSmrg        type=str,
6026fa459cSmrg        dest='infile',
6126fa459cSmrg        help='Input file',
6226fa459cSmrg        default=None)
6326fa459cSmrg    parser.add_argument(
6426fa459cSmrg        '-o',
6526fa459cSmrg        '--output',
6626fa459cSmrg        metavar='FILE',
6726fa459cSmrg        type=str,
6826fa459cSmrg        dest='outfile',
6926fa459cSmrg        help='Output file',
7026fa459cSmrg        default=None)
7126fa459cSmrg    parser.add_argument(
7226fa459cSmrg        '-f',
7326fa459cSmrg        '--force',
7426fa459cSmrg        action='store_true',
7526fa459cSmrg        help='Overwrite existing output file',
7626fa459cSmrg        default=False)
7726fa459cSmrg    parser.add_argument(
7826fa459cSmrg        '-d',
7926fa459cSmrg        '--decompress',
8026fa459cSmrg        action='store_true',
8126fa459cSmrg        help='Decompress input file',
8226fa459cSmrg        default=False)
8326fa459cSmrg    params = parser.add_argument_group('optional encoder parameters')
8426fa459cSmrg    params.add_argument(
8526fa459cSmrg        '-m',
8626fa459cSmrg        '--mode',
8726fa459cSmrg        metavar='MODE',
8826fa459cSmrg        type=int,
8926fa459cSmrg        choices=[0, 1, 2],
9026fa459cSmrg        help='The compression mode can be 0 for generic input, '
9126fa459cSmrg        '1 for UTF-8 encoded text, or 2 for WOFF 2.0 font data. '
9226fa459cSmrg        'Defaults to 0.')
9326fa459cSmrg    params.add_argument(
9426fa459cSmrg        '-q',
9526fa459cSmrg        '--quality',
9626fa459cSmrg        metavar='QUALITY',
9726fa459cSmrg        type=int,
9826fa459cSmrg        choices=list(range(0, 12)),
9926fa459cSmrg        help='Controls the compression-speed vs compression-density '
10026fa459cSmrg        'tradeoff. The higher the quality, the slower the '
10126fa459cSmrg        'compression. Range is 0 to 11. Defaults to 11.')
10226fa459cSmrg    params.add_argument(
10326fa459cSmrg        '--lgwin',
10426fa459cSmrg        metavar='LGWIN',
10526fa459cSmrg        type=int,
10626fa459cSmrg        choices=list(range(10, 25)),
10726fa459cSmrg        help='Base 2 logarithm of the sliding window size. Range is '
10826fa459cSmrg        '10 to 24. Defaults to 22.')
10926fa459cSmrg    params.add_argument(
11026fa459cSmrg        '--lgblock',
11126fa459cSmrg        metavar='LGBLOCK',
11226fa459cSmrg        type=int,
11326fa459cSmrg        choices=[0] + list(range(16, 25)),
11426fa459cSmrg        help='Base 2 logarithm of the maximum input block size. '
11526fa459cSmrg        'Range is 16 to 24. If set to 0, the value will be set based '
11626fa459cSmrg        'on the quality. Defaults to 0.')
11726fa459cSmrg    # set default values using global DEFAULT_PARAMS dictionary
11826fa459cSmrg    parser.set_defaults(**DEFAULT_PARAMS)
11926fa459cSmrg
12026fa459cSmrg    options = parser.parse_args(args=args)
12126fa459cSmrg
12226fa459cSmrg    if options.infile:
12326fa459cSmrg        if not os.path.isfile(options.infile):
12426fa459cSmrg            parser.error('file "%s" not found' % options.infile)
12526fa459cSmrg        with open(options.infile, 'rb') as infile:
12626fa459cSmrg            data = infile.read()
12726fa459cSmrg    else:
12826fa459cSmrg        if sys.stdin.isatty():
12926fa459cSmrg            # interactive console, just quit
13026fa459cSmrg            parser.error('no input')
13126fa459cSmrg        infile = get_binary_stdio('stdin')
13226fa459cSmrg        data = infile.read()
13326fa459cSmrg
13426fa459cSmrg    if options.outfile:
13526fa459cSmrg        if os.path.isfile(options.outfile) and not options.force:
13626fa459cSmrg            parser.error('output file exists')
13726fa459cSmrg        outfile = open(options.outfile, 'wb')
13826fa459cSmrg    else:
13926fa459cSmrg        outfile = get_binary_stdio('stdout')
14026fa459cSmrg
14126fa459cSmrg    try:
14226fa459cSmrg        if options.decompress:
14326fa459cSmrg            data = brotli.decompress(data)
14426fa459cSmrg        else:
14526fa459cSmrg            data = brotli.compress(
14626fa459cSmrg                data,
14726fa459cSmrg                mode=options.mode,
14826fa459cSmrg                quality=options.quality,
14926fa459cSmrg                lgwin=options.lgwin,
15026fa459cSmrg                lgblock=options.lgblock)
15126fa459cSmrg    except brotli.error as e:
15226fa459cSmrg        parser.exit(1,
15326fa459cSmrg                    'bro: error: %s: %s' % (e, options.infile or 'sys.stdin'))
15426fa459cSmrg
15526fa459cSmrg    outfile.write(data)
15626fa459cSmrg    outfile.close()
15726fa459cSmrg
15826fa459cSmrg
15926fa459cSmrgif __name__ == '__main__':
16026fa459cSmrg    main()
161