from dataclasses import dataclass, field from typing import List, Optional @dataclass class Packet: """ Represents a single Teletext packet (Row). In T42, we have 42 bytes: Byte 0-1: Magazine and Row Address (Hamming 8/4 encoding) Byte 2-41: Data bytes (40 bytes) """ original_data: bytes magazine: int = field(init=False) row: int = field(init=False) data: bytearray = field(init=False) def __post_init__(self): if len(self.original_data) != 42: raise ValueError(f"Packet must be 42 bytes, got {len(self.original_data)}") # Parse Magazine and Row from the first 2 bytes (Hamming 8/4) # MRAG (Magazine + Row Address Group) # Byte 0: P1 D1 P2 D2 P3 D3 P4 D4 # Byte 1: P1 D1 P2 D2 P3 D3 P4 D4 b1 = self.original_data[0] b2 = self.original_data[1] # De-interleave Hamming bits to get M (3 bits) and R (5 bits) # This is the "basic" interpretation. # For a robust editor we assume the input T42 is valid or we just store bytes. # But we need Mag/Row to organize pages. # Decode Hamming 8/4 logic is complex to implementation from scratch correctly # without a reference, but usually D1, D2, D3, D4 are at bit positions 1, 3, 5, 7 # (0-indexed, where 0 is LSB). # Let's perform a simple extraction assuming no bit errors for now. def decode_hamming_8_4(byte_val): # Extract data bits: bits 1, 3, 5, 7 return ((byte_val >> 1) & 1) | \ (((byte_val >> 3) & 1) << 1) | \ (((byte_val >> 5) & 1) << 2) | \ (((byte_val >> 7) & 1) << 3) d1 = decode_hamming_8_4(b1) d2 = decode_hamming_8_4(b2) # Magazine is 3 bits (Logic is specific: Mag 8 is encoded as 0) # Row is 5 bits. # According to Spec (ETSI EN 300 706): # b1 encoded: M1 M2 M3 R1 # b2 encoded: R2 R3 R4 R5 self.magazine = (d1 & 0b0111) if self.magazine == 0: self.magazine = 8 row_low_bit = (d1 >> 3) & 1 row_high_bits = d2 self.row = (row_high_bits << 1) | row_low_bit self.data = bytearray(self.original_data[2:]) @property def is_header(self): return self.row == 0 @dataclass class Page: """ Represents a Teletext Page (e.g., 100). Can have multiple subpages. """ magazine: int page_number: int # 00-99 sub_code: int = 0 # Subpage code (0000 to 3F7F hex usually, simplest is 0-99 equivalent) packets: List[Packet] = field(default_factory=list) @property def full_page_number(self): return f"{self.magazine}{self.page_number:02d}" @dataclass class TeletextService: """ Container for all pages. """ pages: List[Page] = field(default_factory=list) # We also keep a flat list of all packets to preserve order on save all_packets: List[Packet] = field(default_factory=list)