73 lines
1.9 KiB
Python
73 lines
1.9 KiB
Python
"""Minimal Ogg page reader/writer."""
|
|
import struct
|
|
|
|
|
|
def _crc_table():
|
|
t = []
|
|
for i in range(256):
|
|
r = i << 24
|
|
for _ in range(8):
|
|
r = ((r << 1) ^ 0x04C11DB7) if r & 0x80000000 else (r << 1)
|
|
t.append(r & 0xFFFFFFFF)
|
|
return t
|
|
|
|
|
|
_T = _crc_table()
|
|
|
|
|
|
def ogg_crc(b: bytes) -> int:
|
|
c = 0
|
|
for x in b:
|
|
c = ((c << 8) & 0xFFFFFFFF) ^ _T[((c >> 24) & 0xFF) ^ x]
|
|
return c
|
|
|
|
|
|
def read_packets(b: bytes):
|
|
"""Return list of (packet_bytes, granule_of_page_if_packet_ends_page_else_None)."""
|
|
pos = 0
|
|
out = []
|
|
cur = b""
|
|
while pos + 27 <= len(b):
|
|
if b[pos:pos + 4] != b"OggS":
|
|
raise ValueError("lost Ogg sync at %d" % pos)
|
|
htype, gran, serial, seq, crc, nseg = struct.unpack_from("<BqIIIB", b, pos + 5)
|
|
segs = b[pos + 27: pos + 27 + nseg]
|
|
p = pos + 27 + nseg
|
|
ends = []
|
|
for s in segs:
|
|
cur += b[p:p + s]
|
|
p += s
|
|
if s < 255:
|
|
ends.append(cur)
|
|
cur = b""
|
|
for i, pk in enumerate(ends):
|
|
out.append((pk, gran if i == len(ends) - 1 else None))
|
|
pos = p
|
|
return out
|
|
|
|
|
|
class OggWriter:
|
|
def __init__(self, serial=0x41434B54):
|
|
self.serial = serial
|
|
self.seq = 0
|
|
self.out = bytearray()
|
|
|
|
def page(self, packets, granule, bos=False, eos=False):
|
|
segs = bytearray()
|
|
body = bytearray()
|
|
for pk in packets:
|
|
n = len(pk)
|
|
while n >= 255:
|
|
segs.append(255); n -= 255
|
|
segs.append(n)
|
|
body += pk
|
|
hdr = struct.pack("<4sBBqIIIB", b"OggS", 0, (2 if bos else 0) | (4 if eos else 0),
|
|
granule, self.serial, self.seq, 0, len(segs)) + bytes(segs)
|
|
page = bytearray(hdr + body)
|
|
struct.pack_into("<I", page, 22, ogg_crc(page))
|
|
self.out += page
|
|
self.seq += 1
|
|
|
|
def bytes(self):
|
|
return bytes(self.out)
|