from __future__ import annotations import io from typing import IO, TYPE_CHECKING from .binary import encode_bin, decode_bin if TYPE_CHECKING: from typing_extensions import Buffer # 3.12+ from typing_extensions import Self # 3.11+ class BitStream(io.RawIOBase, IO[bytes]): __slots__ = ("substream",) def __init__(self, substream: IO[bytes]) -> None: self.substream = substream def __enter__(self) -> Self: return self class BitStreamReader(BitStream): __slots__ = ("buffer", "total_size") def __init__(self, substream: IO[bytes]) -> None: super().__init__(substream) self.total_size = 0 self.buffer = b"" def close(self) -> None: if self.total_size % 8 != 0: raise ValueError("total size of read data must be a multiple of 8", self.total_size) def tell(self) -> int: return self.substream.tell() def seek(self, pos: int, whence: int = 0) -> int: self.buffer = b"" self.total_size = 0 self.substream.seek(pos, whence) return 0 def read(self, count: int = -1) -> bytes: if count < 0: raise ValueError("count cannot be negative") l = len(self.buffer) if count == 0: data = b"" elif count <= l: data = self.buffer[:count] self.buffer = self.buffer[count:] else: data = self.buffer count -= l bytes = count // 8 if count & 7: bytes += 1 buf = encode_bin(self.substream.read(bytes)) data += buf[:count] self.buffer = buf[count:] self.total_size += len(data) return data class BitStreamWriter(BitStream): __slots__ = ("buffer", "pos") def __init__(self, substream: IO[bytes]) -> None: super().__init__(substream) self.buffer: list[bytes] = [] self.pos = 0 def close(self) -> None: self.flush() def flush(self) -> None: bytes = decode_bin(b"".join(self.buffer)) self.substream.write(bytes) self.buffer = [] self.pos = 0 def tell(self) -> int: return self.substream.tell() + self.pos // 8 def seek(self, pos: int, whence: int = 0) -> int: self.flush() return self.substream.seek(pos, whence) def write(self, data: Buffer) -> int: if not data: return 0 if type(data) is not bytes: raise TypeError("data must be a bytes, not %r" % (type(data),)) self.buffer.append(data) return len(data)