Frame interpolation, FPS hot-update, timing metrics, KC brightness fixes

- CSS: add frame interpolation option — blends between consecutive captured
  frames on idle ticks so LED output runs at full target FPS even when
  capture rate is lower (e.g. capture 30fps, output 60fps)
- WledTargetProcessor: re-read stream.target_fps each loop tick so FPS
  changes to the CSS source take effect without restarting the target
- WledTargetProcessor: restore per-stage timing metrics on target card by
  pulling extract/map/smooth/total from CSS stream get_last_timing()
- TargetProcessingState schema: add missing timing_extract_ms,
  timing_map_leds_ms, timing_smooth_ms, timing_total_ms fields
- KC targets: add extraction FPS badge to target card props row
- KC targets: fix 500 error when changing brightness — update_fields now
  accepts (and ignores) WLED-specific kwargs
- KC targets: fix partial key_colors_settings update wiping pattern_template_id
  — update route merges only explicitly-set fields using model_dump(exclude_unset=True)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 20:29:22 +03:00
parent be37df4459
commit 55e25b8860
14 changed files with 138 additions and 6 deletions

View File

@@ -25,6 +25,7 @@ class CSSEditorModal extends Modal {
saturation: document.getElementById('css-editor-saturation').value,
gamma: document.getElementById('css-editor-gamma').value,
color: document.getElementById('css-editor-color').value,
frame_interpolation: document.getElementById('css-editor-frame-interpolation').checked,
led_count: (type === 'static' || type === 'gradient') ? '0' : document.getElementById('css-editor-led-count').value,
gradient_stops: type === 'gradient' ? JSON.stringify(_gradientStops) : '[]',
};
@@ -194,6 +195,8 @@ export async function showCSSEditor(cssId = null) {
const gamma = css.gamma ?? 1.0;
document.getElementById('css-editor-gamma').value = gamma;
document.getElementById('css-editor-gamma-value').textContent = parseFloat(gamma).toFixed(2);
document.getElementById('css-editor-frame-interpolation').checked = css.frame_interpolation || false;
}
document.getElementById('css-editor-led-count').value = css.led_count ?? 0;
@@ -214,6 +217,7 @@ export async function showCSSEditor(cssId = null) {
document.getElementById('css-editor-saturation-value').textContent = '1.00';
document.getElementById('css-editor-gamma').value = 1.0;
document.getElementById('css-editor-gamma-value').textContent = '1.00';
document.getElementById('css-editor-frame-interpolation').checked = false;
document.getElementById('css-editor-color').value = '#ffffff';
document.getElementById('css-editor-led-count').value = 0;
document.getElementById('css-editor-title').textContent = t('color_strip.add');
@@ -281,6 +285,7 @@ export async function saveCSSEditor() {
brightness: parseFloat(document.getElementById('css-editor-brightness').value),
saturation: parseFloat(document.getElementById('css-editor-saturation').value),
gamma: parseFloat(document.getElementById('css-editor-gamma').value),
frame_interpolation: document.getElementById('css-editor-frame-interpolation').checked,
led_count: parseInt(document.getElementById('css-editor-led-count').value) || 0,
};
if (!cssId) payload.source_type = 'picture';

View File

@@ -75,6 +75,7 @@ export function createKCTargetCard(target, sourceMap, patternTemplateMap) {
<span class="stream-card-prop" title="${t('kc.source')}">📺 ${escapeHtml(sourceName)}</span>
<span class="stream-card-prop" title="${t('kc.pattern_template')}">📄 ${escapeHtml(patternName)}</span>
<span class="stream-card-prop">▭ ${rectCount} rect${rectCount !== 1 ? 's' : ''}</span>
<span class="stream-card-prop" title="${t('kc.fps')}">⚡ ${kcSettings.fps ?? 10} fps</span>
</div>
<div class="brightness-control" data-kc-brightness-wrap="${target.id}">
<input type="range" class="brightness-slider" min="0" max="255"

View File

@@ -552,6 +552,8 @@
"color_strip.interpolation.dominant": "Dominant",
"color_strip.smoothing": "Smoothing:",
"color_strip.smoothing.hint": "Temporal blending between frames (0=none, 1=full). Reduces flicker.",
"color_strip.frame_interpolation": "Frame Interpolation:",
"color_strip.frame_interpolation.hint": "Blends between consecutive captured frames to produce output at the full target FPS even when capture rate is lower. Reduces visible stepping on slow ambient transitions.",
"color_strip.color_corrections": "Color Corrections",
"color_strip.brightness": "Brightness:",
"color_strip.brightness.hint": "Output brightness multiplier (0=off, 1=unchanged, 2=double). Applied after color extraction.",

View File

@@ -552,6 +552,8 @@
"color_strip.interpolation.dominant": "Доминирующий",
"color_strip.smoothing": "Сглаживание:",
"color_strip.smoothing.hint": "Временное смешивание кадров (0=без смешивания, 1=полное). Уменьшает мерцание.",
"color_strip.frame_interpolation": "Интерполяция кадров:",
"color_strip.frame_interpolation.hint": "Смешивает последовательные захваченные кадры для вывода на полной целевой частоте кадров, даже если скорость захвата ниже. Уменьшает заметные ступеньки при плавных переходах.",
"color_strip.color_corrections": "Цветокоррекция",
"color_strip.brightness": "Яркость:",
"color_strip.brightness.hint": "Множитель яркости (0=выкл, 1=без изменений, 2=двойная). Применяется после извлечения цвета.",