Script meetings/bin/meeting-start
Starts a new live transcription session.
./bin/meeting-start
Usage
-
German:meeting-start --de -
English:Β
meeting-start --en -
Auto:meeting-start --auto
--de and --auto do NOT work at this stage of the project. Only use --en.
meeting-start --en
Script
#!/usr/bin/env bash
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
source "$HERE/../lib/paths.sh"
source "$HERE/../lib/whisper.sh"
# ------------------------------------------------------------
# Argument parsing
# ------------------------------------------------------------
LANG_MODE="en"
NO_AUDIO=0
NO_TRANSCRIPT=0
while [[ $# -gt 0 ]]; do
case "$1" in
--de) LANG_MODE="de"; shift ;;
--en) LANG_MODE="en"; shift ;;
--auto) LANG_MODE="auto"; shift ;;
--no-audio) NO_AUDIO=1; shift ;;
--no-transcript) NO_TRANSCRIPT=1; shift ;;
*)
echo "Usage: meeting-start [--de|--en|--auto] [--no-audio] [--no-transcript]"
exit 2
;;
esac
done
if [[ "$NO_AUDIO" -eq 1 && "$NO_TRANSCRIPT" -eq 1 ]]; then
echo "β --no-audio and --no-transcript together: nothing to do." >&2
exit 2
fi
# ------------------------------------------------------------
# Select model + language options for whisper-stream
# ------------------------------------------------------------
case "$LANG_MODE" in
en)
MODEL="$MODEL_EN"
LANG_OPTS=(-l en)
;;
de)
MODEL="$MODEL_MULTI"
LANG_OPTS=(-l de)
;;
auto)
MODEL="$MODEL_MULTI"
LANG_OPTS=()
;;
esac
# ------------------------------------------------------------
# Audio setup: persistent sink + virtual microphone
# ------------------------------------------------------------
DISCORD_SINK="discord_sink"
DISCORD_MONITOR="${DISCORD_SINK}.monitor"
VIRTUAL_MIC="whisper_mic"
echo "π Session: $SESSION"
echo "π£οΈ Language mode: $LANG_MODE"
echo "ποΈ Audio recording: $([ "$NO_AUDIO" -eq 1 ] && echo 'off (--no-audio)' || echo 'on')"
echo "π Live transcript: $([ "$NO_TRANSCRIPT" -eq 1 ] && echo 'off (--no-transcript)' || echo 'on')"
echo "π§ Setting up audio routingβ¦"
echo " Sink: $DISCORD_SINK"
echo " Source: $VIRTUAL_MIC (master: $DISCORD_MONITOR)"
echo "----"
# 1) Ensure discord_sink exists
if ! pactl list short sinks | awk '{print $2}' | grep -qx "$DISCORD_SINK"; then
pactl load-module module-null-sink \
sink_name="$DISCORD_SINK" \
sink_properties=device.description=DiscordSink >/dev/null
fi
# 2) Try to move an active sink-input (Discord) to discord_sink
# This may be transient in silent channels β poll briefly.
moved="no"
for _ in {1..40}; do
SID="$(pactl list short sink-inputs 2>/dev/null | awk 'NF{print $1}' | head -n1 || true)"
if [[ -n "${SID:-}" ]]; then
if pactl move-sink-input "$SID" "$DISCORD_SINK" 2>/dev/null; then
moved="yes"
break
fi
fi
sleep 0.25
done
if [[ "$moved" != "yes" ]]; then
echo "β οΈ Could not move a sink-input to $DISCORD_SINK."
echo " Make sure Legcord is connected to a voice channel, then re-run meeting-start."
fi
# 3) Remove existing whisper_mic remap-sources (idempotent)
for mid in $(pactl list short modules | awk '$0 ~ /module-remap-source/ && $0 ~ /source_name=whisper_mic/ {print $1}'); do
pactl unload-module "$mid" >/dev/null 2>&1 || true
done
# 4) Create virtual microphone (mono; attempt 16 kHz)
REMAPPED_MID="$(pactl load-module module-remap-source \
master="$DISCORD_MONITOR" \
source_name="$VIRTUAL_MIC" \
channels=1 \
rate=16000 \
master_channel_map=front-left \
channel_map=mono \
source_properties=device.description=WhisperMic16k)"
echo "β
Audio ready (remap module id: $REMAPPED_MID)"
echo "----"
# ------------------------------------------------------------
# Start recording (ffmpeg) and/or transcription (whisper-stream)
# ------------------------------------------------------------
FFMPEG_PID=""
WHISPER_PID=""
# --- ffmpeg: record to WAV ---
if [[ "$NO_AUDIO" -eq 0 ]]; then
if [[ -e "$AUDIO" ]]; then
echo "β Refusing to overwrite existing file: $AUDIO" >&2
echo " (Session dir collision or leftover file)" >&2
exit 1
fi
ffmpeg -nostdin -y -hide_banner -loglevel error \
-f pulse -i "$VIRTUAL_MIC" \
-ac 1 -ar 16000 \
"$AUDIO" &
FFMPEG_PID=$!
echo "ποΈ Recording to: $AUDIO"
fi
# --- whisper-stream: live transcription ---
# whisper-stream reads directly from the capture device (-c 1 in STREAM_OPTS).
# Do NOT pass -f: for whisper-stream, -f is a text output file, not an audio input.
if [[ "$NO_TRANSCRIPT" -eq 0 ]]; then
"$WHISPER" \
-m "$MODEL" \
"${LANG_OPTS[@]}" \
"${STREAM_OPTS[@]}" \
"${THREAD_OPTS[@]}" \
| tee -a "$TRANSCRIPT" &
WHISPER_PID=$!
echo "π Live transcription to: $TRANSCRIPT"
fi
echo "β‘οΈ Follow the transcript with: meeting-follow"
echo "βΉ Stop with: meeting-stop"
echo "----"
# Announce active session
echo "$SESSION" > "$CURRENT"
# ------------------------------------------------------------
# Metadata
# ------------------------------------------------------------
cat > "$META" <<EOF
SESSION=$SESSION
AUDIO=${AUDIO:-}
TRANSCRIPT=${TRANSCRIPT:-}
LANG_MODE=$LANG_MODE
MODEL=$MODEL
NO_AUDIO=$NO_AUDIO
NO_TRANSCRIPT=$NO_TRANSCRIPT
DISCORD_SINK=$DISCORD_SINK
DISCORD_MONITOR=$DISCORD_MONITOR
VIRTUAL_MIC=$VIRTUAL_MIC
REMAPPED_MID=$REMAPPED_MID
FFMPEG_PID=${FFMPEG_PID:-}
WHISPER_PID=${WHISPER_PID:-}
EOF
[[ -n "${FFMPEG_PID:-}" ]] && echo "$FFMPEG_PID" > "$SESSION/ffmpeg.pid"
[[ -n "${WHISPER_PID:-}" ]] && echo "$WHISPER_PID" > "$SESSION/whisper.pid"
echo "β
meeting-start finished setup successfully."
No comments to display
No comments to display