2015-03-16 17:56:10 +00:00
|
|
|
#! /usr/bin/env python
|
2016-12-12 09:28:15 +00:00
|
|
|
"""Compression/decompression utility using the Brotli algorithm."""
|
2015-03-16 17:56:10 +00:00
|
|
|
|
2023-07-19 12:43:51 +00:00
|
|
|
# Note: Python2 has been deprecated long ago, but some projects out in
|
|
|
|
# the wide world may still use it nevertheless. This should not
|
|
|
|
# deprive them from being able to run Brotli.
|
2015-03-16 17:56:10 +00:00
|
|
|
from __future__ import print_function
|
2023-07-19 12:43:51 +00:00
|
|
|
|
2015-05-08 13:17:20 +00:00
|
|
|
import argparse
|
2015-03-16 17:56:10 +00:00
|
|
|
import os
|
2015-04-16 11:41:40 +00:00
|
|
|
import platform
|
2023-01-31 21:28:34 +00:00
|
|
|
import sys
|
2015-03-16 17:56:10 +00:00
|
|
|
|
2016-12-12 09:28:15 +00:00
|
|
|
import brotli
|
2015-03-16 17:56:10 +00:00
|
|
|
|
2023-07-19 12:43:51 +00:00
|
|
|
|
2015-05-08 14:46:56 +00:00
|
|
|
# default values of encoder parameters
|
2023-07-19 12:43:51 +00:00
|
|
|
_DEFAULT_PARAMS = {
|
2015-05-11 10:10:48 +00:00
|
|
|
'mode': brotli.MODE_GENERIC,
|
2015-05-08 14:46:56 +00:00
|
|
|
'quality': 11,
|
|
|
|
'lgwin': 22,
|
|
|
|
'lgblock': 0,
|
2015-03-16 17:56:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def get_binary_stdio(stream):
|
2023-07-19 12:43:51 +00:00
|
|
|
"""Return the specified stdin/stdout/stderr stream.
|
|
|
|
|
|
|
|
If the stdio stream requested (i.e. sys.(stdin|stdout|stderr))
|
|
|
|
has been replaced with a stream object that does not have a `.buffer`
|
|
|
|
attribute, this will return the original stdio stream's buffer, i.e.
|
|
|
|
`sys.__(stdin|stdout|stderr)__.buffer`.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
stream: One of 'stdin', 'stdout', 'stderr'.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The stream, as a 'raw' buffer object (i.e. io.BufferedIOBase subclass
|
|
|
|
instance such as io.Bufferedreader/io.BufferedWriter), suitable for
|
|
|
|
reading/writing binary data from/to it.
|
2015-03-16 17:56:10 +00:00
|
|
|
"""
|
2023-07-19 12:43:51 +00:00
|
|
|
if stream == 'stdin': stdio = sys.stdin
|
|
|
|
elif stream == 'stdout': stdio = sys.stdout
|
|
|
|
elif stream == 'stderr': stdio = sys.stderr
|
|
|
|
else:
|
|
|
|
raise ValueError('invalid stream name: %s' % (stream,))
|
2015-03-16 17:56:10 +00:00
|
|
|
if sys.version_info[0] < 3:
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
# set I/O stream binary flag on python2.x (Windows)
|
2015-04-16 11:41:40 +00:00
|
|
|
runtime = platform.python_implementation()
|
2016-12-12 09:28:15 +00:00
|
|
|
if runtime == 'PyPy':
|
2023-07-19 12:43:51 +00:00
|
|
|
# the msvcrt trick doesn't work in pypy, so use fdopen().
|
2016-12-12 09:28:15 +00:00
|
|
|
mode = 'rb' if stream == 'stdin' else 'wb'
|
2015-04-16 11:41:40 +00:00
|
|
|
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)
|
2015-03-16 17:56:10 +00:00
|
|
|
return stdio
|
|
|
|
else:
|
2023-07-19 12:43:51 +00:00
|
|
|
try:
|
2015-03-16 17:56:10 +00:00
|
|
|
return stdio.buffer
|
2023-07-19 12:43:51 +00:00
|
|
|
except AttributeError:
|
|
|
|
# The Python reference explains
|
|
|
|
# (-> https://docs.python.org/3/library/sys.html#sys.stdin)
|
|
|
|
# that the `.buffer` attribute might not exist, since
|
|
|
|
# the standard streams might have been replaced by something else
|
|
|
|
# (such as an `io.StringIO()` - perhaps via
|
|
|
|
# `contextlib.redirect_stdout()`).
|
|
|
|
# We fall back to the original stdio in these cases.
|
|
|
|
if stream == 'stdin': return sys.__stdin__.buffer
|
|
|
|
if stream == 'stdout': return sys.__stdout__.buffer
|
|
|
|
if stream == 'stderr': return sys.__stderr__.buffer
|
|
|
|
assert False, 'Impossible Situation.'
|
2015-03-16 17:56:10 +00:00
|
|
|
|
|
|
|
|
2015-10-05 17:57:32 +00:00
|
|
|
def main(args=None):
|
2015-05-08 13:17:20 +00:00
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
2016-12-12 09:28:15 +00:00
|
|
|
prog=os.path.basename(__file__), description=__doc__)
|
|
|
|
parser.add_argument(
|
2023-01-16 18:04:35 +00:00
|
|
|
'--version', action='version', version=brotli.version)
|
2016-12-12 09:28:15 +00:00
|
|
|
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)
|
2015-05-08 14:46:56 +00:00
|
|
|
params = parser.add_argument_group('optional encoder parameters')
|
2016-12-12 09:28:15 +00:00
|
|
|
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.')
|
2023-07-19 12:43:51 +00:00
|
|
|
# set default values using global _DEFAULT_PARAMS dictionary
|
|
|
|
parser.set_defaults(**_DEFAULT_PARAMS)
|
2015-05-08 13:17:20 +00:00
|
|
|
|
2015-10-05 17:57:32 +00:00
|
|
|
options = parser.parse_args(args=args)
|
2015-03-16 17:56:10 +00:00
|
|
|
|
|
|
|
if options.infile:
|
2023-07-19 12:43:51 +00:00
|
|
|
try:
|
|
|
|
with open(options.infile, 'rb') as infile:
|
|
|
|
data = infile.read()
|
|
|
|
except OSError:
|
|
|
|
parser.error('Could not read --infile: %s' % (infile,))
|
2015-03-16 17:56:10 +00:00
|
|
|
else:
|
|
|
|
if sys.stdin.isatty():
|
|
|
|
# interactive console, just quit
|
2023-07-19 12:43:51 +00:00
|
|
|
parser.error('No input (called from interactive terminal).')
|
2015-03-16 17:56:10 +00:00
|
|
|
infile = get_binary_stdio('stdin')
|
|
|
|
data = infile.read()
|
|
|
|
|
|
|
|
if options.outfile:
|
2023-07-19 12:43:51 +00:00
|
|
|
# Caution! If `options.outfile` is a broken symlink, will try to
|
|
|
|
# redirect the write according to symlink.
|
|
|
|
if os.path.exists(options.outfile) and not options.force:
|
|
|
|
parser.error(('Target --outfile=%s already exists, '
|
|
|
|
'but --force was not requested.') % (outfile,))
|
2016-12-12 09:28:15 +00:00
|
|
|
outfile = open(options.outfile, 'wb')
|
2023-07-19 12:43:51 +00:00
|
|
|
did_open_outfile = True
|
2015-03-16 17:56:10 +00:00
|
|
|
else:
|
|
|
|
outfile = get_binary_stdio('stdout')
|
2023-07-19 12:43:51 +00:00
|
|
|
did_open_outfile = False
|
2015-03-16 17:56:10 +00:00
|
|
|
try:
|
2023-07-19 12:43:51 +00:00
|
|
|
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)
|
|
|
|
outfile.write(data)
|
|
|
|
finally:
|
|
|
|
if did_open_outfile: outfile.close()
|
2015-03-16 17:56:10 +00:00
|
|
|
except brotli.error as e:
|
2016-12-12 09:28:15 +00:00
|
|
|
parser.exit(1,
|
2023-07-19 12:43:51 +00:00
|
|
|
'bro: error: %s: %s' % (e, options.infile or '{stdin}'))
|
2015-03-16 17:56:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2015-05-08 13:17:20 +00:00
|
|
|
main()
|