Stream partial decoded images during SSTV decode progress

The decode canvas was always black because nothing drew on it. Now the
backend encodes partial JPEG snapshots every 5% progress and the frontend
uses an <img> tag with in-place DOM updates instead of recreating innerHTML
on every SSE event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-07 11:58:00 +00:00
parent 06c218c736
commit a0f64f6fa6
3 changed files with 76 additions and 24 deletions

View File

@@ -9,7 +9,9 @@ original monolithic utils/sstv.py.
from __future__ import annotations
import base64
import contextlib
import io
import subprocess
import threading
import time
@@ -95,6 +97,7 @@ class DecodeProgress:
signal_level: int | None = None # 0-100 RMS audio level, None = not measured
sstv_tone: str | None = None # 'leader', 'sync', 'noise', None
vis_state: str | None = None # VIS detector state name
partial_image: str | None = None # base64 data URL of partial decode
def to_dict(self) -> dict:
result: dict = {
@@ -114,6 +117,8 @@ class DecodeProgress:
result['sstv_tone'] = self.sstv_tone
if self.vis_state:
result['vis_state'] = self.vis_state
if self.partial_image:
result['partial_image'] = self.partial_image
return result
@@ -380,6 +385,7 @@ class SSTVDecoder:
image_decoder: SSTVImageDecoder | None = None
current_mode_name: str | None = None
chunk_counter = 0
last_partial_pct = -1
logger.info("Audio decode thread started")
rtl_fm_error: str = ''
@@ -418,12 +424,28 @@ class SSTVDecoder:
# Currently decoding an image
complete = image_decoder.feed(samples)
# Encode partial image every 5% progress
pct = image_decoder.progress_percent
partial_url = None
if pct >= last_partial_pct + 5 or complete:
last_partial_pct = pct
try:
img = image_decoder.get_image()
if img is not None:
buf = io.BytesIO()
img.save(buf, format='JPEG', quality=40)
b64 = base64.b64encode(buf.getvalue()).decode('ascii')
partial_url = f'data:image/jpeg;base64,{b64}'
except Exception:
pass
# Emit progress
self._emit_progress(DecodeProgress(
status='decoding',
mode=current_mode_name,
progress_percent=image_decoder.progress_percent,
message=f'Decoding {current_mode_name}: {image_decoder.progress_percent}%'
progress_percent=pct,
message=f'Decoding {current_mode_name}: {pct}%',
partial_image=partial_url,
))
if complete: