From 4552e823a1fe717bac0465284fd3a734f6f0ec79 Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Thu, 16 Jul 2026 10:45:28 -0400 Subject: [PATCH] perf(web): stream the preview PNG raw instead of PIL decode + re-encode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit display_preview_generator() opened each changed snapshot with PIL and re-encoded it to PNG just to base64 it — but /tmp/led_matrix_preview.png already IS a PNG, written atomically by the display service (tmp file + os.replace in display_manager.py), so a partially-written file can never be observed. Read the bytes and base64 them directly: identical payload (front-end consumes data:image/png;base64 — verified in base.html), one full image decode+encode per frame less on the same Pi that's driving the matrix. The existing mtime skip and viewer-marker throttling are unchanged (they already covered the "skip unchanged frames" concern). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ --- web_interface/app.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/web_interface/app.py b/web_interface/app.py index d3121856..e61c5cec 100644 --- a/web_interface/app.py +++ b/web_interface/app.py @@ -615,8 +615,6 @@ def system_status_generator(): def display_preview_generator(): """Generate display preview updates from snapshot file""" import base64 - from PIL import Image - import io snapshot_path = "/tmp/led_matrix_preview.png" # nosec B108 - fixed path matches display_manager; only read here # Viewer marker: this generator only runs while the broadcaster has @@ -658,23 +656,23 @@ def display_preview_generator(): # Only read if file is new or has been updated if last_modified is None or current_modified > last_modified: try: - # Read and encode the image - with Image.open(snapshot_path) as img: - # Convert to PNG and encode as base64 - buffer = io.BytesIO() - img.save(buffer, format='PNG') - img_str = base64.b64encode(buffer.getvalue()).decode('utf-8') - - preview_data = { - 'timestamp': time.time(), - 'width': width, - 'height': height, - 'image': img_str - } - last_modified = current_modified - yield preview_data - except Exception: # nosec B110 - SSE preview file may be mid-write; transient error, skip this update - # File might be being written, skip this update + # The snapshot is already a PNG, written atomically by + # the display service (tmp + os.replace in + # display_manager), so pass the raw bytes straight + # through instead of PIL-decoding and re-encoding — + # identical payload, much less CPU on the Pi. + with open(snapshot_path, 'rb') as f: + img_str = base64.b64encode(f.read()).decode('utf-8') + + preview_data = { + 'timestamp': time.time(), + 'width': width, + 'height': height, + 'image': img_str + } + last_modified = current_modified + yield preview_data + except Exception: # nosec B110 - transient read error (e.g. file rotated away); skip this update pass else: # No snapshot available