Skip to content

Repository files navigation

Voice Genius

Record your voice, get it transcribed, have a language model expand on it, and hear the result read back.

A full-stack demo built with Django REST Framework and React + Vite. It runs from a fresh clone with no database server and no configuration — add an OpenAI key when you want the audio pipeline to do real work.

License: GPL v3

How it works

Browser (MediaRecorder)
   │  POST /api/audio_processing/   multipart: file + user_id
   ▼
Django  ──▶ ffmpeg (trim to 60s)
        ──▶ OpenAI Whisper          speech → text
        ──▶ OpenAI gpt-3.5-turbo    text → elaborated text (≤150 words)
        ──▶ gTTS                    text → mp3
   │
   ▼  JSON: { audio_file, data_to_show[] }
Browser renders the transcript and speaks it with the Web Speech API

The generated mp3 is stored server-side and its URL returned, but playback in the browser uses the Web Speech API on the response text, not that file.

Features

  • Voice recording in the browser via MediaRecorder.
  • Transcription with OpenAI Whisper, capped at 60 seconds per clip.
  • Text generation with gpt-3.5-turbo-0125, capped at ~150 words.
  • Playback with play / pause / resume through the Web Speech API.
  • Rolling history of the last 10 exchanges per session, with a pager.
  • Contact form protected by Google reCAPTCHA.
  • Automatic data expiry — stored recordings and transcripts are swept after 24 hours by default.

Tech stack

Layer Choice
Backend Python 3.12, Django 5.2, Django REST Framework 3.16
Frontend React 19, Vite 7, Tailwind CSS 3
Database SQLite by default; PostgreSQL/MySQL via DB_ENGINE
Speech-to-text OpenAI Whisper (whisper-1)
Text generation OpenAI (gpt-3.5-turbo-0125)
Text-to-speech gTTS (server-side), Web Speech API (in-browser playback)
Audio decoding pydub + ffmpeg
Serving Gunicorn + WhiteNoise (backend), nginx (frontend)

Requirements

  • Python 3.12+
  • Node.js 22+
  • ffmpeg and ffprobe on PATH — pydub shells out to them to decode uploads.
    • Debian/Ubuntu: sudo apt-get install ffmpeg
    • macOS: brew install ffmpeg
    • Windows: winget install Gyan.FFmpeg, then reopen your terminal

Without ffmpeg the app starts and the contact form works; the audio endpoint returns 503 with a message saying so.

Quick start

git clone <repository-url>
cd voice-genius

Backend (terminal 1):

cd backend
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env               # runs as-is; set OPENAI_API_KEY for the audio pipeline
python manage.py migrate           # creates db.sqlite3
python manage.py runserver

Frontend (terminal 2):

cd frontend
npm ci
cp .env.example .env
npm run dev

Open http://localhost:5173. The API is at http://localhost:8000/api/.

With Docker

Builds the production images — Gunicorn and nginx, both running as non-root:

cp backend/.env.example backend/.env   # optional; set OPENAI_API_KEY here
docker compose up --build

Frontend on http://localhost:8080, API on http://localhost:8000. Requires Docker Compose v2.24+ (the .env file is declared optional, so the stack still comes up without one).

Configuration

Copy backend/.env.examplebackend/.env and frontend/.env.examplefrontend/.env. Every variable below is read by the code; nothing is listed here that the application does not actually use.

Backend

Variable Default Description
DEBUG false Never enable in production. Also gates the fallbacks below.
DJANGO_SECRET_KEY dev fallback when DEBUG=true Required when DEBUG=false — startup fails without it.
ALLOWED_HOSTS localhost,127.0.0.1,[::1] when DEBUG=true Required when DEBUG=false. * is rejected.
CORS_ALLOWED_ORIGINS localhost:5173 when DEBUG=true, else empty Comma-separated origins allowed to call the API.
CSRF_TRUSTED_ORIGINS same as CORS_ALLOWED_ORIGINS Comma-separated.
OPENAI_API_KEY (empty) Required for the audio pipeline; without it that endpoint returns 503.
RECAPTCHA_SECRET_KEY (empty when DEBUG=true) Required when DEBUG=false. Empty in debug skips verification.
DB_ENGINE django.db.backends.sqlite3 Point at another backend to use a database server.
DB_NAME backend/db.sqlite3 Required when DB_ENGINE is not SQLite.
DB_USER Required when DB_ENGINE is not SQLite.
DB_PASSWORD Required when DB_ENGINE is not SQLite.
DB_HOST localhost Ignored for SQLite.
DB_PORT (empty) Ignored for SQLite.
OPENAI_TRANSCRIPTION_MODEL whisper-1 Speech-to-text model.
OPENAI_TEXT_MODEL gpt-3.5-turbo-0125 Text generation model.
OPENAI_TIMEOUT_SECONDS 60 Per-request timeout for OpenAI calls.
MEDIA_ROOT backend/media Where recordings and transcripts are written.
AUDIO_MAX_DURATION_SECONDS 60 Uploads are trimmed to this before transcription.
AUDIO_MAX_UPLOAD_BYTES 10485760 (10 MB) Larger uploads are rejected before touching disk.
AUDIO_HISTORY_LENGTH 10 Transcript/response pairs kept per session.
AUDIO_RETENTION_HOURS 24 Session folders older than this are swept. 0 disables.
AUDIO_PERSIST_RECORDINGS true Set false to never store audio, keeping only text.
LOG_LEVEL INFO Root logger level.
API_ANON_THROTTLE_RATE 30/minute DRF throttle for anonymous callers.
SECURE_SSL_REDIRECT true when DEBUG=false Set false if a proxy already redirects.
SECURE_HSTS_SECONDS 31536000 Applied only when DEBUG=false.

