CPU-only online speaker labelling for caption windows (CAPTION_DIARIZATION=1): pyannote segmentation-3.0 finds local turns, a TitaNet-small voiceprint via sherpa-onnx matches them to per-room speakers numbered in arrival order. /transcribe?speakers=1 returns committed words grouped into segments with a speaker ID and the name assigned for the show. Models are SHA-256 pinned and cached with the Whisper weights; audio and voiceprints are never stored. Rooms gain a persisted cast list and in-memory per-show names through /api/rooms/<id>/speakers. The capture page offers "Identify speakers automatically", publishes one relay caption per speaker turn with label and spk, and shares a speaker-names panel (speaker-names.js) with the editor. Also adds scripts/diarize_eval.py, deploy.py --speakers, and copies rooms.py into the GPU image.
105 lines
3.1 KiB
CSS
105 lines
3.1 KiB
CSS
:root {
|
|
color-scheme: dark;
|
|
--cc-bg: #0f1420;
|
|
--cc-panel: #172338;
|
|
--cc-panel-2: #0c111c;
|
|
--cc-border: #2c3c58;
|
|
--cc-text: #eef3fb;
|
|
--cc-muted: #9fb0cb;
|
|
--cc-accent: #2498eb;
|
|
--cc-accent-strong: #1769aa;
|
|
--cc-good: #4fd47f;
|
|
--cc-warn: #f2c14e;
|
|
--cc-danger: #ff7777;
|
|
--cc-radius: 12px;
|
|
--cc-radius-sm: 8px;
|
|
--cc-font: 16px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
}
|
|
|
|
.cc-dot {
|
|
display: inline-block;
|
|
flex: 0 0 auto;
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 50%;
|
|
background: #6f7d92;
|
|
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.18);
|
|
vertical-align: -3px;
|
|
}
|
|
|
|
.cc-dot[data-state="good"] {
|
|
background: var(--cc-good);
|
|
box-shadow: 0 0 0 2px rgba(79, 212, 127, 0.18);
|
|
}
|
|
|
|
.cc-dot[data-state="warn"] {
|
|
background: var(--cc-warn);
|
|
box-shadow: 0 0 0 2px rgba(242, 193, 78, 0.18);
|
|
}
|
|
|
|
.cc-dot[data-state="danger"] {
|
|
background: var(--cc-danger);
|
|
box-shadow: 0 0 0 2px rgba(255, 119, 119, 0.18);
|
|
}
|
|
|
|
.cc-status-row {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-height: 28px;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*, *::before, *::after {
|
|
animation-duration: 0.01ms !important;
|
|
transition-duration: 0.01ms !important;
|
|
}
|
|
}
|
|
|
|
.cc-room-menu { width: min(820px, calc(100% - 32px)); margin: 24px auto 18px; }
|
|
.cc-room-menu[hidden] { display: none; }
|
|
.cc-room-menu-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
align-items: center;
|
|
margin: 0;
|
|
padding: 10px;
|
|
list-style: none;
|
|
background: var(--cc-panel);
|
|
border: 1px solid var(--cc-border);
|
|
border-radius: var(--cc-radius);
|
|
}
|
|
.cc-room-menu-list li { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
.cc-room-menu-list li + li::before { content: "→"; color: var(--cc-muted); }
|
|
.cc-room-menu-item {
|
|
display: inline-block;
|
|
padding: 7px 10px;
|
|
border: 1px solid transparent;
|
|
border-radius: 7px;
|
|
color: var(--cc-text);
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
}
|
|
.cc-room-menu-item:hover { border-color: var(--cc-accent); }
|
|
.cc-room-menu-item.is-current {
|
|
border-color: var(--cc-accent);
|
|
background: var(--cc-panel-2);
|
|
font-weight: 650;
|
|
}
|
|
.cc-room-menu-item.is-disabled { color: var(--cc-muted); opacity: 0.65; cursor: not-allowed; }
|
|
|
|
@media (max-width: 600px) {
|
|
.cc-room-menu-list li + li::before { display: none; }
|
|
}
|
|
.cc-speaker-list { display: grid; gap: 8px; margin: 10px 0; }
|
|
.cc-speaker-row { display: grid; grid-template-columns: minmax(90px, max-content) minmax(0, 1fr); align-items: center; gap: 10px; }
|
|
.cc-speaker-row > span { color: var(--cc-muted); font-weight: 650; }
|
|
.cc-speaker-chip { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
.cc-speaker-row input, .cc-speaker-chip input, .cc-speaker-list + details textarea {
|
|
font: inherit; padding: 8px 10px; border: 1px solid var(--cc-border); border-radius: var(--cc-radius-sm);
|
|
background: var(--cc-panel-2); color: var(--cc-text); width: 100%; margin: 0; box-sizing: border-box;
|
|
}
|
|
.cc-speaker-row input:focus, .cc-speaker-chip input:focus { outline: 2px solid var(--cc-accent); outline-offset: 1px; }
|
|
.cc-speaker-list + details textarea { min-height: 0; font-size: 1rem; margin: 8px 0; }
|