adding the first version of the presentation and the initial attempt at the .meetings script
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
Here are the extracted action items, tasks, and commitments:
|
||||
|
||||
1. **Who**: Unassigned
|
||||
**What**: Review and revise meeting notes to ensure accuracy and completeness.
|
||||
**When**: No deadline specified
|
||||
**Priority**: Low
|
||||
|
||||
2. **Who**: Alaz
|
||||
**What**: Follow up with Snorri regarding his dimensional teapot and its contents.
|
||||
**When**: No deadline specified
|
||||
**Priority**: Medium
|
||||
|
||||
3. **Who**: Tuxi
|
||||
**What**: Provide Alaz with more information about the dimensional teapot's prize.
|
||||
**When**: No deadline specified
|
||||
**Priority**: Low
|
||||
|
||||
4. **Who**: Unassigned
|
||||
**What**: Research and provide answers to Snorri's questions regarding the family portraits in his lounge.
|
||||
**When**: No deadline specified
|
||||
**Priority**: Medium
|
||||
|
||||
5. **Who**: Alaz
|
||||
**What**: Investigate the origins of the jar image found in Snorri's lounge.
|
||||
**When**: No deadline specified
|
||||
**Priority**: Low
|
||||
|
||||
6. **Who**: Unassigned
|
||||
**What**: Clarify and resolve any outstanding questions or issues from the meeting.
|
||||
**When**: No deadline specified
|
||||
**Priority**: High
|
||||
|
||||
Note: The conversation at the end of the transcript appears to be a discussion about whether anything was missed, but no specific action items were assigned.
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
|
||||
**Topic**: **Campaign Session Recap**
|
||||
|
||||
**Key Points**:
|
||||
|
||||
* The group participated in a combat encounter with the Beatles, using creative methods to defeat them.
|
||||
* A "beat-o" was defeated by Snorri using an apple, granting him a ghostly speed ability for a week.
|
||||
* The group prepared and presented three dishes: Tuxi's colorful boom-boom decoration, Snorri's sashimi, and Alaz's flamed dish.
|
||||
* For dessert, the group went on a foraging task, with Tuxy preparing lychee surprise and Snorri making a lingon berry jelly.
|
||||
* Alaz visited a village of awakened apes and obtained ingredients to make a rice pudding.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
* The competition was decided, with Alaz winning a dimensional teapot and Snorri coming second with an arcane Rubik's gift.
|
||||
* Snorri exchanged prizes with Alaz, obtaining the teapot and rubbing it three times to teleport into a different dimension.
|
||||
* Snorri obtained an image of a jar from the new dimension.
|
||||
|
||||
**Open Questions**:
|
||||
|
||||
* What is the significance of the family portraits in the new dimension?
|
||||
* How will the group's experience in this new dimension affect their future gameplay?
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,178 @@
|
||||
# 🎤 meetings — local audio → transcript → summary + action items
|
||||
|
||||
A single-file shell script CLI that transcribes meeting recordings (using GGUF Whisper or Parakeet models), then generates a summary and extracts action items using Ollama. Everything runs 100% locally.
|
||||
|
||||
```
|
||||
audio file ──▶ ffmpeg ──▶ whisper.cpp / parakeet.cpp ──▶ Ollama ──▶ report.md
|
||||
│ │ │
|
||||
16kHz mono WAV GGUF transcription summary + actions
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# 1. Clone/download
|
||||
git clone <this-repo> meetings-cli && cd meetings-cli
|
||||
|
||||
# 2. One-time setup (installs whisper.cpp, downloads model, pulls Ollama LLM)
|
||||
./meetings setup
|
||||
|
||||
# 3. Process a meeting recording
|
||||
./meetings recording.mp3
|
||||
|
||||
# 4. Check everything is healthy
|
||||
./meetings doctor
|
||||
```
|
||||
|
||||
## What it does
|
||||
|
||||
| Step | Tool | What happens |
|
||||
|------|------|--------------|
|
||||
| 1. Convert | ffmpeg | Any audio → 16kHz mono WAV |
|
||||
| 2. Transcribe | whisper.cpp or parakeet.cpp | GGUF/GGML model → text transcript |
|
||||
| 3. Summarize | Ollama | Transcript → structured summary (topic, key points, decisions, open questions) |
|
||||
| 4. Extract | Ollama | Transcript → numbered action items (who, what, when, priority) |
|
||||
|
||||
## Output
|
||||
|
||||
For each audio file, a directory is created containing:
|
||||
|
||||
```
|
||||
2026-06-07_1402_team_standup/
|
||||
├── report.md # Combined: summary + actions + transcript
|
||||
├── transcript.txt # Raw transcription
|
||||
├── summary.md # LLM-generated summary
|
||||
└── action_items.md # Extracted action items
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
| Dependency | Install | Purpose |
|
||||
|------------|---------|---------|
|
||||
| **ffmpeg** | `brew install ffmpeg` | Audio format conversion |
|
||||
| **whisper.cpp** | `brew install whisper-cpp` | Speech-to-text (GGML models) |
|
||||
| **Ollama** | [ollama.com](https://ollama.com) | LLM for summarization |
|
||||
| **jq** | `brew install jq` | JSON parsing for Ollama API |
|
||||
|
||||
> `./meetings setup` handles all of this automatically.
|
||||
|
||||
## STT engines
|
||||
|
||||
### whisper.cpp (default, recommended)
|
||||
- Battle-tested, many languages, large model ecosystem
|
||||
- Models from [ggerganov/whisper.cpp](https://huggingface.co/ggerganov/whisper.cpp)
|
||||
- Install: `brew install whisper-cpp`
|
||||
|
||||
| Model | Size | Best for |
|
||||
|-------|------|----------|
|
||||
| tiny.en | 75 MB | Quick tests, English |
|
||||
| base.en | 142 MB | Good balance, English |
|
||||
| small.en | 466 MB | **Recommended for English** |
|
||||
| medium.en | 1.5 GB | High accuracy, English |
|
||||
| large-v3-turbo | 809 MB | Best multilingual, fast |
|
||||
| large-v3 | 2.9 GB | Best accuracy, any language |
|
||||
|
||||
### parakeet.cpp (alternative, faster)
|
||||
- NVIDIA Parakeet models, excellent English, smaller footprint
|
||||
- Models from [mudler/parakeet-cpp-gguf](https://huggingface.co/mudler/parakeet-cpp-gguf)
|
||||
- Install: Build from [source](https://github.com/mudler/parakeet.cpp) or use Docker
|
||||
|
||||
| Model | Size | Best for |
|
||||
|-------|------|----------|
|
||||
| tdt_ctc-110m-q8_0 | 178 MB | Fast, good English |
|
||||
| tdt_ctc-110m-f16 | 268 MB | Fast, lossless English |
|
||||
| tdt-0.6b-v3-f16 | 1.4 GB | Multilingual |
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment variables
|
||||
|
||||
```bash
|
||||
MEETINGS_DIR # Config & models directory (default: ~/.meetings)
|
||||
MEETINGS_STT # STT engine: whisper | parakeet
|
||||
MEETINGS_STT_MODEL # Path to GGUF/GGML model file
|
||||
MEETINGS_LLM # Ollama model for summarization (default: llama3.1:8b)
|
||||
MEETINGS_THREADS # Thread count for STT (default: 4)
|
||||
MEETINGS_LANG # Language code (default: en; use "auto" for multilingual)
|
||||
MEETINGS_OUTPUT # Output directory (default: .)
|
||||
```
|
||||
|
||||
### CLI flags
|
||||
|
||||
```bash
|
||||
./meetings recording.mp3 --stt whisper --llm llama3.1:8b --lang en --output ./reports
|
||||
```
|
||||
|
||||
### Config file
|
||||
|
||||
Saved at `~/.meetings/config` after running `./meetings setup`:
|
||||
|
||||
```
|
||||
STT_ENGINE=whisper
|
||||
OLLAMA_MODEL=llama3.1:8b
|
||||
THREADS=4
|
||||
STT_MODEL=/home/user/.meetings/models/ggml-small.en.bin
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
./meetings <audio_file> # Run the full pipeline
|
||||
./meetings setup # Install deps + download model (interactive)
|
||||
./meetings doctor # Check all dependencies
|
||||
./meetings config # Show current configuration
|
||||
./meetings help # Show help
|
||||
```
|
||||
|
||||
## How Ollama fits in
|
||||
|
||||
**Ollama does NOT run the whisper/parakeet models** — those use their own inference engines (whisper.cpp / parakeet.cpp). Ollama is only used for the LLM steps:
|
||||
|
||||
1. **Summary generation** — sends the transcript to an Ollama model with a structured summarization prompt
|
||||
2. **Action item extraction** — sends the transcript to an Ollama model with an action-item extraction prompt
|
||||
|
||||
You can use any Ollama model. Smaller models (llama3.2:1b, gemma3:1b) are faster; larger models (llama3.1:8b, qwen2.5-coder:7b) produce better summaries.
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
$ ./meetings team_standup.m4a
|
||||
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 🎤 M E E T I N G S │
|
||||
│ audio → transcript → summary + actions │
|
||||
│ whisper.cpp · parakeet.cpp · ollama │
|
||||
└─────────────────────────────────────────────┘
|
||||
|
||||
Input: team_standup.m4a
|
||||
STT engine: whisper
|
||||
STT model: ggml-small.en.bin
|
||||
LLM model: llama3.1:8b
|
||||
Language: en
|
||||
Output: ./2026-06-07_1402_team_standup/
|
||||
|
||||
── Step 1/4 — Converting audio ──
|
||||
▸ Converting audio to 16kHz mono WAV...
|
||||
✓ Audio converted: 1.2M
|
||||
|
||||
── Step 2/4 — Transcribing with whisper ──
|
||||
▸ Transcribing with whisper.cpp...
|
||||
✓ Transcript: 847 words
|
||||
|
||||
── Step 3/4 — Summarizing (llama3.1:8b) ──
|
||||
✓ Summary saved
|
||||
|
||||
── Step 4/4 — Extracting action items (llama3.1:8b) ──
|
||||
✓ Action items saved
|
||||
|
||||
✓ All done! Files saved to: ./2026-06-07_1402_team_standup/
|
||||
|
||||
📄 Report: ./2026-06-07_1402_team_standup/report.md
|
||||
📝 Transcript: ./2026-06-07_1402_team_standup/transcript.txt
|
||||
📋 Summary: ./2026-06-07_1402_team_standup/summary.md
|
||||
✅ Action Items: ./2026-06-07_1402_item_standup/action_items.md
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
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