import os from typing import List from .models import Packet, Page, TeletextService def load_t42(file_path: str) -> TeletextService: service = TeletextService() with open(file_path, 'rb') as f: while True: chunk = f.read(42) if not chunk: break if len(chunk) < 42: # Should not happen in a valid T42 stream, or we just ignore incomplete tail break packet = Packet(chunk) service.all_packets.append(packet) # Logic to group into pages. # This is non-trivial because packets for a page might be interleaved or sequential. # Standard implementation: Packets arrive in order. Row 0 starts a new page/subpage. if packet.row == 0: # Start of a new page header. # Byte 2-9 of header contain Page Number, Subcode, Control bits etc. # We need to parse the header to identify the page. # Header format (after Mag/Row): # Bytes: P1 P2 S1 S2 S3 S4 C1 C2 ... # All Hamming 8/4 encoded. # For now, let's just create a new page entry for every Header we see, # or find the existing one if we want to support updates (but T42 usually is a stream capture). # If it's an editor file, it's likely sequential. p_num, sub_code = parse_header(packet.data) # Create new page new_page = Page(magazine=packet.magazine, page_number=p_num, sub_code=sub_code) new_page.packets.append(packet) service.pages.append(new_page) else: # Add to the "current" page of this magazine. # We need to track the current active page for each magazine. # A simplistic approach: add to the last page added that matches the magazine ?? # Robust approach: Maintain a dict of current_pages_by_magazine. # Let's find the last page in service that matches the packet's magazine # This is O(N) but N (pages) is small. target_page = None for p in reversed(service.pages): if p.magazine == packet.magazine: target_page = p break if target_page: target_page.packets.append(packet) else: # Packet without a header? Orphaned. Just keep in all_packets pass return service def save_t42(file_path: str, service: TeletextService): with open(file_path, 'wb') as f: # User requirement: "without rearranging the order of the packets" # Implies we should iterate the original list. # However, if we edit data, we modify the Packet objects in place. # If we Add/Delete packets, we need to handle that. for packet in service.all_packets: # Reconstruct the 42 bytes from the packet fields # The packet.data (bytearray) should be mutable and edited by the UI. # packet.original_data (first 2 bytes) + packet.data # Note: If we changed Magazine or Row, we'd need to re-encode the first 2 bytes. # For now, assume we primarily edit content (bytes 2-41). header = packet.original_data[:2] # Keep original address for now # TODO: regenerating header if Mag/Row changed f.write(header + packet.data) def decode_hamming_8_4(byte_val): return ((byte_val >> 1) & 1) | \ (((byte_val >> 3) & 1) << 1) | \ (((byte_val >> 5) & 1) << 2) | \ (((byte_val >> 7) & 1) << 3) def parse_header(data: bytearray): # Data is 40 bytes. # Bytes 0-7 are Page Num (2), Subcode (4), Control (2) - ALL Hamming encoded. # 0: Page Units (PU) # 1: Page Tens (PT) pu = decode_hamming_8_4(data[0]) pt = decode_hamming_8_4(data[1]) page_num = (pt & 0xF) * 10 + (pu & 0xF) # Subcode: S1, S2, S3, S4 # S1 (low), S2, S3, S4 (high) s1 = decode_hamming_8_4(data[2]) s2 = decode_hamming_8_4(data[3]) s3 = decode_hamming_8_4(data[4]) s4 = decode_hamming_8_4(data[5]) # Subcode logic is a bit complex with specific bit mapping for "Time" vs "Subcode" # But usually just combining them gives the raw subcode value. # S1: bits 0-3 # S2: bits 4-6 (bit 4 is C4) -> actually S2 has 3 bits of subcode + 1 control bit usually? # Let's simplify and just concat them for a unique identifier. sub_code = s1 | (s2 << 4) | (s3 << 8) | (s4 << 12) return page_num, sub_code