forked from kedartatwawadi/stanford_compression_library
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathzlib_external.py
More file actions
150 lines (120 loc) · 5.94 KB
/
Copy pathzlib_external.py
File metadata and controls
150 lines (120 loc) · 5.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""External compressors gzip for testing/benchmarking purposes.
Zlib format is described in https://datatracker.ietf.org/doc/html/rfc1950.
It mostly relies on Deflate which is described in https://datatracker.ietf.org/doc/html/rfc1951.
Zlib library website: https://www.zlib.net/.
Also see https://aws.amazon.com/blogs/opensource/improving-zlib-cloudflare-and-comparing-performance-with-other-zlib-forks/
for information on more efficient implementations.
We use the python zlib module (part of standard library) which internally calls the C library.
"""
import os
import tempfile
from scl.core.data_block import DataBlock
from scl.core.data_encoder_decoder import DataDecoder, DataEncoder
from scl.core.data_stream import TextFileDataStream, Uint8FileDataStream
from scl.core.encoded_stream import EncodedBlockReader, EncodedBlockWriter
from scl.core.prob_dist import ProbabilityDist
from scl.utils.bitarray_utils import BitArray, bitarray_to_uint, uint_to_bitarray
from scl.utils.test_utils import (
create_random_binary_file,
try_file_lossless_compression,
try_lossless_compression,
)
import zlib
class ZlibExternalEncoder(DataEncoder):
def __init__(self, level=6):
self.level = level
# state stays alive across blocks so we can benefit
self.zlib_context = zlib.compressobj(level=self.level)
self.block_size_num_bits = 32 # num bits used to encode the block size at start
def reset(self):
# start new zlib context
self.zlib_context = zlib.compressobj(level=self.level)
def encode_block(self, data_block: DataBlock):
raw_bytes = bytes(data_block.data_list)
# flush below with Z_SYNC_FLUSH that ensures decompress is able to decompress the
# data till now. Note that this still utilizes this block for finding matches when
# we are compressing the next block (as opposed to Z_FULL_FLUSH that resets the state).
# See https://www.zlib.net/manual.html for more information
compressed_bytes = self.zlib_context.compress(raw_bytes) + self.zlib_context.flush(
zlib.Z_SYNC_FLUSH
)
# FIXME: might be inefficient to convert to BitArray since it will be later be
# converted back to bytes when writing to file
# FIXME: also, should we worry about endianness?
# at start write the compressed size because zlib decoder expects to get complete blocks
# and cannot determine when we want to end
compressed_bitarray = BitArray(
uint_to_bitarray(len(compressed_bytes) * 8, bit_width=self.block_size_num_bits)
)
compressed_bitarray.frombytes(compressed_bytes)
return compressed_bitarray
def encode_file(self, input_file_path: str, encoded_file_path: str, block_size: int = 10000):
"""utility wrapper around the encode function using Uint8FileDataStream
Args:
input_file_path (str): path of the input file
encoded_file_path (str): path of the encoded binary file
block_size (int): choose the block size to be used to call the encode function
"""
# call the encode function and write to the binary file
with Uint8FileDataStream(input_file_path, "rb") as fds:
with EncodedBlockWriter(encoded_file_path) as writer:
self.encode(fds, block_size=block_size, encode_writer=writer)
class ZlibExternalDecoder(DataDecoder):
def __init__(self, level=6):
self.level = level
self.zlib_context = zlib.decompressobj()
self.block_size_num_bits = 32 # num used to encode the block size at start
def reset(self):
self.zlib_context = zlib.decompressobj()
def decode_block(self, compressed_bitarray: BitArray):
# first read the size of the gzipped block(s)
zlib_size = bitarray_to_uint(compressed_bitarray[: self.block_size_num_bits])
zlib_compressed = compressed_bitarray[
self.block_size_num_bits : self.block_size_num_bits + zlib_size
]
compressed_bytes = zlib_compressed.tobytes()
return (
DataBlock(list(self.zlib_context.decompress(compressed_bytes))),
self.block_size_num_bits + zlib_size,
)
def decode_file(self, encoded_file_path: str, output_file_path: str):
"""utility wrapper around the decode function using Uint8FileDataStream
Args:
encoded_file_path (str): input binary file
output_file_path (str): output (text) file to which decoded data is written
"""
# decode data and write output to a text file
with EncodedBlockReader(encoded_file_path) as reader:
with Uint8FileDataStream(output_file_path, "wb") as fds:
self.decode(reader, fds)
def test_zlib_encode_decode():
encoder = ZlibExternalEncoder()
decoder = ZlibExternalDecoder()
# create some sample data consisting of bytes
data_list = [0, 0, 1, 3, 4, 100, 255, 123, 234, 42, 186]
data_block = DataBlock(data_list)
is_lossless, _, _ = try_lossless_compression(
data_block, encoder, decoder, add_extra_bits_to_encoder_output=True
)
assert is_lossless
def test_zlib_file_encode_decode():
"""full test for ZlibExternalEncoder and ZlibExternalDecoder
- create a sample file
- encode the file using ZlibExternalEncoder
- perform decoding and check if the compression was lossless
"""
# define encoder, decoder
encoder = ZlibExternalEncoder()
decoder = ZlibExternalDecoder()
with tempfile.TemporaryDirectory() as tmpdirname:
# create a file with some random data
input_file_path = os.path.join(tmpdirname, "inp_file.txt")
create_random_binary_file(
input_file_path,
file_size=5000,
prob_dist=ProbabilityDist({44: 0.5, 45: 0.25, 46: 0.2, 255: 0.05}),
)
# test lossless compression
assert try_file_lossless_compression(
input_file_path, encoder, decoder, encode_block_size=1000
)