Skip to main content

livestream

Warehouse audio pipeline for live capture, transcription, and narration from meeting room mics via Tailscale. Triggers: livestream, warehouse audio, transcription pipeline, meeting capture, whisper.

Quellinformationen

Repository
plurigrid/asi
Letzte Quellaktivität
10. Juni 2026 um 11:55
Erkannte Sprache von SKILL.md
Englisch
Sterne
67
Forks
12

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
livestream
description
Warehouse audio pipeline for live capture, transcription, and narration from meeting room mics via Tailscale. Triggers: livestream, warehouse audio, transcription pipeline, meeting capture, whisper.
# Livestream Skill: Warehouse Audio Pipeline Live audio capture, transcription, and narration from the meeting room via Tailscale network. ## Architecture ``` conversation-logger (10.1.10.107) Local Mac 3x EMEET OfficeCore M0 Plus USB mics (fallback: audio-capture-org.py) Whisper large-v3-turbo, 6-speaker mlx-whisper-small, no diarization PostgreSQL → Flask :5000 audio-capture.org → DuckDB │ │ ▼ ▼ /api/transcripts?limit=N live_history_pipeline.sql │ └──── sshpass via gx10-acee ──────────────┐ ▼ Say MCP (Samantha Enhanced) ``` ## Access Path ### Step 1: SSH to gx10-acee (jump host) ```bash sshpass -p 'aaaaaa' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no a@100.67.53.87 ``` - Host: gx10-acee, Tailscale IP: 100.67.53.87 - User: `a`, Password: `aaaaaa` - NVIDIA HDA audio card, WiFi on `wlP9s9` ### Step 2: SSH to conversation-logger ```bash sshpass -p 'aaaaaa' ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no alu@10.1.10.107 ``` - Host: conversation-logger, LAN IP: 10.1.10.107 - User: `alu`, Password: `aaaaaa` - 3x EMEET mics on ALSA cards 1, 2, 3 ### Step 3: Query the API ```bash curl -s 'http://10.1.10.107:5000/api/transcripts?limit=10' ``` ### One-liner (from local Mac) ```bash sshpass -p 'aaaaaa' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no a@100.67.53.87 \ "curl -s 'http://10.1.10.107:5000/api/transcripts?limit=10'" ``` ### One-liner (execute command on logger) ```bash sshpass -p 'aaaaaa' ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no a@100.67.53.87 \ 'sshpass -p "aaaaaa" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no alu@10.1.10.107 "COMMAND"' ``` ## API Endpoints | Endpoint | Method | Description | |----------|--------|-------------| | `/api/transcripts?limit=N` | GET | Recent transcripts (JSON: id, speaker_id, transcript, started_at, ended_at, zone_id, confidence, duration_sec) | | `/transcripts` | GET | Web UI transcript browser | | `/conversations` | GET | Conversation groupings | | `/digests` | GET | Digest summaries | | `/speakers` | GET | Speaker profiles | ## Infrastructure on conversation-logger ### Systemd Services - `warehouse-capture-mic1.service` — Mic 1 capture (device 4, Whisper large-v3-turbo) - `warehouse-capture-mic2.service` — Mic 2 capture (device 5) - `warehouse-capture-mic3.service` — Mic 3 capture (device 6) - `warehouse-autogain.service` — Auto-gain controller - `warehouse-gui.service` — Flask web dashboard (:5000) - `postgresql@16-main.service` — PostgreSQL 16 ### Key Paths - `/opt/warehouse-logging/scripts/capture_node.py` — Main capture script - `/opt/warehouse-logging/scripts/auto_gain.py` — Gain controller - `/opt/warehouse-logging/app.py` — Flask dashboard - `/opt/warehouse-logging/venv/` — Python virtualenv ### Hardware - 3x EMEET OfficeCore M0 Plus (USB, Bus 001 Devices 3/5/9) - ALSA cards: 1 (Plus), 2 (Plus_1), 3 (Plus_2) - NVIDIA HDA on card 0 (not used for capture) ### Network - WiFi only (`wlP9s9`): SSID `TP-Link_A7B3`, 2.4GHz Ch2, -47dBm, 94% - All ethernet ports DOWN (NO-CARRIER) — single point of failure - Consider connecting ethernet for reliability ## Live Narration Script Save to `/tmp/live-warehouse-stream.sh`: ```bash #!/bin/bash # Speaks ALL new transcripts, batched by speaker, no cutoffs export PATH="/Users/alice/v/.flox/run/aarch64-darwin.v.dev/bin:$PATH" ACEE="100.67.53.87" LOGGER="10.1.10.107" LAST_ID="" POLL_INTERVAL=5 LIMIT=50 voice_for_speaker() { case "$1" in SPEAKER_00|alu) echo "Ava (Premium)" ;; SPEAKER_01) echo "Evan (Enhanced)" ;; SPEAKER_02) echo "Allison (Enhanced)" ;; SPEAKER_03) echo "Nathan (Enhanced)" ;; SPEAKER_04) echo "Noelle (Enhanced)" ;; SPEAKER_05) echo "Nicky (Enhanced)" ;; silly-alu) echo "Samantha (Enhanced)" ;; *) echo "Ava (Premium)" ;; esac } while true; do RESULT=$(sshpass -p 'aaaaaa' ssh -o ConnectTimeout=8 \ -o StrictHostKeyChecking=no -o BatchMode=no a@$ACEE \ "curl -s 'http://$LOGGER:5000/api/transcripts?limit=$LIMIT'" 2>/dev/null) [ $? -ne 0 ] || [ -z "$RESULT" ] && { sleep $POLL_INTERVAL; continue; } # Parse & reverse to chronological order PARSED=$(echo "$RESULT" | python3 -c " import json,sys try: d=json.load(sys.stdin) lines = [] for t in d['transcripts']: lines.append(f\"{t['id']}|{t['speaker_id']}|{t['transcript']}\") for line in reversed(lines): print(line) except: pass " 2>/dev/null) [ -z "$PARSED" ] && { sleep $POLL_INTERVAL; continue; } # First run: initialize without speaking history if [ -z "$LAST_ID" ]; then LAST_ID=$(echo "$PARSED" | tail -1 | cut -d'|' -f1) sleep $POLL_INTERVAL; continue fi # Collect all new transcripts, batch consecutive same-speaker FOUND_LAST=0; CURRENT_SPEAKER=""; CURRENT_TEXT=""; NEW_COUNT=0 while IFS= read -r line; do ID=$(echo "$line" | cut -d'|' -f1) SPEAKER=$(echo "$line" | cut -d'|' -f2) TEXT=$(echo "$line" | cut -d'|' -f3) if [ "$FOUND_LAST" -eq 0 ]; then [ "$ID" = "$LAST_ID" ] && FOUND_LAST=1; continue fi NEW_COUNT=$((NEW_COUNT + 1)); LAST_ID="$ID" TRIMMED=$(echo "$TEXT" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [ -z "$TRIMMED" ] && continue if [ "$SPEAKER" = "$CURRENT_SPEAKER" ]; then CURRENT_TEXT="$CURRENT_TEXT $TRIMMED" else if [ -n "$CURRENT_TEXT" ] && [ -n "$CURRENT_SPEAKER" ]; then VOICE=$(voice_for_speaker "$CURRENT_SPEAKER") echo "[$(date +%H:%M:%S)] $CURRENT_SPEAKER: $CURRENT_TEXT" say -v "$VOICE" -r 210 "$CURRENT_TEXT" 2>/dev/null fi CURRENT_SPEAKER="$SPEAKER"; CURRENT_TEXT="$TRIMMED" fi done <<< "$PARSED" # Speak last batch if [ -n "$CURRENT_TEXT" ] && [ "$NEW_COUNT" -gt 0 ]; then VOICE=$(voice_for_speaker "$CURRENT_SPEAKER") echo "[$(date +%H:%M:%S)] $CURRENT_SPEAKER: $CURRENT_TEXT" say -v "$VOICE" -r 210 "$CURRENT_TEXT" 2>/dev/null fi sleep $POLL_INTERVAL done ``` ### Key design choices - **`limit=50`**: Catches all transcripts between polls (Whisper produces ~1 fragment/second) - **Chronological reversal**: API returns newest-first; we reverse for natural speech order - **Speaker batching**: Consecutive same-speaker fragments concatenated into one `say` call - **No "SPEAKER says:" prefix**: Voice identity conveys the speaker; text spoken naturally - **First-poll skip**: Initializes at current position without blasting history ## Say MCP Voice Selection Two MCP servers available for TTS: | Server | Tool | Voice Param | Rate Param | |--------|------|-------------|------------| | `say` | `mcp__say__speak` | Name string (e.g. `"Ava (Premium)"`) | WPM (1-500, default 175) | | `macos-speech-sdk` | `mcp__macos-speech-sdk__speak` | Name or identifier (e.g. `"com.apple.voice.premium.en-US.Ava"`) | 0.0-1.0 mapped to 80-300 WPM, or direct WPM if >1 | ### High-Quality en-US Voices | Voice | Quality | Identifier | Gender | Trit | |-------|---------|------------|--------|------| | Ava (Premium) | premium | `com.apple.voice.premium.en-US.Ava` | F | +1 | | Ava (Enhanced) | enhanced | `com.apple.voice.enhanced.en-US.Ava` | F | +1 | | Samantha (Enhanced) | enhanced | `com.apple.voice.enhanced.en-US.Samantha` | F | 0 | | Allison (Enhanced) | enhanced | `com.apple.voice.enhanced.en-US.Allison` | F | -1 | | Evan (Enhanced) | enhanced | `com.apple.voice.enhanced.en-US.Evan` | M | +1 | | Nathan (Enhanced) | enhanced | `com.apple.voice.enhanced.en-US.Nathan` | M | 0 | | Nicky (Enhanced) | enhanced | `com.apple.ttsbundle.siri_Nicky_en-US_premium` | F | -1 | | Noelle (Enhanced) | enhanced | `com.apple.voice.enhanced.en-US.Noelle` | F | 0 | ### Per-Speaker Voice Mapping - SPEAKER_00/alu → Ava (Premium) — primary speaker, highest quality - SPEAKER_01 → Evan (Enhanced) — male voice for contrast - SPEAKER_02 → Allison (Enhanced) - SPEAKER_03 → Nathan (Enhanced) - SPEAKER_04 → Noelle (Enhanced) - SPEAKER_05 → Nicky (Enhanced) ### MCP vs CLI Usage - **Background script** (`/tmp/live-warehouse-stream.sh`): Uses CLI `say -v "Voice Name"` — works headless - **In-session narration**: Use `mcp__macos-speech-sdk__speak` with voice identifier for full control - `mcp__say__speak` has `background: true` param for non-blocking speech ## Local Fallback (SDF Ch8 Degeneracy) When remote pipeline is unreachable, use local mic capture: ```bash /Users/alice/v/.venv-mlx-lm/bin/python /Users/alice/v/scripts/audio-capture-org.py ``` - Captures MacBook Pro Microphone via FFmpeg avfoundation `:1` - Transcribes with mlx-whisper-small (16kHz, 8s chunks) - Appends to `/Users/alice/v/audio-capture.org` ## DuckDB Integration ### Ingest history for audio digest ```bash duckdb -c ".read /Users/alice/v/live_history_pipeline.sql" ``` - Merges claude/preclaude/codex history - Generates TTS-ready `narration_line` fields - `audio_digest` view: top 10 sessions formatted for voice ### Audio ACSet database - `/Users/alice/v/audio_acset.duckdb` — Structured audio metadata - Tables: AudioFile, Transcript, Segment, Speaker, Topic, ACSetSchema ## SDF Analysis Per Software Design for Flexibility (Hanson & Sussman): - **Ch1 Combinators**: Pipeline = compose(ssh_tunnel, api_poll, tts_narrate) - **Ch7 Propagators**: Transcripts flow: mic → whisper → postgres → API → say (bidirectional: can query history backwards) - **Ch8 Degeneracy**: Remote warehouse (primary) vs local mic (fallback) — same generic interface, different implementations - **Ch9 Generic Dispatch**: `narrate(source)` dispatches on source type: warehouse API vs local org file ## Dependency Structure ``` [USB Mics] ──USB──→ [conversation-logger] │ [ALSA/PulseAudio] │ [capture_node.py × 3] │ [Whisper large-v3-turbo] │ [PostgreSQL 16] │ [Flask :5000] │ [WiFi: TP-Link_A7B3] ← SINGLE POINT OF FAILURE │ [LAN: 10.1.10.107] │ [gx10-acee: 100.67.53.87 via Tailscale] │ [Local Mac: sshpass + curl] │ [Say MCP / say command] ``` **Risk**: WiFi is the only network path. All ethernet ports show NO-CARRIER. **Mitigation**: USB mics and local capture/transcription continue even if WiFi drops — data accumulates locally and can be retrieved when connectivity returns.
Auf GitHub ansehen