Frontend

Variable Default Description
VITE_API_URL http://localhost:8000 Backend origin only — the /api/ prefix is added in code.
VITE_RECAPTCHA_SITE_KEY (empty) Public site key. Without it the form shows a notice instead of the captcha.

Vite inlines VITE_* variables at build time, not at container start. A Docker image must receive them as build arguments; see docker-compose.yml. Never put a secret in a VITE_* variable — they ship in the bundle.

API reference

Base URL: {VITE_API_URL}/api/. Both endpoints are unauthenticated and subject to API_ANON_THROTTLE_RATE.

POST /api/submit_form/

Submits the contact form.

Body (application/json):

Field Type Notes
first_name string max 100
last_name string max 100
company string max 200
email string must be a valid email
project string[] at least one entry
message string
recaptcha_token string verified unless DEBUG=true and no secret key is set

Responses: 201 {"message": "Form submitted successfully"} · 400 with a field-keyed error object · 429 when throttled.

POST /api/audio_processing/

Transcribes a recording, elaborates on it, and synthesises speech.

Body (multipart/form-data):

Field Type Notes
file file Non-empty, at most AUDIO_MAX_UPLOAD_BYTES
user_id string Must be a valid UUID — it names the storage folder

200:

{
  "audio_file": "http://localhost:8000/media/<uuid>/latest_output.mp3",
  "data_to_show": [
    { "transcription": "hello world", "generated_text": "..." }
  ]
}

data_to_show is newest-first and holds up to AUDIO_HISTORY_LENGTH entries. audio_file is null when AUDIO_PERSIST_RECORDINGS=false.

Error responses — each is a real status, never a 200 carrying a failure message in the transcript field:

Status Meaning
400 Invalid user_id, empty/oversized file, or undecodable audio
429 Rate limited
502 Transcription, generation, or speech synthesis failed upstream
503 OPENAI_API_KEY unset, or ffmpeg/ffprobe not on PATH

Errors are shaped {"error": "..."}; validation errors are field-keyed.

/media/ is served by Django only when DEBUG=true. In production, serve it from your web server or object store.

Project structure

voice-genius/
├── backend/
│   ├── VoiceGenius/          # Django project: settings, root urls, wsgi/asgi
│   ├── voiceApp/
│   │   ├── views.py          # HTTP transport only
│   │   ├── serializers.py    # request validation
│   │   ├── services.py       # business logic: storage, retention, orchestration
│   │   ├── providers.py      # adapters for OpenAI, gTTS, ffmpeg
│   │   ├── exceptions.py     # domain errors, each with an HTTP status
│   │   ├── models.py         # AppFormSubmission
│   │   └── management/commands/purge_sessions.py
│   ├── tests/                # pytest suite (49 tests)
│   ├── requirements.txt      # runtime dependencies
│   ├── requirements-dev.txt  # + ruff, pytest, pip-audit
│   └── pyproject.toml        # ruff and pytest configuration
├── frontend/
│   └── src/
│       ├── components/       # presentational components
│       ├── hooks/            # stateful logic
│       ├── pages/Home.jsx
│       ├── services/         # API client and URL building
│       ├── data/products.js
│       └── constants.js      # shared magic values
├── .github/workflows/ci.yml
├── docker-compose.yml
├── CONTRIBUTING.md
├── SECURITY.md
└── LICENSE

Development

The exact commands CI runs are in CONTRIBUTING.md.

# Backend, from backend/
ruff check . && ruff format --check . && pytest

# Frontend, from frontend/
npm run lint && npm test && npm run build

Delete stored personal data on demand:

python manage.py purge_sessions          # anything past AUDIO_RETENTION_HOURS
python manage.py purge_sessions --all    # everything

Limitations

Worth knowing before you build on this:

  • Both API endpoints are unauthenticated. Rate limiting and reCAPTCHA are the only protection. Anyone who can reach the audio endpoint spends your OpenAI quota.
  • The session id is client-supplied. It is validated as a UUID so it cannot escape MEDIA_ROOT, but anyone holding another session's UUID can read that session's transcript history. It is not an authentication mechanism.
  • Recordings are capped at 60 seconds; anything longer is silently trimmed.
  • English only. Transcription, generation and synthesis are all hardcoded to en.
  • Contact form submissions never expire automatically. Audio and transcripts do; database rows do not. See SECURITY.md.
  • Recordings leave your infrastructure. Audio goes to OpenAI; generated text goes to Google's TTS endpoint via gTTS. Disclose this to your users.
  • No streaming. The request blocks for the full transcribe → generate → synthesise round trip, typically several seconds.
  • gpt-3.5-turbo-0125 is a dated model. Change OPENAI_TEXT_MODEL to use a current one.
  • Browser support: MediaRecorder and the Web Speech API are required. Playback quality varies considerably between browsers.

Security

Please report vulnerabilities privately — see SECURITY.md, which also carries a safe-deployment checklist and a description of exactly what personal data this application collects.

Licence

Distributed under the GNU General Public License v3.0. See LICENSE.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

About

Voice-to-text and voice-generation platform that allows users to record audio, transcribe speech with advanced artificial intelligence, expand the content into polished output, and listen to the result through realistic speech playback.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages