adding the first version of the presentation and the initial attempt at the .meetings script
This commit is contained in:
Executable
+659
@@ -0,0 +1,659 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# meetings — audio → transcript → summary + action items, all local
|
||||
#
|
||||
# Transcribes audio using whisper.cpp or parakeet.cpp (GGUF/GGML models),
|
||||
# then uses Ollama to summarize and extract action items.
|
||||
#
|
||||
# Usage:
|
||||
# ./meetings <audio_file> # full pipeline
|
||||
# ./meetings setup # install deps + download model
|
||||
# ./meetings doctor # check dependencies
|
||||
# ./meetings config # show current config
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# ─── colours ────────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'; GRN='\033[0;32m'; YEL='\033[1;33m'
|
||||
BLU='\033[0;34m'; CYN='\033[0;36m'; RST='\033[0m'
|
||||
BOLD='\033[1m'
|
||||
|
||||
# ─── paths ──────────────────────────────────────────────────────────
|
||||
MEETINGS_DIR="${MEETINGS_DIR:-$HOME/.meetings}"
|
||||
CONFIG_FILE="$MEETINGS_DIR/config"
|
||||
MODELS_DIR="$MEETINGS_DIR/models"
|
||||
|
||||
# ─── helpers ───────────────────────────────────────────────────────
|
||||
die() { printf "${RED}error: %s${RST}\n" "$*" >&2; exit 1; }
|
||||
info() { printf "${BLU}▸ %s${RST}\n" "$*" >&2; }
|
||||
ok() { printf "${GRN}✓ %s${RST}\n" "$*" >&2; }
|
||||
warn() { printf "${YEL}⚠ %s${RST}\n" "$*" >&2; }
|
||||
step() { printf "\n${BOLD}${CYN}── %s ──${RST}\n" "$*" >&2; }
|
||||
|
||||
banner() {
|
||||
cat <<'BAN'
|
||||
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 🎤 M E E T I N G S │
|
||||
│ audio → transcript → summary + actions │
|
||||
│ whisper.cpp · parakeet.cpp · ollama │
|
||||
└─────────────────────────────────────────────┘
|
||||
|
||||
BAN
|
||||
}
|
||||
|
||||
# ─── load config: env vars > config file > defaults ────────────────
|
||||
load_config() {
|
||||
# defaults
|
||||
STT_ENGINE=""
|
||||
STT_MODEL=""
|
||||
OLLAMA_MODEL="llama3.1:8b"
|
||||
OLLAMA_HOST="http://localhost:11434"
|
||||
THREADS="4"
|
||||
LANGUAGE="en"
|
||||
OUTPUT_DIR="."
|
||||
# config file overrides defaults
|
||||
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE"
|
||||
# env vars override everything (|| true prevents set -e exit on empty vars)
|
||||
[[ -n "${MEETINGS_STT:-}" ]] && STT_ENGINE="$MEETINGS_STT" || true
|
||||
[[ -n "${MEETINGS_STT_MODEL:-}" ]] && STT_MODEL="$MEETINGS_STT_MODEL" || true
|
||||
[[ -n "${MEETINGS_LLM:-}" ]] && OLLAMA_MODEL="$MEETINGS_LLM" || true
|
||||
[[ -n "${MEETINGS_THREADS:-}" ]] && THREADS="$MEETINGS_THREADS" || true
|
||||
[[ -n "${MEETINGS_LANG:-}" ]] && LANGUAGE="$MEETINGS_LANG" || true
|
||||
[[ -n "${MEETINGS_OUTPUT:-}" ]] && OUTPUT_DIR="$MEETINGS_OUTPUT" || true
|
||||
}
|
||||
|
||||
# ─── detect STT engine ──────────────────────────────────────────────
|
||||
detect_stt() {
|
||||
if command -v parakeet-cli &>/dev/null; then
|
||||
echo "parakeet"
|
||||
elif command -v whisper-cli &>/dev/null; then
|
||||
echo "whisper"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
get_stt_binary() {
|
||||
local engine="${1:-$STT_ENGINE}"
|
||||
case "$engine" in
|
||||
whisper) command -v whisper-cli 2>/dev/null || echo "" ;;
|
||||
parakeet) command -v parakeet-cli 2>/dev/null || echo "" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
find_stt_model() {
|
||||
local engine="${1:-whisper}"
|
||||
if [[ -n "${STT_MODEL:-}" && -f "${STT_MODEL}" ]]; then
|
||||
echo "$STT_MODEL"; return 0
|
||||
fi
|
||||
case "$engine" in
|
||||
whisper)
|
||||
for f in "$MODELS_DIR"/ggml-*.bin "$MODELS_DIR"/ggml-*.gguf; do
|
||||
[[ -f "$f" ]] && echo "$f" && return 0
|
||||
done
|
||||
;;
|
||||
parakeet)
|
||||
for f in "$MODELS_DIR"/*parakeet*.gguf "$MODELS_DIR"/*tdt*.gguf; do
|
||||
[[ -f "$f" ]] && echo "$f" && return 0
|
||||
done
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── doctor ─────────────────────────────────────────────────────────
|
||||
cmd_doctor() {
|
||||
banner >&2
|
||||
local ok_count=0 total=0
|
||||
|
||||
total=$((total+1))
|
||||
if command -v ffmpeg &>/dev/null; then
|
||||
ok "ffmpeg: $(command -v ffmpeg)"; ok_count=$((ok_count+1))
|
||||
else
|
||||
warn "ffmpeg: not found"
|
||||
fi
|
||||
|
||||
total=$((total+1))
|
||||
if command -v ollama &>/dev/null; then
|
||||
ok "ollama: $(command -v ollama)"; ok_count=$((ok_count+1))
|
||||
if curl -sf "${OLLAMA_HOST:-http://localhost:11434}/api/tags" &>/dev/null; then
|
||||
ok " server: running at ${OLLAMA_HOST:-http://localhost:11434}"
|
||||
else
|
||||
warn " server: not responding (run: ollama serve)"
|
||||
fi
|
||||
else
|
||||
warn "ollama: not found"
|
||||
fi
|
||||
|
||||
total=$((total+1))
|
||||
local engine="${STT_ENGINE:-$(detect_stt)}"
|
||||
local bin
|
||||
bin="$(get_stt_binary "$engine")"
|
||||
if [[ -n "$bin" ]]; then
|
||||
ok "STT engine: $engine ($bin)"; ok_count=$((ok_count+1))
|
||||
else
|
||||
warn "STT engine: not found (run: ./meetings setup)"
|
||||
fi
|
||||
|
||||
total=$((total+1))
|
||||
local model
|
||||
model="$(find_stt_model "$engine")" || true
|
||||
if [[ -n "$model" ]]; then
|
||||
ok "STT model: $model"; ok_count=$((ok_count+1))
|
||||
else
|
||||
warn "STT model: not found (run: ./meetings setup)"
|
||||
fi
|
||||
|
||||
total=$((total+1))
|
||||
local ollama_model="${OLLAMA_MODEL:-llama3.1:8b}"
|
||||
local model_name
|
||||
model_name="$(echo "$ollama_model" | cut -d: -f1)"
|
||||
if ollama list 2>/dev/null | awk '{print $1}' | grep -qF "$model_name"; then
|
||||
ok "LLM model: $ollama_model (pulled)"; ok_count=$((ok_count+1))
|
||||
else
|
||||
warn "LLM model: $ollama_model (not pulled — run: ollama pull $ollama_model)"
|
||||
fi
|
||||
|
||||
echo "" >&2
|
||||
if [[ $ok_count -eq $total ]]; then
|
||||
ok "All good ($ok_count/$total)"
|
||||
else
|
||||
warn "Ready ($ok_count/$total). Run './meetings setup' to install missing pieces."
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── setup ──────────────────────────────────────────────────────────
|
||||
cmd_setup() {
|
||||
load_config
|
||||
banner >&2
|
||||
mkdir -p "$MEETINGS_DIR" "$MODELS_DIR"
|
||||
|
||||
# ── ffmpeg ──
|
||||
step "Checking ffmpeg"
|
||||
if command -v ffmpeg &>/dev/null; then
|
||||
ok "ffmpeg already installed"
|
||||
else
|
||||
info "Installing ffmpeg..."
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
brew install ffmpeg || die "Could not install ffmpeg"
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update && sudo apt-get install -y ffmpeg
|
||||
elif command -v dnf &>/dev/null; then
|
||||
sudo dnf install -y ffmpeg
|
||||
else
|
||||
die "Please install ffmpeg manually: https://ffmpeg.org/download.html"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── ollama ──
|
||||
step "Checking Ollama"
|
||||
if command -v ollama &>/dev/null; then
|
||||
ok "ollama already installed"
|
||||
else
|
||||
info "Installing Ollama..."
|
||||
curl -fsSL https://ollama.com/install.sh | sh || die "Could not install Ollama"
|
||||
fi
|
||||
|
||||
if ! curl -sf "${OLLAMA_HOST:-http://localhost:11434}/api/tags" &>/dev/null; then
|
||||
info "Starting Ollama server..."
|
||||
ollama serve &>/dev/null &
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
# ── choose STT engine ──
|
||||
step "Choosing STT engine"
|
||||
echo "" >&2
|
||||
echo " 1) whisper.cpp — battle-tested, many languages, brew install" >&2
|
||||
echo " 2) parakeet.cpp — faster, great English, smaller footprint" >&2
|
||||
echo "" >&2
|
||||
local choice
|
||||
if [[ -n "${STT_ENGINE:-}" ]]; then
|
||||
choice="$STT_ENGINE"
|
||||
info "Using pre-configured engine: $choice"
|
||||
else
|
||||
read -rp " Choose [1/2, default=1]: " choice
|
||||
case "${choice:-1}" in
|
||||
2|parakeet) choice="parakeet" ;;
|
||||
*) choice="whisper" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$choice" in
|
||||
whisper) setup_whisper ;;
|
||||
parakeet) setup_parakeet ;;
|
||||
esac
|
||||
|
||||
STT_ENGINE="$choice"
|
||||
grep -q "^STT_ENGINE=" "$CONFIG_FILE" 2>/dev/null \
|
||||
&& sed -i '' "s/^STT_ENGINE=.*/STT_ENGINE=$choice/" "$CONFIG_FILE" 2>/dev/null \
|
||||
|| echo "STT_ENGINE=$choice" >> "$CONFIG_FILE"
|
||||
|
||||
# ── pull LLM ──
|
||||
step "Pulling LLM: ${OLLAMA_MODEL:-llama3.1:8b}"
|
||||
local ollama_model="${OLLAMA_MODEL:-llama3.1:8b}"
|
||||
if ollama list 2>/dev/null | awk '{print $1}' | grep -qF "$(echo "$ollama_model" | cut -d: -f1)"; then
|
||||
ok "$ollama_model already pulled"
|
||||
else
|
||||
ollama pull "$ollama_model" || warn "Could not pull $ollama_model. Run: ollama pull $ollama_model"
|
||||
fi
|
||||
|
||||
echo "" >&2
|
||||
ok "Setup complete! Run './meetings doctor' to verify, then './meetings <audio_file>' to process."
|
||||
}
|
||||
|
||||
setup_whisper() {
|
||||
step "Installing whisper.cpp"
|
||||
if command -v whisper-cli &>/dev/null; then
|
||||
ok "whisper-cli already available at $(command -v whisper-cli)"
|
||||
else
|
||||
info "Installing via package manager..."
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
brew install whisper-cpp || die "Could not install whisper-cpp via brew"
|
||||
else
|
||||
die "Please install whisper-cli manually: https://github.com/ggml-org/whisper.cpp
|
||||
On macOS: brew install whisper-cpp
|
||||
Or build from source: git clone https://github.com/ggml-org/whisper.cpp && cd whisper.cpp && cmake -B build && cmake --build build -j"
|
||||
fi
|
||||
fi
|
||||
|
||||
step "Downloading Whisper model"
|
||||
local model_choice
|
||||
echo "" >&2
|
||||
echo " Available models (from HuggingFace ggerganov/whisper.cpp):" >&2
|
||||
echo " tiny.en — 75 MB (fastest, English only)" >&2
|
||||
echo " base.en — 142 MB (good for quick tests)" >&2
|
||||
echo " small.en — 466 MB (recommended for English) ★" >&2
|
||||
echo " medium.en — 1.5 GB (high accuracy, English only)" >&2
|
||||
echo " large-v3-turbo — 809 MB (best multilingual, fast)" >&2
|
||||
echo " large-v3 — 2.9 GB (best accuracy, any language)" >&2
|
||||
echo "" >&2
|
||||
if [[ -n "${STT_MODEL:-}" && -f "${STT_MODEL}" ]]; then
|
||||
info "Using existing model: $STT_MODEL"
|
||||
else
|
||||
read -rp " Choose model [default=small.en]: " model_choice
|
||||
model_choice="${model_choice:-small.en}"
|
||||
local model_file="$MODELS_DIR/ggml-${model_choice}.bin"
|
||||
if [[ -f "$model_file" ]]; then
|
||||
ok "Model already downloaded: $model_file"
|
||||
else
|
||||
info "Downloading ggml-${model_choice}.bin (this may take a moment)..."
|
||||
curl -L --progress-bar \
|
||||
-o "$model_file" \
|
||||
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model_choice}.bin" \
|
||||
|| die "Failed to download model"
|
||||
ok "Downloaded: $model_file"
|
||||
fi
|
||||
STT_MODEL="$model_file"
|
||||
echo "STT_MODEL=$model_file" >> "$CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_parakeet() {
|
||||
step "Installing parakeet.cpp"
|
||||
if command -v parakeet-cli &>/dev/null; then
|
||||
ok "parakeet-cli already available at $(command -v parakeet-cli)"
|
||||
else
|
||||
info "Checking for pre-built binary or Docker..."
|
||||
if command -v docker &>/dev/null; then
|
||||
ok "Docker available — parakeet.cpp will run via container"
|
||||
else
|
||||
die "parakeet-cli not found and Docker not installed. Please either:
|
||||
- Build parakeet.cpp: https://github.com/mudler/parakeet.cpp
|
||||
- Or install Docker for the container approach"
|
||||
fi
|
||||
fi
|
||||
|
||||
step "Downloading Parakeet model"
|
||||
local model_choice
|
||||
echo "" >&2
|
||||
echo " Available models (from HuggingFace mudler/parakeet-cpp-gguf):" >&2
|
||||
echo " tdt_ctc-110m-q8_0 — 178 MB (fast, good English, smallest) ★" >&2
|
||||
echo " tdt_ctc-110m-f16 — 268 MB (fast, lossless English)" >&2
|
||||
echo " tdt-0.6b-v3-f16 — 1.4 GB (multilingual, recommended)" >&2
|
||||
echo "" >&2
|
||||
if [[ -n "${STT_MODEL:-}" && -f "${STT_MODEL}" ]]; then
|
||||
info "Using existing model: $STT_MODEL"
|
||||
else
|
||||
read -rp " Choose model [default=tdt_ctc-110m-q8_0]: " model_choice
|
||||
model_choice="${model_choice:-tdt_ctc-110m-q8_0}"
|
||||
local model_file="$MODELS_DIR/${model_choice}.gguf"
|
||||
if [[ -f "$model_file" ]]; then
|
||||
ok "Model already downloaded: $model_file"
|
||||
else
|
||||
info "Downloading ${model_choice}.gguf..."
|
||||
curl -L --progress-bar \
|
||||
-o "$model_file" \
|
||||
"https://huggingface.co/mudler/parakeet-cpp-gguf/resolve/main/${model_choice}.gguf" \
|
||||
|| die "Failed to download model"
|
||||
ok "Downloaded: $model_file"
|
||||
fi
|
||||
STT_MODEL="$model_file"
|
||||
echo "STT_MODEL=$model_file" >> "$CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── config display ─────────────────────────────────────────────────
|
||||
cmd_config() {
|
||||
load_config
|
||||
banner >&2
|
||||
echo "" >&2
|
||||
if [[ -f "$CONFIG_FILE" ]]; then
|
||||
echo " Saved config ($CONFIG_FILE):" >&2
|
||||
echo "" >&2
|
||||
while IFS='=' read -r key val; do
|
||||
printf " %-16s %s\n" "$key" "$val" >&2
|
||||
done < "$CONFIG_FILE"
|
||||
else
|
||||
echo " (no saved config — run: ./meetings setup)" >&2
|
||||
fi
|
||||
echo "" >&2
|
||||
echo " Effective settings:" >&2
|
||||
local stt_display="${STT_ENGINE:-$(detect_stt)}"
|
||||
stt_display="${stt_display:-(not set)}"
|
||||
echo " STT engine: $stt_display" >&2
|
||||
echo " STT model: ${STT_MODEL:-(auto-detect)}" >&2
|
||||
echo " LLM model: $OLLAMA_MODEL" >&2
|
||||
echo " Ollama host: $OLLAMA_HOST" >&2
|
||||
echo " Threads: $THREADS" >&2
|
||||
echo " Language: $LANGUAGE" >&2
|
||||
echo " Output dir: $OUTPUT_DIR" >&2
|
||||
echo "" >&2
|
||||
}
|
||||
|
||||
# ─── convert audio ──────────────────────────────────────────────────
|
||||
convert_audio() {
|
||||
local input="$1" output="$2"
|
||||
info "Converting audio to 16kHz mono WAV..."
|
||||
ffmpeg -y -i "$input" -ar 16000 -ac 1 -c:a pcm_s16le "$output" \
|
||||
-loglevel warning 2>/dev/null \
|
||||
|| die "ffmpeg conversion failed for $input"
|
||||
ok "Audio converted: $(du -h "$output" | cut -f1)"
|
||||
}
|
||||
|
||||
# ─── transcribe with whisper ───────────────────────────────────────
|
||||
transcribe_whisper() {
|
||||
local wav_file="$1" model="$2" binary="$3"
|
||||
info "Transcribing with whisper.cpp..."
|
||||
local lang_flag=""
|
||||
[[ "$LANGUAGE" != "auto" ]] && lang_flag="-l $LANGUAGE"
|
||||
|
||||
local transcript
|
||||
transcript=$("$binary" \
|
||||
-m "$model" \
|
||||
-f "$wav_file" \
|
||||
-t "$THREADS" \
|
||||
$lang_flag \
|
||||
-nt \
|
||||
--no-prints \
|
||||
2>/dev/null) || die "Whisper transcription failed"
|
||||
|
||||
echo "$transcript"
|
||||
}
|
||||
|
||||
# ─── transcribe with parakeet ──────────────────────────────────────
|
||||
transcribe_parakeet() {
|
||||
local wav_file="$1" model="$2" binary="$3"
|
||||
info "Transcribing with parakeet.cpp..."
|
||||
|
||||
local decoder_flag=""
|
||||
if echo "$model" | grep -qi "ctc"; then
|
||||
decoder_flag="--decoder ctc"
|
||||
elif echo "$model" | grep -qi "tdt"; then
|
||||
decoder_flag="--decoder tdt"
|
||||
fi
|
||||
|
||||
if [[ -z "$binary" ]] && command -v docker &>/dev/null; then
|
||||
info "Using Docker for parakeet.cpp..."
|
||||
docker run --rm \
|
||||
-v "$model:/model.gguf:ro" \
|
||||
-v "$wav_file:/audio.wav:ro" \
|
||||
ghcr.io/mudler/parakeet.cpp-cli:latest \
|
||||
transcribe --model /model.gguf --input /audio.wav $decoder_flag 2>/dev/null \
|
||||
|| die "Docker parakeet transcription failed"
|
||||
else
|
||||
"$binary" transcribe --model "$model" --input "$wav_file" $decoder_flag 2>/dev/null \
|
||||
|| die "Parakeet transcription failed"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── call Ollama ───────────────────────────────────────────────────
|
||||
ollama_generate() {
|
||||
local prompt="$1" system="${2:-}"
|
||||
local json_payload
|
||||
|
||||
if [[ -n "$system" ]]; then
|
||||
json_payload=$(jq -n \
|
||||
--arg model "$OLLAMA_MODEL" \
|
||||
--arg system "$system" \
|
||||
--arg prompt "$prompt" \
|
||||
'{model: $model, system: $system, prompt: $prompt, stream: false}')
|
||||
else
|
||||
json_payload=$(jq -n \
|
||||
--arg model "$OLLAMA_MODEL" \
|
||||
--arg prompt "$prompt" \
|
||||
'{model: $model, prompt: $prompt, stream: false}')
|
||||
fi
|
||||
|
||||
curl -sf "$OLLAMA_HOST/api/generate" \
|
||||
-d "$json_payload" 2>/dev/null \
|
||||
| jq -r '.response // empty' \
|
||||
|| die "Ollama request failed. Is the server running? (ollama serve)"
|
||||
}
|
||||
|
||||
# ─── main pipeline ──────────────────────────────────────────────────
|
||||
cmd_process() {
|
||||
local input_file="$1"
|
||||
shift || true
|
||||
|
||||
# Load config BEFORE flag parsing, so flags can override
|
||||
load_config
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--stt) STT_ENGINE="$2"; shift 2 ;;
|
||||
--model) STT_MODEL="$2"; shift 2 ;;
|
||||
--llm) OLLAMA_MODEL="$2"; shift 2 ;;
|
||||
--lang) LANGUAGE="$2"; shift 2 ;;
|
||||
--threads) THREADS="$2"; shift 2 ;;
|
||||
--output) OUTPUT_DIR="$2"; shift 2 ;;
|
||||
*) die "Unknown option: $1. Run: ./meetings help" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$input_file" ]] && die "Usage: ./meetings <audio_file>"
|
||||
[[ ! -f "$input_file" ]] && die "File not found: $input_file"
|
||||
|
||||
STT_ENGINE="${STT_ENGINE:-$(detect_stt)}"
|
||||
[[ -z "$STT_ENGINE" ]] && die "No STT engine found. Run: ./meetings setup"
|
||||
|
||||
local binary
|
||||
binary="$(get_stt_binary "$STT_ENGINE")"
|
||||
[[ -z "$binary" ]] && die "Could not find $STT_ENGINE binary. Run: ./meetings setup"
|
||||
|
||||
local model
|
||||
if [[ -n "${STT_MODEL:-}" ]] && [[ -f "$STT_MODEL" ]]; then
|
||||
model="$STT_MODEL"
|
||||
else
|
||||
model="$(find_stt_model "$STT_ENGINE")" \
|
||||
|| die "No STT model found. Run: ./meetings setup"
|
||||
fi
|
||||
[[ ! -f "$model" ]] && die "Model file not found: $model"
|
||||
|
||||
command -v ffmpeg &>/dev/null || die "ffmpeg not found. Install it first."
|
||||
curl -sf "$OLLAMA_HOST/api/tags" &>/dev/null \
|
||||
|| die "Ollama server not responding at $OLLAMA_HOST. Run: ollama serve"
|
||||
|
||||
# ── create output directory ──
|
||||
local basename
|
||||
basename="$(date +%Y-%m-%d_%H%M)_$(basename "${input_file%.*}" | tr ' ' '_')"
|
||||
local outdir="$OUTPUT_DIR/$basename"
|
||||
mkdir -p "$outdir"
|
||||
|
||||
banner >&2
|
||||
echo " Input: $input_file" >&2
|
||||
echo " STT engine: $STT_ENGINE" >&2
|
||||
echo " STT model: $(basename "$model")" >&2
|
||||
echo " LLM model: $OLLAMA_MODEL" >&2
|
||||
echo " Language: $LANGUAGE" >&2
|
||||
echo " Output: $outdir/" >&2
|
||||
echo "" >&2
|
||||
|
||||
# ── step 1: convert ──
|
||||
step "Step 1/4 — Converting audio"
|
||||
local wav_file="$outdir/audio_16k.wav"
|
||||
convert_audio "$input_file" "$wav_file"
|
||||
|
||||
# ── step 2: transcribe ──
|
||||
step "Step 2/4 — Transcribing with $STT_ENGINE"
|
||||
local transcript
|
||||
case "$STT_ENGINE" in
|
||||
whisper) transcript="$(transcribe_whisper "$wav_file" "$model" "$binary")" ;;
|
||||
parakeet) transcript="$(transcribe_parakeet "$wav_file" "$model" "$binary")" ;;
|
||||
*) die "Unknown engine: $STT_ENGINE" ;;
|
||||
esac
|
||||
|
||||
if [[ -z "$transcript" || -z "$(echo "$transcript" | tr -d '[:space:]')" ]]; then
|
||||
die "Transcription produced empty output"
|
||||
fi
|
||||
|
||||
echo "$transcript" > "$outdir/transcript.txt"
|
||||
local word_count
|
||||
word_count=$(echo "$transcript" | wc -w | tr -d ' ')
|
||||
ok "Transcript: $word_count words"
|
||||
|
||||
# ── step 3: summarize ──
|
||||
step "Step 3/4 — Summarizing ($OLLAMA_MODEL)"
|
||||
local summarize_prompt
|
||||
summarize_prompt="Please provide a clear, concise summary of the following meeting transcript.
|
||||
Structure the summary with:
|
||||
- **Topic**: What the meeting was about
|
||||
- **Key Points**: Main topics discussed (3-5 bullet points)
|
||||
- **Decisions**: Any decisions that were made
|
||||
- **Open Questions**: Things left unresolved
|
||||
|
||||
TRANSCRIPT:
|
||||
$transcript"
|
||||
|
||||
local summary
|
||||
summary="$(ollama_generate "$summarize_prompt" "You are an expert meeting summarizer. Be concise but thorough. Always use markdown formatting.")"
|
||||
echo "$summary" > "$outdir/summary.md"
|
||||
ok "Summary saved"
|
||||
|
||||
# ── step 4: action items ──
|
||||
step "Step 4/4 — Extracting action items ($OLLAMA_MODEL)"
|
||||
local actions_prompt
|
||||
actions_prompt="Extract all action items, tasks, and commitments from the following meeting transcript.
|
||||
For each action item, provide:
|
||||
- **Who**: The person responsible (or 'Unassigned' if unclear)
|
||||
- **What**: The specific task or action
|
||||
- **When**: Any deadline mentioned (or 'No deadline specified')
|
||||
- **Priority**: High / Medium / Low (based on urgency and context)
|
||||
|
||||
Be thorough — capture every commitment, follow-up, and task mentioned or implied.
|
||||
Format as a numbered list.
|
||||
|
||||
TRANSCRIPT:
|
||||
$transcript"
|
||||
|
||||
local actions
|
||||
actions="$(ollama_generate "$actions_prompt" "You are an expert at extracting action items from meeting notes. Be thorough and specific. Always use markdown formatting.")"
|
||||
echo "$actions" > "$outdir/action_items.md"
|
||||
ok "Action items saved"
|
||||
|
||||
# ── combine report ──
|
||||
cat > "$outdir/report.md" <<REPORT
|
||||
# Meeting Report
|
||||
|
||||
**Date:** $(date '+%B %d, %Y at %H:%M')
|
||||
**Source:** $(basename "$input_file")
|
||||
**STT:** $STT_ENGINE | **LLM:** $OLLAMA_MODEL
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
$summary
|
||||
|
||||
---
|
||||
|
||||
## Action Items
|
||||
|
||||
$actions
|
||||
|
||||
---
|
||||
|
||||
## Full Transcript
|
||||
|
||||
$transcript
|
||||
REPORT
|
||||
|
||||
echo "" >&2
|
||||
ok "All done! Files saved to: $outdir/" >&2
|
||||
echo "" >&2
|
||||
echo " 📄 Report: $outdir/report.md" >&2
|
||||
echo " 📝 Transcript: $outdir/transcript.txt" >&2
|
||||
echo " 📋 Summary: $outdir/summary.md" >&2
|
||||
echo " ✅ Action Items: $outdir/action_items.md" >&2
|
||||
echo "" >&2
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
|
||||
echo "" >&2
|
||||
head -50 "$outdir/report.md" | tail -35 >&2
|
||||
echo "" >&2
|
||||
echo "(showing preview — full report at $outdir/report.md)" >&2
|
||||
}
|
||||
|
||||
# ─── entry point ────────────────────────────────────────────────────
|
||||
case "${1:-help}" in
|
||||
setup) shift; cmd_setup ;;
|
||||
doctor) load_config; cmd_doctor ;;
|
||||
config) cmd_config ;;
|
||||
help|--help|-h)
|
||||
banner >&2
|
||||
cat <<'HELP'
|
||||
Usage:
|
||||
./meetings <audio_file> Run the full pipeline
|
||||
./meetings setup Install deps + download model
|
||||
./meetings doctor Check all dependencies
|
||||
./meetings config Show current configuration
|
||||
|
||||
Options (override config):
|
||||
--stt <whisper|parakeet> STT engine to use
|
||||
--model <path> Path to GGUF/GGML model file
|
||||
--llm <ollama_model> Ollama model for summarization
|
||||
--lang <code> Language code (default: en, auto for whisper)
|
||||
--threads <N> Number of threads for STT
|
||||
--output <dir> Output directory (default: .)
|
||||
|
||||
Environment variables:
|
||||
MEETINGS_DIR Config & models directory (default: ~/.meetings)
|
||||
MEETINGS_STT STT engine (whisper/parakeet)
|
||||
MEETINGS_STT_MODEL Path to model file
|
||||
MEETINGS_LLM Ollama model name (default: llama3.1:8b)
|
||||
MEETINGS_THREADS Thread count (default: 4)
|
||||
MEETINGS_LANG Language code (default: en)
|
||||
MEETINGS_OUTPUT Output directory (default: .)
|
||||
|
||||
Examples:
|
||||
# First time — install everything
|
||||
./meetings setup
|
||||
|
||||
# Process a meeting recording
|
||||
./meetings recording.mp3
|
||||
|
||||
# Use a different LLM model
|
||||
./meetings meeting.wav --llm llama3.1:8b
|
||||
|
||||
# Use parakeet with multilingual model
|
||||
./meetings call.wav --stt parakeet --lang auto
|
||||
|
||||
# Specify output directory
|
||||
./meetings interview.m4a --output ./reports
|
||||
HELP
|
||||
;;
|
||||
*)
|
||||
cmd_process "$@"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user