Add Picture Streams architecture with postprocessing templates and stream test UI

Introduce Picture Stream abstraction that separates the capture pipeline into
composable layers: raw streams (display + capture engine + FPS) and processed
streams (source stream + postprocessing template). Devices reference a picture
stream instead of managing individual capture settings.

- Add PictureStream and PostprocessingTemplate data models and stores
- Add CRUD API endpoints for picture streams and postprocessing templates
- Add stream chain resolution in ProcessorManager for start_processing
- Add picture stream test endpoint with postprocessing preview support
- Add Stream Settings modal with border_width and interpolation_mode controls
- Add stream test modal with capture preview and performance metrics
- Add full frontend: Picture Streams tab, Processing Templates tab, stream
  selector on device cards, test buttons on stream cards
- Add localization keys for all new features (en, ru)
- Migrate existing devices to picture streams on startup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 00:00:30 +03:00
parent 3db7ba4b0e
commit 493f14fba9
23 changed files with 2773 additions and 200 deletions

View File

@@ -1762,24 +1762,6 @@ input:-webkit-autofill:focus {
gap: 20px;
}
.templates-separator {
grid-column: 1 / -1;
display: flex;
align-items: center;
gap: 12px;
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 500;
}
.templates-separator::before,
.templates-separator::after {
content: '';
flex: 1;
height: 1px;
background: var(--border-color);
}
.template-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
@@ -1839,7 +1821,7 @@ input:-webkit-autofill:focus {
margin-bottom: 12px;
}
.template-card:has(.card-remove-btn) .template-card-header {
.template-card .template-card-header {
padding-right: 24px;
}
@@ -1857,11 +1839,6 @@ input:-webkit-autofill:focus {
text-transform: uppercase;
}
.badge-default {
background: var(--primary-color);
color: white;
}
.template-description {
color: var(--text-secondary);
font-size: 14px;
@@ -2038,6 +2015,36 @@ input:-webkit-autofill:focus {
font-size: 16px;
}
/* Stream type badges */
.badge-raw {
background: #1976d2;
color: white;
}
.badge-processed {
background: #7b1fa2;
color: white;
}
/* Stream info panel in stream selector modal */
.stream-info-panel {
background: var(--bg-secondary, #2a2a2a);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 12px 16px;
margin-top: 12px;
font-size: 14px;
line-height: 1.6;
}
.stream-info-panel div {
margin-bottom: 4px;
}
.stream-info-panel strong {
margin-right: 6px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.templates-grid {