# π Meetup Killer β AI Course Assistant
π«π· [Version franΓ§aise](./README.fr.md)
[](https://github.com/Alithiel31/ParseAndCutV2/actions/workflows/lint.yml)
[](https://github.com/Alithiel31/ParseAndCutV2/actions/workflows/integration.yml)
[](https://www.python.org/)
[](https://fastapi.tiangolo.com/)
[](https://react.dev/)
[](https://www.typescriptlang.org/)
[](https://vite.dev/)
[](https://www.docker.com/)
[](https://nginx.org/)
[](https://www.raspberrypi.com/)
[](https://groq.com/)
[](https://parseandcut.alithiel31.dev)
[](https://www.cloudflare.com/)
[](https://opensource.org/licenses/MIT)
> π **Live service**: [parseandcut.alithiel31.dev](https://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).
## Table of contents
- [Stack & skills](#stack--skills)
- [Features](#features)
- [Supported audio formats](#supported-audio-formats)
- [Architecture](#architecture)
- [Environment variables](#environment-variables)
- [Local setup](#local-setup)
- [Deployment (Docker + Raspberry Pi)](#deployment-docker--raspberry-pi)
- [Testing & CI](#testing--ci)
- [Releases & versioning](#releases--versioning)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [License](#license)
## Stack & skills
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/`](./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 β see [`docs/DEPLOY_PI.md`](./docs/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`, `/api/transcribe/start` error paths) on every push/PR
- **Operational history**: migrated the hosting platform from Railway to a self-hosted Docker/Pi setup β see [`docs/Troubleshooting.md`](./docs/Troubleshooting.md)
- **Documentation**: versioned changelog ([Keep a Changelog](https://keepachangelog.com/en/1.0.0/)), tagged releases (SemVer)
## Features
| 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`](./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 |
## Supported audio formats
`mp3` Β· `mp4` Β· `wav` Β· `m4a` Β· `ogg` Β· `webm` Β· `flac` Β· `aac` Β· `opus`
## Architecture
```mermaid
flowchart LR
U["User (browser / TWA)"] -->|upload audio| FE
subgraph Pi["Raspberry Pi (Docker Compose)"]
TRA["Traefik
reverse proxy Β· :8000"]
FE["frontend container
nginx Β· frontend/
traefik-net"]
BE["backend container
FastAPI + Uvicorn
: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"
GPT-OSS 120B| MD["structured Markdown"]
TXT -->|"mode=transcript"| RAW["raw transcript returned as-is"]
TUN["Cloudflare Tunnel
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.
## Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
| `GROQ_API_KEY` | β
| β | Groq API key ([console.groq.com](https://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 `/api/transcribe/start` and on creating/finishing a segmented recording (per IP), format `N/period` |
| `RATE_LIMIT_SEGMENTS` | β | `60/minute` | Rate limit on segment uploads of a segmented recording (per IP, shared by all segments) |
| `RECORDING_SEGMENT_SEC` | β | `300` | Length of one recording segment, announced to the browser |
| `RECORDING_EARLY_TRANSCRIPTION` | β | `1` | Transcribe each segment as soon as it arrives, while the meeting is still running (`0` to process everything at the end) |
| `MAX_SEGMENT_SIZE_MB` | β | `50` | Max size of one uploaded segment |
| `MAX_RECORDING_SIZE_MB` | β | `300` | Max cumulated size of all segments of one recording |
| `MAX_SEGMENTS` | β | `500` | Max number of segments in one recording |
| `FLASK_DEBUG` | β | `false` | Debug mode (dev only) |
> **Production note:** on the Caesura deployment, `GROQ_API_KEY` is not read from `.env` β it's injected at deploy time via Infisical (see `docs/DEPLOY_PI.md`). For local development, set it directly in your `.env` as described below.
## Local setup
1. **Clone the repository**
```bash
git clone https://github.com/Alithiel31/ParseAndCutV2.git
cd ParseAndCutV2
```
2. **Create your `.env` file**
```bash
cp ".env exemple" .env
```
Fill in `GROQ_API_KEY` at minimum.
3. **Run with Docker**
```bash
docker build -t meetup-killer .
docker run -d -p 5000:5000 --name meetup-app --env-file .env meetup-killer
```
Or without Docker (Python 3.10 + FFmpeg installed locally):
```bash
pip install -r requirements.txt
python -m app.main
```
4. **Open the app** at [http://localhost:5000](http://localhost:5000)
> On Raspberry Pi, replace `localhost` with the device's local IP address.
## Deployment (Docker + Raspberry Pi)
This is the production setup. This repo's `docker-compose.yml` runs the backend (internal network, port **5000**) and the [`frontend/`](./frontend) PWA (nginx, routed via Traefik on `traefik-net`, no port exposed on the host) β see [`docs/DEPLOY_PI.md`](./docs/DEPLOY_PI.md) for the full procedure (creating the `docker context` to the Pi, building, configuring the Traefik labels and the Cloudflare ingress).
```bash
docker context use rpi
docker compose build
bash deploy.sh
```
Check the [`/health`](https://parseandcut.alithiel31.dev/api/health) endpoint anytime to verify service status.
## Testing & CI
```bash
# 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
pytest
```
Two 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 `/api/transcribe/start` error paths (missing file β 400, unsupported format β 415)
## Releases & versioning
This project follows [Semantic Versioning](https://semver.org/) (`MAJOR.MINOR.PATCH`) and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Every notable change lands in [`CHANGELOG.md`](./CHANGELOG.md) under `[Unreleased]` first, then under a version heading once tagged:
```bash
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z
```
A GitHub Release is then created from the tag, with its description copied from the matching `CHANGELOG.md` section.
## Documentation
| Guide | Content |
|---|---|
| [`docs/architecture.md`](./docs/architecture.md) | Asynchronous job model, processing pipeline, segmented recording, security layers, repository structure |
| [`docs/environment.md`](./docs/environment.md) | Every environment variable, hard-coded constants, deployment and CI secrets |
| [`docs/development.md`](./docs/development.md) | Run it locally, routes and error codes, tests, CI |
| [`docs/DEPLOY_PI.md`](./docs/DEPLOY_PI.md) | Docker context, Traefik, Cloudflare ingress, Infisical |
| [`docs/handover-guide.md`](./docs/handover-guide.md) | What runs where, which accesses are needed, how to rebuild from scratch, known risks |
| [`docs/Troubleshooting.md`](./docs/Troubleshooting.md) | Incidents already encountered, with the diagnostic process |
| [`docs/CONTRIBUTING.md`](./docs/CONTRIBUTING.md) Β· [`docs/SECURITY.md`](./docs/SECURITY.md) Β· [`docs/SUPPORT.md`](./docs/SUPPORT.md) Β· [`docs/CODE_OF_CONDUCT.md`](./docs/CODE_OF_CONDUCT.md) | Community files |
Each guide has a French version (`*.fr.md`).
## Contributing
See [`docs/CONTRIBUTING.md`](./docs/CONTRIBUTING.md) for the development environment, how to reproduce the CI checks locally, and the PR/release format.
## Security
See [`docs/SECURITY.md`](./docs/SECURITY.md) to report a vulnerability.
## License
MIT (see `license` field in [`package.json`](./package.json)).