Initial commit: Core Teletext Editor functionality
This commit is contained in:
119
src/teletext/io.py
Normal file
119
src/teletext/io.py
Normal file
@@ -0,0 +1,119 @@
|
||||
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
|
||||
Reference in New Issue
Block a user