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.
44 lines
2.2 KiB
Python
44 lines
2.2 KiB
Python
"""Copy the versioned Caption Local browser bundle to a captionninja checkout."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FILES = ('theme.css', 'favicon.svg', 'caption-status.js', 'caption-room-menu.js', 'capture.css', 'app.js', 'audio-buffer.js', 'pcm-worklet.js', 'ws-publisher.js',
|
|
'relay-config.js', 'private-relay-client.js', 'local-connection.js', 'local-page.js', 'speaker-names.js')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('checkout', type=Path)
|
|
parser.add_argument('--check', action='store_true')
|
|
args = parser.parse_args()
|
|
if not (args.checkout / 'overlay.html').is_file():
|
|
parser.error('Expected a captionninja checkout with overlay.html')
|
|
outputs = {args.checkout / 'capture-local.html': (ROOT / 'static/capture-local.html').read_bytes().replace(
|
|
b'/static/', b'./caption-local/')}
|
|
outputs[args.checkout / 'caption-local/LICENSE'] = (ROOT / 'LICENSE').read_bytes()
|
|
outputs[args.checkout / 'relay-config.js'] = (ROOT / 'static/relay-config.js').read_bytes()
|
|
outputs[args.checkout / 'private-relay-client.js'] = (ROOT / 'static/private-relay-client.js').read_bytes()
|
|
outputs[args.checkout / 'ws-publisher.js'] = (ROOT / 'static/ws-publisher.js').read_bytes()
|
|
for name in FILES:
|
|
outputs[args.checkout / 'caption-local' / name] = (ROOT / 'static' / name).read_bytes()
|
|
manifest = {path.relative_to(args.checkout).as_posix(): hashlib.sha256(data).hexdigest()
|
|
for path, data in outputs.items()}
|
|
outputs[args.checkout / 'caption-local/manifest.json'] = (json.dumps({
|
|
'source': 'https://github.com/steveseguin/caption-local', 'bundle_schema': 1,
|
|
'sha256': manifest}, indent=2) + '\n').encode()
|
|
for path, data in outputs.items():
|
|
if args.check:
|
|
if not path.is_file() or path.read_bytes() != data:
|
|
raise SystemExit(f'Bundle differs: {path}')
|
|
else:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_bytes(data)
|
|
print('Bundle verified' if args.check else 'Bundle synchronized')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|