#! /usr/bin/env python """Compression/decompression utility using the Brotli algorithm.""" from __future__ import print_function import argparse import sys import os import platform import brotli # default values of encoder parameters DEFAULT_PARAMS = { 'mode': brotli.MODE_GENERIC, 'quality': 11, 'lgwin': 22, 'lgblock': 0, } def get_binary_stdio(stream): """ Return the specified standard input, output or errors stream as a 'raw' buffer object suitable for reading/writing binary data from/to it. """ assert stream in ['stdin', 'stdout', 'stderr'], 'invalid stream name' stdio = getattr(sys, stream) if sys.version_info[0] < 3: if sys.platform == 'win32': # set I/O stream binary flag on python2.x (Windows) runtime = platform.python_implementation() if runtime == 'PyPy': # the msvcrt trick doesn't work in pypy, so I use fdopen mode = 'rb' if stream == 'stdin' else 'wb' stdio = os.fdopen(stdio.fileno(), mode, 0) else: # this works with CPython -- untested on other implementations import msvcrt msvcrt.setmode(stdio.fileno(), os.O_BINARY) return stdio else: # get 'buffer' attribute to read/write binary data on python3.x if hasattr(stdio, 'buffer'): return stdio.buffer else: orig_stdio = getattr(sys, '__%s__' % stream) return orig_stdio.buffer def main(args=None): parser = argparse.ArgumentParser( prog=os.path.basename(__file__), description=__doc__) parser.add_argument( '--version', action='version', version=brotli.version) parser.add_argument( '-i', '--input', metavar='FILE', type=str, dest='infile', help='Input file', default=None) parser.add_argument( '-o', '--output', metavar='FILE', type=str, dest='outfile', help='Output file', default=None) parser.add_argument( '-f', '--force', action='store_true', help='Overwrite existing output file', default=False) parser.add_argument( '-d', '--decompress', action='store_true', help='Decompress input file', default=False) params = parser.add_argument_group('optional encoder parameters') params.add_argument( '-m', '--mode', metavar='MODE', type=int, choices=[0, 1, 2], help='The compression mode can be 0 for generic input, ' '1 for UTF-8 encoded text, or 2 for WOFF 2.0 font data. ' 'Defaults to 0.') params.add_argument( '-q', '--quality', metavar='QUALITY', type=int, choices=list(range(0, 12)), help='Controls the compression-speed vs compression-density ' 'tradeoff. The higher the quality, the slower the ' 'compression. Range is 0 to 11. Defaults to 11.') params.add_argument( '--lgwin', metavar='LGWIN', type=int, choices=list(range(10, 25)), help='Base 2 logarithm of the sliding window size. Range is ' '10 to 24. Defaults to 22.') params.add_argument( '--lgblock', metavar='LGBLOCK', type=int, choices=[0] + list(range(16, 25)), help='Base 2 logarithm of the maximum input block size. ' 'Range is 16 to 24. If set to 0, the value will be set based ' 'on the quality. Defaults to 0.') # set default values using global DEFAULT_PARAMS dictionary parser.set_defaults(**DEFAULT_PARAMS) options = parser.parse_args(args=args) if options.infile: if not os.path.isfile(options.infile): parser.error('file "%s" not found' % options.infile) with open(options.infile, 'rb') as infile: data = infile.read() else: if sys.stdin.isatty(): # interactive console, just quit parser.error('no input') infile = get_binary_stdio('stdin') data = infile.read() if options.outfile: if os.path.isfile(options.outfile) and not options.force: parser.error('output file exists') outfile = open(options.outfile, 'wb') else: outfile = get_binary_stdio('stdout') try: if options.decompress: data = brotli.decompress(data) else: data = brotli.compress( data, mode=options.mode, quality=options.quality, lgwin=options.lgwin, lgblock=options.lgblock) except brotli.error as e: parser.exit(1, 'bro: error: %s: %s' % (e, options.infile or 'sys.stdin')) outfile.write(data) outfile.close() if __name__ == '__main__': main()