Add dynamic brightness value source support for KC targets, fix subtab selector collision
Extend value source brightness modulation to Key Colors targets (matching LED target support). Also fix stream subtab CSS selector collision that broke target subtab selection, and use 🔢 emoji for value source UI elements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,7 @@ def _kc_settings_to_schema(settings: KeyColorsSettings) -> KeyColorsSettingsSche
|
|||||||
smoothing=settings.smoothing,
|
smoothing=settings.smoothing,
|
||||||
pattern_template_id=settings.pattern_template_id,
|
pattern_template_id=settings.pattern_template_id,
|
||||||
brightness=settings.brightness,
|
brightness=settings.brightness,
|
||||||
|
brightness_value_source_id=settings.brightness_value_source_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ def _kc_schema_to_settings(schema: KeyColorsSettingsSchema) -> KeyColorsSettings
|
|||||||
smoothing=schema.smoothing,
|
smoothing=schema.smoothing,
|
||||||
pattern_template_id=schema.pattern_template_id,
|
pattern_template_id=schema.pattern_template_id,
|
||||||
brightness=schema.brightness,
|
brightness=schema.brightness,
|
||||||
|
brightness_value_source_id=schema.brightness_value_source_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -254,6 +256,7 @@ async def update_target(
|
|||||||
smoothing=incoming.get("smoothing", ex.smoothing),
|
smoothing=incoming.get("smoothing", ex.smoothing),
|
||||||
pattern_template_id=incoming.get("pattern_template_id", ex.pattern_template_id),
|
pattern_template_id=incoming.get("pattern_template_id", ex.pattern_template_id),
|
||||||
brightness=incoming.get("brightness", ex.brightness),
|
brightness=incoming.get("brightness", ex.brightness),
|
||||||
|
brightness_value_source_id=incoming.get("brightness_value_source_id", ex.brightness_value_source_id),
|
||||||
)
|
)
|
||||||
kc_settings = _kc_schema_to_settings(merged)
|
kc_settings = _kc_schema_to_settings(merged)
|
||||||
else:
|
else:
|
||||||
@@ -273,6 +276,13 @@ async def update_target(
|
|||||||
description=data.description,
|
description=data.description,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Detect KC brightness VS change (inside key_colors_settings)
|
||||||
|
kc_brightness_vs_changed = False
|
||||||
|
if data.key_colors_settings is not None:
|
||||||
|
kc_incoming = data.key_colors_settings.model_dump(exclude_unset=True)
|
||||||
|
if "brightness_value_source_id" in kc_incoming:
|
||||||
|
kc_brightness_vs_changed = True
|
||||||
|
|
||||||
# Sync processor manager
|
# Sync processor manager
|
||||||
try:
|
try:
|
||||||
target.sync_with_manager(
|
target.sync_with_manager(
|
||||||
@@ -283,7 +293,7 @@ async def update_target(
|
|||||||
data.key_colors_settings is not None),
|
data.key_colors_settings is not None),
|
||||||
css_changed=data.color_strip_source_id is not None,
|
css_changed=data.color_strip_source_id is not None,
|
||||||
device_changed=data.device_id is not None,
|
device_changed=data.device_id is not None,
|
||||||
brightness_vs_changed=data.brightness_value_source_id is not None,
|
brightness_vs_changed=(data.brightness_value_source_id is not None or kc_brightness_vs_changed),
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class KeyColorsSettingsSchema(BaseModel):
|
|||||||
smoothing: float = Field(default=0.3, description="Temporal smoothing (0.0-1.0)", ge=0.0, le=1.0)
|
smoothing: float = Field(default=0.3, description="Temporal smoothing (0.0-1.0)", ge=0.0, le=1.0)
|
||||||
pattern_template_id: str = Field(default="", description="Pattern template ID for rectangle layout")
|
pattern_template_id: str = Field(default="", description="Pattern template ID for rectangle layout")
|
||||||
brightness: float = Field(default=1.0, description="Output brightness (0.0-1.0)", ge=0.0, le=1.0)
|
brightness: float = Field(default=1.0, description="Output brightness (0.0-1.0)", ge=0.0, le=1.0)
|
||||||
|
brightness_value_source_id: str = Field(default="", description="Brightness value source ID")
|
||||||
|
|
||||||
|
|
||||||
class ExtractedColorResponse(BaseModel):
|
class ExtractedColorResponse(BaseModel):
|
||||||
|
|||||||
@@ -96,9 +96,11 @@ class KCTargetProcessor(TargetProcessor):
|
|||||||
):
|
):
|
||||||
super().__init__(target_id, ctx, picture_source_id)
|
super().__init__(target_id, ctx, picture_source_id)
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
|
self._brightness_vs_id = settings.brightness_value_source_id if settings else ""
|
||||||
|
|
||||||
# Runtime state
|
# Runtime state
|
||||||
self._live_stream: Optional[LiveStream] = None
|
self._live_stream: Optional[LiveStream] = None
|
||||||
|
self._value_stream = None # active brightness value stream
|
||||||
self._previous_colors: Optional[Dict[str, Tuple[int, int, int]]] = None
|
self._previous_colors: Optional[Dict[str, Tuple[int, int, int]]] = None
|
||||||
self._latest_colors: Optional[Dict[str, Tuple[int, int, int]]] = None
|
self._latest_colors: Optional[Dict[str, Tuple[int, int, int]]] = None
|
||||||
self._ws_clients: List = []
|
self._ws_clients: List = []
|
||||||
@@ -156,6 +158,16 @@ class KCTargetProcessor(TargetProcessor):
|
|||||||
logger.error(f"Failed to initialize live stream for KC target {self._target_id}: {e}")
|
logger.error(f"Failed to initialize live stream for KC target {self._target_id}: {e}")
|
||||||
raise RuntimeError(f"Failed to initialize live stream: {e}")
|
raise RuntimeError(f"Failed to initialize live stream: {e}")
|
||||||
|
|
||||||
|
# Acquire value stream for brightness modulation (if configured)
|
||||||
|
if self._brightness_vs_id and self._ctx.value_stream_manager:
|
||||||
|
try:
|
||||||
|
self._value_stream = self._ctx.value_stream_manager.acquire(
|
||||||
|
self._brightness_vs_id, self._target_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to acquire value stream {self._brightness_vs_id}: {e}")
|
||||||
|
self._value_stream = None
|
||||||
|
|
||||||
# Reset metrics
|
# Reset metrics
|
||||||
self._metrics = ProcessingMetrics(start_time=datetime.utcnow())
|
self._metrics = ProcessingMetrics(start_time=datetime.utcnow())
|
||||||
self._previous_colors = None
|
self._previous_colors = None
|
||||||
@@ -192,6 +204,14 @@ class KCTargetProcessor(TargetProcessor):
|
|||||||
logger.warning(f"Error releasing live stream for KC target: {e}")
|
logger.warning(f"Error releasing live stream for KC target: {e}")
|
||||||
self._live_stream = None
|
self._live_stream = None
|
||||||
|
|
||||||
|
# Release value stream
|
||||||
|
if self._value_stream is not None and self._ctx.value_stream_manager:
|
||||||
|
try:
|
||||||
|
self._ctx.value_stream_manager.release(self._brightness_vs_id, self._target_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error releasing value stream: {e}")
|
||||||
|
self._value_stream = None
|
||||||
|
|
||||||
logger.info(f"Stopped KC processing for target {self._target_id}")
|
logger.info(f"Stopped KC processing for target {self._target_id}")
|
||||||
self._ctx.fire_event({"type": "state_change", "target_id": self._target_id, "processing": False})
|
self._ctx.fire_event({"type": "state_change", "target_id": self._target_id, "processing": False})
|
||||||
|
|
||||||
@@ -199,8 +219,37 @@ class KCTargetProcessor(TargetProcessor):
|
|||||||
|
|
||||||
def update_settings(self, settings) -> None:
|
def update_settings(self, settings) -> None:
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
|
# Keep _brightness_vs_id in sync (hot-swap handled separately)
|
||||||
|
self._brightness_vs_id = settings.brightness_value_source_id if settings else ""
|
||||||
logger.info(f"Updated KC target settings: {self._target_id}")
|
logger.info(f"Updated KC target settings: {self._target_id}")
|
||||||
|
|
||||||
|
def update_brightness_value_source(self, vs_id: str) -> None:
|
||||||
|
"""Hot-swap the brightness value source for a running KC target."""
|
||||||
|
old_vs_id = self._brightness_vs_id
|
||||||
|
self._brightness_vs_id = vs_id
|
||||||
|
vs_mgr = self._ctx.value_stream_manager
|
||||||
|
|
||||||
|
if not self._is_running or vs_mgr is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Release old stream
|
||||||
|
if self._value_stream is not None and old_vs_id:
|
||||||
|
try:
|
||||||
|
vs_mgr.release(old_vs_id, self._target_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error releasing old value stream {old_vs_id}: {e}")
|
||||||
|
self._value_stream = None
|
||||||
|
|
||||||
|
# Acquire new stream
|
||||||
|
if vs_id:
|
||||||
|
try:
|
||||||
|
self._value_stream = vs_mgr.acquire(vs_id, self._target_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to acquire value stream {vs_id}: {e}")
|
||||||
|
self._value_stream = None
|
||||||
|
|
||||||
|
logger.info(f"Hot-swapped brightness VS for KC target {self._target_id}: {old_vs_id} -> {vs_id}")
|
||||||
|
|
||||||
# ----- State / Metrics -----
|
# ----- State / Metrics -----
|
||||||
|
|
||||||
def get_state(self) -> dict:
|
def get_state(self) -> dict:
|
||||||
@@ -220,6 +269,7 @@ class KCTargetProcessor(TargetProcessor):
|
|||||||
"timing_total_ms": round(metrics.timing_total_ms, 1) if self._is_running else None,
|
"timing_total_ms": round(metrics.timing_total_ms, 1) if self._is_running else None,
|
||||||
"last_update": metrics.last_update,
|
"last_update": metrics.last_update,
|
||||||
"errors": [metrics.last_error] if metrics.last_error else [],
|
"errors": [metrics.last_error] if metrics.last_error else [],
|
||||||
|
"brightness_value_source_id": self._brightness_vs_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_metrics(self) -> dict:
|
def get_metrics(self) -> dict:
|
||||||
@@ -333,11 +383,17 @@ class KCTargetProcessor(TargetProcessor):
|
|||||||
s = self._settings
|
s = self._settings
|
||||||
calc_fn = calc_fns.get(s.interpolation_mode, calculate_average_color)
|
calc_fn = calc_fns.get(s.interpolation_mode, calculate_average_color)
|
||||||
|
|
||||||
|
# Effective brightness: static setting * value stream
|
||||||
|
eff_brightness = s.brightness
|
||||||
|
vs = self._value_stream
|
||||||
|
if vs is not None:
|
||||||
|
eff_brightness *= vs.get_value()
|
||||||
|
|
||||||
# CPU-bound work in thread pool
|
# CPU-bound work in thread pool
|
||||||
colors, colors_arr, frame_timing = await asyncio.to_thread(
|
colors, colors_arr, frame_timing = await asyncio.to_thread(
|
||||||
_process_kc_frame,
|
_process_kc_frame,
|
||||||
capture, rect_names, rect_bounds, calc_fn,
|
capture, rect_names, rect_bounds, calc_fn,
|
||||||
prev_colors_arr, s.smoothing, s.brightness,
|
prev_colors_arr, s.smoothing, eff_brightness,
|
||||||
)
|
)
|
||||||
|
|
||||||
prev_colors_arr = colors_arr
|
prev_colors_arr = colors_arr
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
_kcNameManuallyEdited, set_kcNameManuallyEdited,
|
_kcNameManuallyEdited, set_kcNameManuallyEdited,
|
||||||
kcWebSockets,
|
kcWebSockets,
|
||||||
PATTERN_RECT_BORDERS,
|
PATTERN_RECT_BORDERS,
|
||||||
|
_cachedValueSources, set_cachedValueSources,
|
||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js';
|
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.js';
|
import { t } from '../core/i18n.js';
|
||||||
@@ -27,6 +28,7 @@ class KCEditorModal extends Modal {
|
|||||||
interpolation: document.getElementById('kc-editor-interpolation').value,
|
interpolation: document.getElementById('kc-editor-interpolation').value,
|
||||||
smoothing: document.getElementById('kc-editor-smoothing').value,
|
smoothing: document.getElementById('kc-editor-smoothing').value,
|
||||||
patternTemplateId: document.getElementById('kc-editor-pattern-template').value,
|
patternTemplateId: document.getElementById('kc-editor-pattern-template').value,
|
||||||
|
brightness_vs: document.getElementById('kc-editor-brightness-vs').value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,15 +350,33 @@ function _autoGenerateKCName() {
|
|||||||
document.getElementById('kc-editor-name').value = `${sourceName} \u00b7 ${patName} (${modeName})`;
|
document.getElementById('kc-editor-name').value = `${sourceName} \u00b7 ${patName} (${modeName})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _populateKCBrightnessVsDropdown(selectedId = '') {
|
||||||
|
const sel = document.getElementById('kc-editor-brightness-vs');
|
||||||
|
// Keep the first "None" option, remove the rest
|
||||||
|
while (sel.options.length > 1) sel.remove(1);
|
||||||
|
_cachedValueSources.forEach(vs => {
|
||||||
|
const typeIcons = { static: '📊', animated: '🔄', audio: '🎵' };
|
||||||
|
const icon = typeIcons[vs.source_type] || '🔢';
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = vs.id;
|
||||||
|
opt.textContent = `${icon} ${vs.name}`;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
});
|
||||||
|
sel.value = selectedId || '';
|
||||||
|
}
|
||||||
|
|
||||||
export async function showKCEditor(targetId = null, cloneData = null) {
|
export async function showKCEditor(targetId = null, cloneData = null) {
|
||||||
try {
|
try {
|
||||||
// Load sources and pattern templates in parallel
|
// Load sources, pattern templates, and value sources in parallel
|
||||||
const [sourcesResp, patResp] = await Promise.all([
|
const [sourcesResp, patResp, vsResp] = await Promise.all([
|
||||||
fetchWithAuth('/picture-sources').catch(() => null),
|
fetchWithAuth('/picture-sources').catch(() => null),
|
||||||
fetchWithAuth('/pattern-templates').catch(() => null),
|
fetchWithAuth('/pattern-templates').catch(() => null),
|
||||||
|
fetchWithAuth('/value-sources').catch(() => null),
|
||||||
]);
|
]);
|
||||||
const sources = (sourcesResp && sourcesResp.ok) ? (await sourcesResp.json()).streams || [] : [];
|
const sources = (sourcesResp && sourcesResp.ok) ? (await sourcesResp.json()).streams || [] : [];
|
||||||
const patTemplates = (patResp && patResp.ok) ? (await patResp.json()).templates || [] : [];
|
const patTemplates = (patResp && patResp.ok) ? (await patResp.json()).templates || [] : [];
|
||||||
|
const valueSources = (vsResp && vsResp.ok) ? (await vsResp.json()).sources || [] : [];
|
||||||
|
set_cachedValueSources(valueSources);
|
||||||
|
|
||||||
// Populate source select
|
// Populate source select
|
||||||
const sourceSelect = document.getElementById('kc-editor-source');
|
const sourceSelect = document.getElementById('kc-editor-source');
|
||||||
@@ -397,6 +417,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
|
|||||||
document.getElementById('kc-editor-smoothing').value = kcSettings.smoothing ?? 0.3;
|
document.getElementById('kc-editor-smoothing').value = kcSettings.smoothing ?? 0.3;
|
||||||
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
|
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
|
||||||
patSelect.value = kcSettings.pattern_template_id || '';
|
patSelect.value = kcSettings.pattern_template_id || '';
|
||||||
|
_populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
|
||||||
document.getElementById('kc-editor-title').textContent = t('kc.edit');
|
document.getElementById('kc-editor-title').textContent = t('kc.edit');
|
||||||
} else if (cloneData) {
|
} else if (cloneData) {
|
||||||
const kcSettings = cloneData.key_colors_settings || {};
|
const kcSettings = cloneData.key_colors_settings || {};
|
||||||
@@ -409,6 +430,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
|
|||||||
document.getElementById('kc-editor-smoothing').value = kcSettings.smoothing ?? 0.3;
|
document.getElementById('kc-editor-smoothing').value = kcSettings.smoothing ?? 0.3;
|
||||||
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
|
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
|
||||||
patSelect.value = kcSettings.pattern_template_id || '';
|
patSelect.value = kcSettings.pattern_template_id || '';
|
||||||
|
_populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
|
||||||
document.getElementById('kc-editor-title').textContent = t('kc.add');
|
document.getElementById('kc-editor-title').textContent = t('kc.add');
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('kc-editor-id').value = '';
|
document.getElementById('kc-editor-id').value = '';
|
||||||
@@ -420,6 +442,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
|
|||||||
document.getElementById('kc-editor-smoothing').value = 0.3;
|
document.getElementById('kc-editor-smoothing').value = 0.3;
|
||||||
document.getElementById('kc-editor-smoothing-value').textContent = '0.3';
|
document.getElementById('kc-editor-smoothing-value').textContent = '0.3';
|
||||||
if (patTemplates.length > 0) patSelect.value = patTemplates[0].id;
|
if (patTemplates.length > 0) patSelect.value = patTemplates[0].id;
|
||||||
|
_populateKCBrightnessVsDropdown('');
|
||||||
document.getElementById('kc-editor-title').textContent = t('kc.add');
|
document.getElementById('kc-editor-title').textContent = t('kc.add');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +487,7 @@ export async function saveKCEditor() {
|
|||||||
const interpolation = document.getElementById('kc-editor-interpolation').value;
|
const interpolation = document.getElementById('kc-editor-interpolation').value;
|
||||||
const smoothing = parseFloat(document.getElementById('kc-editor-smoothing').value);
|
const smoothing = parseFloat(document.getElementById('kc-editor-smoothing').value);
|
||||||
const patternTemplateId = document.getElementById('kc-editor-pattern-template').value;
|
const patternTemplateId = document.getElementById('kc-editor-pattern-template').value;
|
||||||
|
const brightnessVsId = document.getElementById('kc-editor-brightness-vs').value;
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
kcEditorModal.showError(t('kc.error.required'));
|
kcEditorModal.showError(t('kc.error.required'));
|
||||||
@@ -483,6 +507,7 @@ export async function saveKCEditor() {
|
|||||||
interpolation_mode: interpolation,
|
interpolation_mode: interpolation,
|
||||||
smoothing,
|
smoothing,
|
||||||
pattern_template_id: patternTemplateId,
|
pattern_template_id: patternTemplateId,
|
||||||
|
brightness_value_source_id: brightnessVsId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -492,10 +492,10 @@ export async function loadPictureSources() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function switchStreamTab(tabKey) {
|
export function switchStreamTab(tabKey) {
|
||||||
document.querySelectorAll('.stream-tab-btn').forEach(btn =>
|
document.querySelectorAll('.stream-tab-btn[data-stream-tab]').forEach(btn =>
|
||||||
btn.classList.toggle('active', btn.dataset.streamTab === tabKey)
|
btn.classList.toggle('active', btn.dataset.streamTab === tabKey)
|
||||||
);
|
);
|
||||||
document.querySelectorAll('.stream-tab-panel').forEach(panel =>
|
document.querySelectorAll('.stream-tab-panel[id^="stream-tab-"]').forEach(panel =>
|
||||||
panel.classList.toggle('active', panel.id === `stream-tab-${tabKey}`)
|
panel.classList.toggle('active', panel.id === `stream-tab-${tabKey}`)
|
||||||
);
|
);
|
||||||
localStorage.setItem('activeStreamTab', tabKey);
|
localStorage.setItem('activeStreamTab', tabKey);
|
||||||
@@ -629,7 +629,7 @@ function renderPictureSourcesList(streams) {
|
|||||||
{ key: 'static_image', icon: '🖼️', titleKey: 'streams.group.static_image', count: staticImageStreams.length },
|
{ key: 'static_image', icon: '🖼️', titleKey: 'streams.group.static_image', count: staticImageStreams.length },
|
||||||
{ key: 'processed', icon: '🎨', titleKey: 'streams.group.processed', count: processedStreams.length },
|
{ key: 'processed', icon: '🎨', titleKey: 'streams.group.processed', count: processedStreams.length },
|
||||||
{ key: 'audio', icon: '🔊', titleKey: 'streams.group.audio', count: _cachedAudioSources.length },
|
{ key: 'audio', icon: '🔊', titleKey: 'streams.group.audio', count: _cachedAudioSources.length },
|
||||||
{ key: 'value', icon: '🎚️', titleKey: 'streams.group.value', count: _cachedValueSources.length },
|
{ key: 'value', icon: '🔢', titleKey: 'streams.group.value', count: _cachedValueSources.length },
|
||||||
];
|
];
|
||||||
|
|
||||||
const tabBar = `<div class="stream-tab-bar">${tabs.map(tab =>
|
const tabBar = `<div class="stream-tab-bar">${tabs.map(tab =>
|
||||||
|
|||||||
@@ -424,6 +424,9 @@
|
|||||||
"kc.pattern_template": "Pattern Template:",
|
"kc.pattern_template": "Pattern Template:",
|
||||||
"kc.pattern_template.hint": "Select the rectangle pattern to use for color extraction",
|
"kc.pattern_template.hint": "Select the rectangle pattern to use for color extraction",
|
||||||
"kc.pattern_template.none": "-- Select a pattern template --",
|
"kc.pattern_template.none": "-- Select a pattern template --",
|
||||||
|
"kc.brightness_vs": "🔢 Brightness Source:",
|
||||||
|
"kc.brightness_vs.hint": "Optional value source that dynamically controls brightness each frame (multiplied with the manual brightness slider)",
|
||||||
|
"kc.brightness_vs.none": "None (manual brightness only)",
|
||||||
"kc.created": "Key colors target created successfully",
|
"kc.created": "Key colors target created successfully",
|
||||||
"kc.updated": "Key colors target updated successfully",
|
"kc.updated": "Key colors target updated successfully",
|
||||||
"kc.deleted": "Key colors target deleted successfully",
|
"kc.deleted": "Key colors target deleted successfully",
|
||||||
@@ -763,7 +766,7 @@
|
|||||||
"audio_source.error.name_required": "Please enter a name",
|
"audio_source.error.name_required": "Please enter a name",
|
||||||
|
|
||||||
"streams.group.value": "Value Sources",
|
"streams.group.value": "Value Sources",
|
||||||
"value_source.group.title": "🎚️ Value Sources",
|
"value_source.group.title": "🔢 Value Sources",
|
||||||
"value_source.add": "Add Value Source",
|
"value_source.add": "Add Value Source",
|
||||||
"value_source.edit": "Edit Value Source",
|
"value_source.edit": "Edit Value Source",
|
||||||
"value_source.name": "Name:",
|
"value_source.name": "Name:",
|
||||||
@@ -807,7 +810,7 @@
|
|||||||
"value_source.deleted": "Value source deleted",
|
"value_source.deleted": "Value source deleted",
|
||||||
"value_source.delete.confirm": "Are you sure you want to delete this value source?",
|
"value_source.delete.confirm": "Are you sure you want to delete this value source?",
|
||||||
"value_source.error.name_required": "Please enter a name",
|
"value_source.error.name_required": "Please enter a name",
|
||||||
"targets.brightness_vs": "Brightness Source:",
|
"targets.brightness_vs": "🔢 Brightness Source:",
|
||||||
"targets.brightness_vs.hint": "Optional value source that dynamically controls brightness each frame (overrides device brightness)",
|
"targets.brightness_vs.hint": "Optional value source that dynamically controls brightness each frame (overrides device brightness)",
|
||||||
"targets.brightness_vs.none": "None (device brightness)"
|
"targets.brightness_vs.none": "None (device brightness)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -424,6 +424,9 @@
|
|||||||
"kc.pattern_template": "Шаблон Паттерна:",
|
"kc.pattern_template": "Шаблон Паттерна:",
|
||||||
"kc.pattern_template.hint": "Выберите шаблон прямоугольников для извлечения цветов",
|
"kc.pattern_template.hint": "Выберите шаблон прямоугольников для извлечения цветов",
|
||||||
"kc.pattern_template.none": "-- Выберите шаблон паттерна --",
|
"kc.pattern_template.none": "-- Выберите шаблон паттерна --",
|
||||||
|
"kc.brightness_vs": "🔢 Источник Яркости:",
|
||||||
|
"kc.brightness_vs.hint": "Опциональный источник значений, динамически управляющий яркостью каждый кадр (умножается на ручной слайдер яркости)",
|
||||||
|
"kc.brightness_vs.none": "Нет (только ручная яркость)",
|
||||||
"kc.created": "Цель ключевых цветов успешно создана",
|
"kc.created": "Цель ключевых цветов успешно создана",
|
||||||
"kc.updated": "Цель ключевых цветов успешно обновлена",
|
"kc.updated": "Цель ключевых цветов успешно обновлена",
|
||||||
"kc.deleted": "Цель ключевых цветов успешно удалена",
|
"kc.deleted": "Цель ключевых цветов успешно удалена",
|
||||||
@@ -763,7 +766,7 @@
|
|||||||
"audio_source.error.name_required": "Введите название",
|
"audio_source.error.name_required": "Введите название",
|
||||||
|
|
||||||
"streams.group.value": "Источники значений",
|
"streams.group.value": "Источники значений",
|
||||||
"value_source.group.title": "🎚️ Источники значений",
|
"value_source.group.title": "🔢 Источники значений",
|
||||||
"value_source.add": "Добавить источник значений",
|
"value_source.add": "Добавить источник значений",
|
||||||
"value_source.edit": "Редактировать источник значений",
|
"value_source.edit": "Редактировать источник значений",
|
||||||
"value_source.name": "Название:",
|
"value_source.name": "Название:",
|
||||||
@@ -807,7 +810,7 @@
|
|||||||
"value_source.deleted": "Источник значений удалён",
|
"value_source.deleted": "Источник значений удалён",
|
||||||
"value_source.delete.confirm": "Удалить этот источник значений?",
|
"value_source.delete.confirm": "Удалить этот источник значений?",
|
||||||
"value_source.error.name_required": "Введите название",
|
"value_source.error.name_required": "Введите название",
|
||||||
"targets.brightness_vs": "Источник яркости:",
|
"targets.brightness_vs": "🔢 Источник яркости:",
|
||||||
"targets.brightness_vs.hint": "Необязательный источник значений для динамического управления яркостью каждый кадр (переопределяет яркость устройства)",
|
"targets.brightness_vs.hint": "Необязательный источник значений для динамического управления яркостью каждый кадр (переопределяет яркость устройства)",
|
||||||
"targets.brightness_vs.none": "Нет (яркость устройства)"
|
"targets.brightness_vs.none": "Нет (яркость устройства)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class KeyColorsSettings:
|
|||||||
smoothing: float = 0.3
|
smoothing: float = 0.3
|
||||||
pattern_template_id: str = ""
|
pattern_template_id: str = ""
|
||||||
brightness: float = 1.0
|
brightness: float = 1.0
|
||||||
|
brightness_value_source_id: str = ""
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {
|
return {
|
||||||
@@ -54,6 +55,7 @@ class KeyColorsSettings:
|
|||||||
"smoothing": self.smoothing,
|
"smoothing": self.smoothing,
|
||||||
"pattern_template_id": self.pattern_template_id,
|
"pattern_template_id": self.pattern_template_id,
|
||||||
"brightness": self.brightness,
|
"brightness": self.brightness,
|
||||||
|
"brightness_value_source_id": self.brightness_value_source_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -64,6 +66,7 @@ class KeyColorsSettings:
|
|||||||
smoothing=data.get("smoothing", 0.3),
|
smoothing=data.get("smoothing", 0.3),
|
||||||
pattern_template_id=data.get("pattern_template_id", ""),
|
pattern_template_id=data.get("pattern_template_id", ""),
|
||||||
brightness=data.get("brightness", 1.0),
|
brightness=data.get("brightness", 1.0),
|
||||||
|
brightness_value_source_id=data.get("brightness_value_source_id", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -82,12 +85,18 @@ class KeyColorsPictureTarget(PictureTarget):
|
|||||||
settings=self.settings,
|
settings=self.settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
def sync_with_manager(self, manager, *, settings_changed: bool, source_changed: bool, device_changed: bool) -> None:
|
def sync_with_manager(self, manager, *, settings_changed: bool,
|
||||||
|
source_changed: bool = False,
|
||||||
|
css_changed: bool = False,
|
||||||
|
device_changed: bool = False,
|
||||||
|
brightness_vs_changed: bool = False) -> None:
|
||||||
"""Push changed fields to the processor manager."""
|
"""Push changed fields to the processor manager."""
|
||||||
if settings_changed:
|
if settings_changed:
|
||||||
manager.update_target_settings(self.id, self.settings)
|
manager.update_target_settings(self.id, self.settings)
|
||||||
if source_changed:
|
if source_changed:
|
||||||
manager.update_target_source(self.id, self.picture_source_id)
|
manager.update_target_source(self.id, self.picture_source_id)
|
||||||
|
if brightness_vs_changed:
|
||||||
|
manager.update_target_brightness_vs(self.id, self.settings.brightness_value_source_id)
|
||||||
|
|
||||||
def update_fields(self, *, name=None, device_id=None, picture_source_id=None,
|
def update_fields(self, *, name=None, device_id=None, picture_source_id=None,
|
||||||
settings=None, key_colors_settings=None, description=None,
|
settings=None, key_colors_settings=None, description=None,
|
||||||
|
|||||||
@@ -32,6 +32,17 @@
|
|||||||
<select id="kc-editor-pattern-template"></select>
|
<select id="kc-editor-pattern-template"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="label-row">
|
||||||
|
<label for="kc-editor-brightness-vs" data-i18n="kc.brightness_vs">🔢 Brightness Source:</label>
|
||||||
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||||
|
</div>
|
||||||
|
<small class="input-hint" style="display:none" data-i18n="kc.brightness_vs.hint">Optional value source that dynamically controls brightness each frame (multiplied with the manual brightness slider)</small>
|
||||||
|
<select id="kc-editor-brightness-vs">
|
||||||
|
<option value="" data-i18n="kc.brightness_vs.none">None (manual brightness only)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="label-row">
|
<div class="label-row">
|
||||||
<label for="kc-editor-fps" data-i18n="kc.fps">Extraction FPS:</label>
|
<label for="kc-editor-fps" data-i18n="kc.fps">Extraction FPS:</label>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="label-row">
|
<div class="label-row">
|
||||||
<label for="target-editor-brightness-vs" data-i18n="targets.brightness_vs">Brightness Source:</label>
|
<label for="target-editor-brightness-vs" data-i18n="targets.brightness_vs">🔢 Brightness Source:</label>
|
||||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
||||||
</div>
|
</div>
|
||||||
<small class="input-hint" style="display:none" data-i18n="targets.brightness_vs.hint">Optional value source that dynamically controls brightness each frame (overrides device brightness)</small>
|
<small class="input-hint" style="display:none" data-i18n="targets.brightness_vs.hint">Optional value source that dynamically controls brightness each frame (overrides device brightness)</small>
|
||||||
|
|||||||
Reference in New Issue
Block a user