Add sync clock entity for synchronized animation timing

Introduces Synchronization Clocks — shared, controllable time bases
that CSS sources can optionally reference for synchronized animation.

Backend:
- New SyncClock dataclass, JSON store, Pydantic schemas, REST API
- Runtime clock with thread-safe pause/resume/reset and speed control
- Ref-counted runtime pool with eager creation for API control
- clock_id field on all ColorStripSource types
- Stream integration: clock time/speed replaces source-local values
- Paused clock skips rendering (saves CPU + stops frame pushes)
- Included in backup/restore via STORE_MAP

Frontend:
- Sync Clocks tab in Streams section with cards and controls
- Clock dropdown in CSS editor (hidden speed slider when clock set)
- Clock crosslink badge on CSS source cards (replaces speed badge)
- Targets tab uses DataCache for picture/audio sources and sync clocks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 21:46:55 +03:00
parent 52ee4bdeb6
commit aa1e4a6afc
32 changed files with 1255 additions and 58 deletions

View File

@@ -307,6 +307,7 @@
"common.delete": "Delete",
"common.edit": "Edit",
"common.clone": "Clone",
"common.none": "None",
"section.filter.placeholder": "Filter...",
"section.filter.reset": "Clear filter",
"section.expand_all": "Expand all sections",
@@ -950,6 +951,7 @@
"audio_template.error.required": "Please fill in all required fields",
"audio_template.error.delete": "Failed to delete audio template",
"streams.group.value": "Value Sources",
"streams.group.sync": "Sync Clocks",
"value_source.group.title": "Value Sources",
"value_source.add": "Add Value Source",
"value_source.edit": "Edit Value Source",
@@ -1137,5 +1139,32 @@
"theme.switched.dark": "Switched to dark theme",
"theme.switched.light": "Switched to light theme",
"accent.color.updated": "Accent color updated",
"search.footer": "↑↓ navigate · Enter select · Esc close"
"search.footer": "↑↓ navigate · Enter select · Esc close",
"sync_clock.group.title": "Sync Clocks",
"sync_clock.add": "Add Sync Clock",
"sync_clock.edit": "Edit Sync Clock",
"sync_clock.name": "Name:",
"sync_clock.name.placeholder": "Main Animation Clock",
"sync_clock.name.hint": "A descriptive name for this synchronization clock",
"sync_clock.speed": "Speed:",
"sync_clock.speed.hint": "Speed multiplier shared by all linked sources. 1.0 = normal speed.",
"sync_clock.description": "Description (optional):",
"sync_clock.description.placeholder": "Optional description",
"sync_clock.description.hint": "Optional notes about this clock's purpose",
"sync_clock.status.running": "Running",
"sync_clock.status.paused": "Paused",
"sync_clock.action.pause": "Pause",
"sync_clock.action.resume": "Resume",
"sync_clock.action.reset": "Reset",
"sync_clock.error.name_required": "Clock name is required",
"sync_clock.error.load": "Failed to load sync clock",
"sync_clock.created": "Sync clock created",
"sync_clock.updated": "Sync clock updated",
"sync_clock.deleted": "Sync clock deleted",
"sync_clock.paused": "Clock paused",
"sync_clock.resumed": "Clock resumed",
"sync_clock.reset_done": "Clock reset to zero",
"sync_clock.delete.confirm": "Delete this sync clock? Sources using it will revert to their own speed.",
"color_strip.clock": "Sync Clock:",
"color_strip.clock.hint": "Link to a sync clock for synchronized animation. When set, speed comes from the clock."
}

View File

