A small helpdesk system: an office Pixel running a patched GrapheneOS Dialer answers technician calls, records both call legs as separate channels, and a Ruby backend transcribes them through Whisper and files an AI summary against the caller. Squashed to a single commit for sharing. No credentials are included; secrets live outside the repo in /etc/helpdesk/env on the server or a gitignored .claude/env.local locally. See .claude/env.local.example for the shape. Start at README.md, then docs/architecture.md.
97 lines
2.9 KiB
Bash
Executable file
97 lines
2.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# smoke_test.sh — End-to-end pipeline smoke test.
|
|
#
|
|
# Synthesizes a 2-speaker Czech dialogue with espeak-ng (no real audio
|
|
# needed), runs it through transcribe.py, and asserts the output contains
|
|
# both speaker tags. The transcription itself will be robotic-TTS nonsense;
|
|
# this test only verifies that the ffmpeg → ASR → align → diarize → assign
|
|
# → render pipeline executes end-to-end without errors.
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
if ! command -v espeak-ng >/dev/null; then
|
|
echo "ERROR: espeak-ng not installed."
|
|
echo " sudo apt install espeak-ng (Debian/Ubuntu)"
|
|
echo " brew install espeak-ng (macOS)"
|
|
exit 2
|
|
fi
|
|
|
|
if ! command -v ffmpeg >/dev/null; then
|
|
echo "ERROR: ffmpeg not installed."
|
|
exit 2
|
|
fi
|
|
|
|
if [ -z "${HF_TOKEN:-}" ]; then
|
|
echo "ERROR: HF_TOKEN not set in the environment."
|
|
echo " Create a token at https://huggingface.co/settings/tokens"
|
|
echo " Accept terms on:"
|
|
echo " https://huggingface.co/pyannote/speaker-diarization-community-1"
|
|
echo " https://huggingface.co/pyannote/segmentation-3.0"
|
|
echo " Then: export HF_TOKEN=hf_..."
|
|
exit 2
|
|
fi
|
|
|
|
TMPDIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMPDIR"' EXIT
|
|
|
|
# Two Czech utterances at distinct pitches to fake two speakers.
|
|
espeak-ng -v cs -p 25 -s 150 \
|
|
"Dobrý den, jak se dnes máte? Doufám, že dobře." \
|
|
-w "$TMPDIR/a.wav"
|
|
espeak-ng -v cs -p 75 -s 145 \
|
|
"Děkuji, mám se výborně. A vy? Co je dnes nového?" \
|
|
-w "$TMPDIR/b.wav"
|
|
espeak-ng -v cs -p 25 -s 150 \
|
|
"Mám hodně práce, ale nestěžuji si. Pracujeme na zajímavém projektu." \
|
|
-w "$TMPDIR/c.wav"
|
|
espeak-ng -v cs -p 75 -s 145 \
|
|
"To je skvělé. Přeji vám hodně úspěchů." \
|
|
-w "$TMPDIR/d.wav"
|
|
|
|
# 0.4 s silence between turns.
|
|
ffmpeg -hide_banner -loglevel error -y \
|
|
-f lavfi -i anullsrc=r=22050:cl=mono -t 0.4 \
|
|
-ac 1 "$TMPDIR/sil.wav"
|
|
|
|
# Concat list.
|
|
{
|
|
echo "file '$TMPDIR/a.wav'"
|
|
echo "file '$TMPDIR/sil.wav'"
|
|
echo "file '$TMPDIR/b.wav'"
|
|
echo "file '$TMPDIR/sil.wav'"
|
|
echo "file '$TMPDIR/c.wav'"
|
|
echo "file '$TMPDIR/sil.wav'"
|
|
echo "file '$TMPDIR/d.wav'"
|
|
} > "$TMPDIR/concat.txt"
|
|
|
|
ffmpeg -hide_banner -loglevel error -y \
|
|
-f concat -safe 0 -i "$TMPDIR/concat.txt" \
|
|
-ar 16000 -ac 1 "$TMPDIR/smoke.wav"
|
|
|
|
OUT="$TMPDIR/smoke.txt"
|
|
echo ">>> running transcribe.py on synthesized 4-utterance dialogue…"
|
|
python transcribe.py "$TMPDIR/smoke.wav" -o "$OUT" \
|
|
--min-speakers 2 --max-speakers 2
|
|
|
|
if [ ! -s "$OUT" ]; then
|
|
echo "FAIL: $OUT is empty."
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
echo "=== output ==="
|
|
cat "$OUT"
|
|
echo "=============="
|
|
echo
|
|
|
|
if grep -q "\[Speaker 1\]:" "$OUT" && grep -q "\[Speaker 2\]:" "$OUT"; then
|
|
echo "PASS: both [Speaker 1] and [Speaker 2] tags present."
|
|
exit 0
|
|
else
|
|
echo "FAIL: output missing one or both speaker tags."
|
|
echo " Pipeline ran end-to-end but diarization may have collapsed"
|
|
echo " the two TTS voices into one speaker (espeak-ng voices are"
|
|
echo " acoustically similar). Try with a real recording."
|
|
exit 1
|
|
fi
|