π«π· Version franΓ§aise
π Live service: parseandcut.alithiel31.dev
Meetup Killer turns audio recordings of lectures or meetings into structured Markdown study notes β or, if you just want the raw transcript, that too. Built to run on a Raspberry Pi, deployed via Docker, exposed publicly through a Cloudflare Tunnel β no port forwarding. Heavy processing is offloaded to the Groq API (Whisper Large V3 for transcription, GPT-OSS 120B for structuring).
- Stack & skills
- Features
- Supported audio formats
- Architecture
- Environment variables
- Local setup
- Deployment (Docker + Raspberry Pi)
- Testing & CI
- Releases & versioning
- Contributing
- License
This project covers, end to end:
- Backend: FastAPI + Uvicorn (Python 3.10), migrated from an original Flask implementation
- Audio processing: FFmpeg (chunking long recordings for the Groq 25 MB per-file limit)
- AI: Groq API β Whisper Large V3 (transcription) + GPT-OSS 120B (structuring)
- Frontend:
frontend/β React + Vite Progressive Web App, packaged as a TWA (Trusted Web Activity) for Android - Containerization & deployment: Docker Compose (backend + frontend containers), Raspberry Pi target via a
docker context, Cloudflare Tunnel for HTTPS/public access with no port forwarding β seedocs/DEPLOY_PI.md - CI/CD: GitHub Actions β linting (flake8 for the backend, oxlint for the frontend), unit tests (pytest), and integration tests (FastAPI app boot,
/health,/processerror paths) on every push/PR - Operational history: migrated the hosting platform from Railway to a self-hosted Docker/Pi setup β see
docs/Troubleshooting.md - Documentation: versioned changelog (Keep a Changelog), tagged releases (SemVer)
| Feature | Detail |
|---|---|
| ποΈ Long audio support | Auto-split into 10-min chunks β handles recordings over 1 hour |
| π§ Structured notes | Summary, hierarchical headings, bold keywords, definition blocks |
| π Two output modes | Pick AI summary or raw transcript per upload β transcript mode skips the LLM call entirely, and comes with per-segment timestamps |
| π Modern UI | Drag & drop, step-by-step progress bar, processing stats |
| π«π·π¬π§ French/English UI | Language switcher (header), translates the whole flow and the legal pages; drives the LLM structuring prompt's output language too β independent of Whisper, which always auto-detects the spoken language |
| π Local notifications | Opt-in "notify me when done" β pings the device via the service worker once a transcription finishes, useful when the tab is backgrounded |
| β Robust validation | File type + size checked both client-side and server-side (100 MB max β Cloudflare's own proxy ceiling on Free/Pro, see docs/Troubleshooting.md) |
| π Auto retry | Whisper retried on network timeout with exponential backoff |
| π³ Docker ready | FFmpeg + Uvicorn pre-configured, lightweight python:3.10-slim image |
π /health endpoint |
Monitoring: Groq status, default output language, supported formats |
| βοΈ Legal pages | Terms, privacy policy and legal notice served by the PWA (/cgu, /politique-de-confidentialite, /mentions-legales), each in French and English |
mp3 Β· mp4 Β· wav Β· m4a Β· ogg Β· webm Β· flac Β· aac Β· opus
flowchart LR
U["User (browser / TWA)"] -->|upload audio| FE
subgraph Pi["Raspberry Pi (Docker Compose)"]
TRA["Traefik<br/>reverse proxy Β· :8000"]
FE["frontend container<br/>nginx Β· frontend/<br/>traefik-net"]
BE["backend container<br/>FastAPI + Uvicorn<br/>:5000 (internal only)"]
TRA -->|"Host: parseandcut.alithiel31.dev"| FE
FE -->|reverse-proxy /api/*| BE
end
BE -->|FFmpeg| CHUNK["10-min chunks"]
CHUNK -->|Whisper Large V3| TXT["raw transcription"]
TXT -->|"mode=summary"<br/>GPT-OSS 120B| MD["structured Markdown"]
TXT -->|"mode=transcript"| RAW["raw transcript returned as-is"]
TUN["Cloudflare Tunnel<br/>parseandcut.alithiel31.dev"] --> TRA
U -.->|public access| TUN
Nginx (frontend container) serves the PWA static files and reverse-proxies /api/ to the backend β same-origin, no CORS to manage in production. Traefik (shared reverse proxy on the Pi) routes the public hostname to the frontend container via traefik-net, without exposing a dedicated port on the host. The Cloudflare Tunnel handles HTTPS and the domain name β no certificate to manage manually, no port opened on the router.
| Variable | Required | Default | Description |
|---|---|---|---|
GROQ_API_KEY |
β | β | Groq API key (console.groq.com) |
LANGUAGE |
β | fr |
Default output language (fr/en) used when a request omits lang β does not force Whisper's input language, which always auto-detects |
PORT |
β | 5000 |
Port the backend listens on |
CHUNK_DURATION_SEC |
β | 600 |
Chunk duration in seconds |
FFMPEG_PATH |
β | ffmpeg |
Path to the FFmpeg binary |
CORS_ORIGINS |
β | https://parseandcut.alithiel31.dev |
Comma-separated allowed origins β override for local dev (Vite on :5173 calling Uvicorn on :5000) |
MAX_UPLOAD_SIZE_MB |
β | 100 |
Max upload size enforced by the backend (in addition to nginx's client_max_body_size) β matches Cloudflare's own proxy ceiling on Free/Pro plans |
RATE_LIMIT_PROCESS |
β | 5/minute |
Rate limit on /process (per IP), format N/period |
FLASK_DEBUG |
β | false |
Debug mode (dev only) |
Production note: on the Caesura deployment,
GROQ_API_KEYis not read from.envβ it's injected at deploy time via Infisical (seedocs/DEPLOY_PI.md). For local development, set it directly in your.envas described below.
-
Clone the repository
git clone https://github.com/Alithiel31/ParseAndCutV2.git cd ParseAndCutV2 -
Create your
.envfilecp ".env exemple" .envFill in
GROQ_API_KEYat minimum. -
Run with Docker
docker build -t meetup-killer . docker run -d -p 5000:5000 --name meetup-app --env-file .env meetup-killerOr without Docker (Python 3.10 + FFmpeg installed locally):
pip install -r requirements.txt python -m app.main
-
Open the app at http://localhost:5000
On Raspberry Pi, replace
localhostwith the device's local IP address.
This is the production setup. This repo's docker-compose.yml runs the backend (internal network, port 5000) and the frontend/ PWA (nginx, routed via Traefik on traefik-net, no port exposed on the host) β see docs/DEPLOY_PI.md for the full procedure (creating the docker context to the Pi, building, configuring the Traefik labels and the Cloudflare ingress).
docker context use rpi
docker compose build
bash deploy.shCheck the /health endpoint anytime to verify service status.
# Linting β same checks as the lint.yml workflow
npm run lint # flake8 (backend)
cd frontend && npm run lint # oxlint (frontend)
# Unit tests (FastAPI routes mocked against FFmpeg/Groq) β same as the integration.yml workflow
pytestTwo workflows run on every push/PR to main:
- CI Lint (
.github/workflows/lint.yml): flake8 (backend), oxlint (frontend) - CI Integration (
.github/workflows/integration.yml): runs the pytest suite, then boots the FastAPI app and checks/,/health, and the/processerror paths (missing file β 400, unsupported format β 415)
This project follows Semantic Versioning (MAJOR.MINOR.PATCH) and Keep a Changelog. Every notable change lands in CHANGELOG.md under [Unreleased] first, then under a version heading once tagged:
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.ZA GitHub Release is then created from the tag, with its description copied from the matching CHANGELOG.md section.
See docs/CONTRIBUTING.md for the development environment, how to reproduce the CI checks locally, and the PR/release format.
See docs/SECURITY.md to report a vulnerability.
MIT (see license field in package.json).