@@ -307,6 +307,7 @@
"common.delete": "Удалить",
"common.edit": "Редактировать",
"common.clone": "Клонировать",
"common.none": "Нет",
"section.filter.placeholder": "Фильтр...",
"section.filter.reset": "Очистить фильтр",
"section.expand_all": "Развернуть все секции",
@@ -950,6 +951,7 @@
"audio_template.error.required": "Пожалуйста, заполните все обязательные поля",
"audio_template.error.delete": "Не удалось удалить аудиошаблон",
"streams.group.value": "Источники значений",
"streams.group.sync": "Часы синхронизации",
"value_source.group.title": "Источники значений",
"value_source.add": "Добавить источник значений",
"value_source.edit": "Редактировать источник значений",
@@ -1137,5 +1139,32 @@
"theme.switched.dark": "Переключено на тёмную тему",
"theme.switched.light": "Переключено на светлую тему",
"accent.color.updated": "Цвет акцента обновлён",
"search.footer": "↑↓ навигация · Enter выбор · Esc закрыть"
"search.footer": "↑↓ навигация · Enter выбор · Esc закрыть",
"sync_clock.group.title": "Часы синхронизации",
"sync_clock.add": "Добавить часы",
"sync_clock.edit": "Редактировать часы",
"sync_clock.name": "Название:",
"sync_clock.name.placeholder": "Основные часы анимации",
"sync_clock.name.hint": "Описательное название для этих часов синхронизации",
"sync_clock.speed": "Скорость:",
"sync_clock.speed.hint": "Множитель скорости, общий для всех привязанных источников. 1.0 = нормальная скорость.",
"sync_clock.description": "Описание (необязательно):",
"sync_clock.description.placeholder": "Необязательное описание",
"sync_clock.description.hint": "Необязательные заметки о назначении этих часов",
"sync_clock.status.running": "Работает",
"sync_clock.status.paused": "Приостановлено",
"sync_clock.action.pause": "Приостановить",
"sync_clock.action.resume": "Возобновить",
"sync_clock.action.reset": "Сбросить",
"sync_clock.error.name_required": "Название часов обязательно",
"sync_clock.error.load": "Не удалось загрузить часы синхронизации",
"sync_clock.created": "Часы синхронизации созданы",
"sync_clock.updated": "Часы синхронизации обновлены",
"sync_clock.deleted": "Часы синхронизации удалены",
"sync_clock.paused": "Часы приостановлены",
"sync_clock.resumed": "Часы возобновлены",
"sync_clock.reset_done": "Часы сброшены на ноль",
"sync_clock.delete.confirm": "Удалить эти часы синхронизации? Источники, использующие их, вернутся к собственной скорости.",
"color_strip.clock": "Часы синхронизации:",
"color_strip.clock.hint": "Привязка к часам синхронизации для синхронной анимации. При установке скорость берётся из часов."
}

View File

@@ -307,6 +307,7 @@
"common.delete": "删除",
"common.edit": "编辑",
"common.clone": "克隆",
"common.none": "无",
"section.filter.placeholder": "筛选...",
"section.filter.reset": "清除筛选",
"section.expand_all": "全部展开",
@@ -950,6 +951,7 @@
"audio_template.error.required": "请填写所有必填项",
"audio_template.error.delete": "删除音频模板失败",
"streams.group.value": "值源",
"streams.group.sync": "同步时钟",
"value_source.group.title": "值源",
"value_source.add": "添加值源",
"value_source.edit": "编辑值源",
@@ -1137,5 +1139,32 @@
"theme.switched.dark": "已切换到深色主题",
"theme.switched.light": "已切换到浅色主题",
"accent.color.updated": "强调色已更新",
"search.footer": "↑↓ 导航 · Enter 选择 · Esc 关闭"
"search.footer": "↑↓ 导航 · Enter 选择 · Esc 关闭",
"sync_clock.group.title": "同步时钟",
"sync_clock.add": "添加同步时钟",
"sync_clock.edit": "编辑同步时钟",
"sync_clock.name": "名称:",
"sync_clock.name.placeholder": "主动画时钟",
"sync_clock.name.hint": "此同步时钟的描述性名称",
"sync_clock.speed": "速度:",
"sync_clock.speed.hint": "所有关联源共享的速度倍率。1.0 = 正常速度。",
"sync_clock.description": "描述(可选):",
"sync_clock.description.placeholder": "可选描述",
"sync_clock.description.hint": "关于此时钟用途的可选备注",
"sync_clock.status.running": "运行中",
"sync_clock.status.paused": "已暂停",
"sync_clock.action.pause": "暂停",
"sync_clock.action.resume": "恢复",
"sync_clock.action.reset": "重置",
"sync_clock.error.name_required": "时钟名称为必填项",
"sync_clock.error.load": "加载同步时钟失败",
"sync_clock.created": "同步时钟已创建",
"sync_clock.updated": "同步时钟已更新",
"sync_clock.deleted": "同步时钟已删除",
"sync_clock.paused": "时钟已暂停",
"sync_clock.resumed": "时钟已恢复",
"sync_clock.reset_done": "时钟已重置为零",
"sync_clock.delete.confirm": "删除此同步时钟?使用它的源将恢复为各自的速度。",
"color_strip.clock": "同步时钟:",
"color_strip.clock.hint": "关联同步时钟以实现同步动画。设置后,速度将来自时钟。"
}