feat: komplette Codebase vom Board ins Repo gespiegelt
app/ = /opt/callrec (Node-Backend, Vite-Frontend, Python-Inferenz, Audio-Skripte, rkllm_server.cpp), system/ = systemd-Units, WirePlumber/PipeWire-Drop-ins, sudoers. Modelle, Binaries, Aufnahmen und node_modules bleiben draussen (.gitignore). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
#!/opt/aime-venv/bin/python
|
||||
"""CallRec CPU transcriber daemon (SHORT clips).
|
||||
|
||||
Loads faster-whisper large-v3-turbo (CT2, CPU int8) ONCE and keeps it warm for
|
||||
the whole callrec lifetime. Used for SHORT recordings where per-call model load
|
||||
(~28-40s) dwarfs the transcription itself. CPU-ONLY — it never touches the NPU,
|
||||
so it coexists with a resident rkllm_server (Qwen-3B): short clips need NO
|
||||
NPU-encoder swap AND no model reload → a 10s clip drops from ~120s to ~20s.
|
||||
|
||||
(The NPU turbo-encoder path stays in transcribe_cli.py for LONG clips, where the
|
||||
2.4x encoder speedup beats the swap overhead.)
|
||||
|
||||
Protocol (line based, like the NPU daemon / spk_embed / rkllm_server):
|
||||
stdin : one absolute wav path per line
|
||||
stdout: "READY" once, then per request "OK <base64(json)>" or "ERR <base64(msg)>"
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
import time
|
||||
import os
|
||||
|
||||
sys.path.insert(0, "/opt/callrec")
|
||||
|
||||
MODEL = "large-v3-turbo"
|
||||
ROOT = "/opt/callrec/models/whisper"
|
||||
BATCH = int(os.environ.get("WHISPER_BATCH", "8"))
|
||||
|
||||
from faster_whisper import WhisperModel, BatchedInferencePipeline
|
||||
|
||||
# CT2 model loaded ONCE (CPU), stays warm. No NPU encoder override -> ct2 runs
|
||||
# its own encoder on the CPU, so nothing here competes with rkllm on the NPU.
|
||||
_model = WhisperModel(MODEL, device="cpu", compute_type="int8",
|
||||
download_root=ROOT, cpu_threads=4)
|
||||
_engine = BatchedInferencePipeline(model=_model) if BATCH > 1 else _model
|
||||
|
||||
|
||||
def transcribe(wav):
|
||||
t0 = time.time()
|
||||
if BATCH > 1:
|
||||
segments, info = _engine.transcribe(
|
||||
wav, language="de", beam_size=1, batch_size=BATCH, word_timestamps=True)
|
||||
else:
|
||||
segments, info = _engine.transcribe(
|
||||
wav, language="de", beam_size=1, word_timestamps=True, vad_filter=True)
|
||||
words = []
|
||||
texts = []
|
||||
for seg in segments:
|
||||
texts.append(seg.text.strip())
|
||||
for w in (seg.words or []):
|
||||
words.append({
|
||||
"word": w.word.strip(),
|
||||
"t_start_ms": int(round((w.start or 0) * 1000)),
|
||||
"t_end_ms": int(round((w.end or 0) * 1000)),
|
||||
"probability": round(w.probability, 3),
|
||||
})
|
||||
dur = float(info.duration or 0)
|
||||
trans_s = time.time() - t0
|
||||
return {
|
||||
"text": " ".join(texts).strip(),
|
||||
"words": words,
|
||||
"duration": round(dur, 1),
|
||||
"load_s": 0.0,
|
||||
"transcribe_s": round(trans_s, 1),
|
||||
"rtf": round(trans_s / max(dur, 0.1), 3),
|
||||
"compute": "cpu-daemon",
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
print("READY", flush=True)
|
||||
for line in sys.stdin:
|
||||
wav = line.strip()
|
||||
if not wav:
|
||||
continue
|
||||
try:
|
||||
out = transcribe(wav)
|
||||
payload = base64.b64encode(
|
||||
json.dumps(out, ensure_ascii=False).encode("utf-8")).decode("ascii")
|
||||
print("OK " + payload, flush=True)
|
||||
except Exception as e: # noqa: BLE001
|
||||
msg = base64.b64encode(str(e).encode("utf-8")).decode("ascii")
|
||||
print("ERR " + msg, flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user