mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
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:
@@ -345,18 +345,33 @@ const SSTVGeneral = (function() {
|
||||
const liveContent = document.getElementById('sstvGeneralLiveContent');
|
||||
if (!liveContent) return;
|
||||
|
||||
liveContent.innerHTML = `
|
||||
<div class="sstv-general-canvas-container">
|
||||
<canvas id="sstvGeneralCanvas" width="320" height="256"></canvas>
|
||||
</div>
|
||||
<div class="sstv-general-decode-info">
|
||||
<div class="sstv-general-mode-label">${data.mode || 'Detecting mode...'}</div>
|
||||
<div class="sstv-general-progress-bar">
|
||||
<div class="progress" style="width: ${data.progress || 0}%"></div>
|
||||
let container = liveContent.querySelector('.sstv-general-decode-container');
|
||||
if (!container) {
|
||||
liveContent.innerHTML = `
|
||||
<div class="sstv-general-decode-container">
|
||||
<div class="sstv-general-canvas-container">
|
||||
<img id="sstvGeneralDecodeImg" width="320" height="256" alt="Decoding..." style="display:block;background:#000;">
|
||||
</div>
|
||||
<div class="sstv-general-decode-info">
|
||||
<div class="sstv-general-mode-label"></div>
|
||||
<div class="sstv-general-progress-bar">
|
||||
<div class="progress" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="sstv-general-status-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sstv-general-status-message">${data.message || 'Decoding...'}</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
container = liveContent.querySelector('.sstv-general-decode-container');
|
||||
}
|
||||
|
||||
container.querySelector('.sstv-general-mode-label').textContent = data.mode || 'Detecting mode...';
|
||||
container.querySelector('.progress').style.width = (data.progress || 0) + '%';
|
||||
container.querySelector('.sstv-general-status-message').textContent = data.message || 'Decoding...';
|
||||
|
||||
if (data.partial_image) {
|
||||
const img = container.querySelector('#sstvGeneralDecodeImg');
|
||||
if (img) img.src = data.partial_image;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -780,18 +780,33 @@ const SSTV = (function() {
|
||||
const liveContent = document.getElementById('sstvLiveContent');
|
||||
if (!liveContent) return;
|
||||
|
||||
liveContent.innerHTML = `
|
||||
<div class="sstv-canvas-container">
|
||||
<canvas id="sstvCanvas" width="320" height="256"></canvas>
|
||||
</div>
|
||||
<div class="sstv-decode-info">
|
||||
<div class="sstv-mode-label">${data.mode || 'Detecting mode...'}</div>
|
||||
<div class="sstv-progress-bar">
|
||||
<div class="progress" style="width: ${data.progress || 0}%"></div>
|
||||
let container = liveContent.querySelector('.sstv-decode-container');
|
||||
if (!container) {
|
||||
liveContent.innerHTML = `
|
||||
<div class="sstv-decode-container">
|
||||
<div class="sstv-canvas-container">
|
||||
<img id="sstvDecodeImg" width="320" height="256" alt="Decoding..." style="display:block;background:#000;">
|
||||
</div>
|
||||
<div class="sstv-decode-info">
|
||||
<div class="sstv-mode-label"></div>
|
||||
<div class="sstv-progress-bar">
|
||||
<div class="progress" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="sstv-status-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sstv-status-message">${data.message || 'Decoding...'}</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
container = liveContent.querySelector('.sstv-decode-container');
|
||||
}
|
||||
|
||||
container.querySelector('.sstv-mode-label').textContent = data.mode || 'Detecting mode...';
|
||||
container.querySelector('.progress').style.width = (data.progress || 0) + '%';
|
||||
container.querySelector('.sstv-status-message').textContent = data.message || 'Decoding...';
|
||||
|
||||
if (data.partial_image) {
|
||||
const img = container.querySelector('#sstvDecodeImg');
|
||||
if (img) img.src = data.partial_image;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user