Skip to content

Instantly share code, notes, and snippets.

@rvanlaar
Created February 12, 2026 13:50
Show Gist options
  • Select an option

  • Save rvanlaar/8bcb776bb0760919db2f93fc1ca41a9d to your computer and use it in GitHub Desktop.

Select an option

Save rvanlaar/8bcb776bb0760919db2f93fc1ca41a9d to your computer and use it in GitHub Desktop.
paco_extract.py
#!/usr/bin/env python3
import struct
import sys
from dataclasses import dataclass, field
@dataclass
class PACoHeader:
# _ stands for unkown usage
head: int # 0x0001
head2: int # 0x0026
width: int
height:int
fps: int # negative is indicative of no audio, but not definitive
flags: int # flags is 0x100: audio
max_chunk_size: int
_zero: int # always zero?
_audio_related: int # unkown, audio related
frames: int # number of chunks, i.e. frames
frames2: int # number of chunks repeated
_eight: int # always 8?
_six: int # always 0x600
_flags2: int # some flags
_zero2: int # always 0?
has_audio: bool = field(init=False)
framerate: int = field(init=False)
def __post_init__(self):
self.has_audio = (self.flags & 0x100) == 0x100
self.framerate = abs(self.fps)
def check(self):
assert self.head == 0x1, f"head is not 0x1: {self.head}"
assert self.head2 == 0x26, f"head2 is not 0x26: {self.head2}"
assert self._zero == 0, f"_zero is not 0: {self._zero}"
assert self._eight == 8, f"_eight is not 8: {self._eight}"
assert self._six == 0x600, f"_six is not 0x600: {self._six}"
assert self._zero2 == 0, f"_zero2 is not 0: {self._zero2}"
def main():
filename = sys.argv[1]
print(f"opening: {filename}")
f = open(filename, "rb")
audio_out = open(f"{filename}.pcm", "wb")
get8 = lambda: struct.unpack(">B", f.read(1))[0]
get16 = lambda: struct.unpack(">H", f.read(2))[0]
get16_s = lambda: struct.unpack(">h", f.read(2))[0]
get24 = lambda: struct.unpack(">I", b'\x00' + f.read(3))[0]
get32 = lambda: struct.unpack(">I", f.read(4))[0]
header = PACoHeader(get16(), get16(), get16(), get16(), get16_s(), get16(), get32(), get32(), get32(), get16(), get16(), get16(), get16(), get32(), get16())
header.check()
print(header)
frame_sizes = [get32() for i in range(header.frames)]
for i in range(header.frames):
next_frame = f.tell() + frame_sizes[i]
print("new frame")
while (f.tell() < next_frame):
chunk_type = get8()
chunk_size = get24()
if chunk_type in CHUNK_TYPES:
chunk_name = CHUNK_TYPES[chunk_type]
else:
chunk_name = f"{chunk_type}: unknown"
print(f"chunk type: {chunk_name} size: {chunk_size}")
if chunk_type == 2:
palette_data = f.read(chunk_size-4)
quicktime_palette = b'\x30\x00\x00\x00\x00\x00\x00\x00'
custom_palette = b'\x10\x00\x00\x00\x00\x00\x00\x00'
if palette_data.startswith(quicktime_palette):
print("QuicktimePalette")
elif palette_data.startswith(custom_palette):
print("Defines custom palette")
else:
print("unkown palette")
elif chunk_type == 4:
# 8 bit unsigned pcm
sampling_rates = [5563, 7418, 11127, 22254]
header = get16()
get16()
index = (header >> 10) & 7
print(f"\t sampling rate: {sampling_rates[index]}")
audio_out.write(f.read(chunk_size-8))
else:
f.seek(f.tell() + chunk_size - 4)
audio_out.close()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment