Free‑Tier Speech‑to‑Text & Text‑to‑Speech Demo

Mistral’s speech models for transcription, TTS and agents

Welcome to another official UVF IT guide! In this quick‑fire tutorial we’ll spin up a minimal speech‑to‑text and text‑to‑speech demo using only free‑tier, open‑source tools. No paid APIs, just Python, SQLite, and the Whisper & Coqui TTS models you can run locally.

You’ll end up with a tiny Flask app that accepts an audio file, returns a transcript, and can synthesize a spoken reply – all in under an hour. Perfect for learning the basics and extending into richer voice agents later.

Before you start…

Demo Architecture
LOCAL RUNTIME transcribe synthesize Flask API Python + SQLite SQLite DB Whisper (tiny) Local model Coqui TTS (vits) Local model
1

Phase 1: Scaffold the project

Create a clean directory, a virtual environment, and install the required packages. This gives you an isolated workspace and pulls in Whisper, Coqui TTS, Flask and a few helpers so the rest of the guide can run without extra setup.

mkdir speech_demo && cd speech_demo
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install flask torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cpu
pip install openai-whisper tts
# Verify installations
python -c "import whisper, tts; print('Packages loaded')"
2

Phase 2: Define the data model

A tiny SQLite table stores each request’s audio filename, transcript, and generated speech path. Keeping this metadata locally lets the Flask app quickly look up past results and demonstrates a simple persistence layer.

model.py

import sqlite3

DB_PATH = 'speech_demo.db'

def init_db():
    conn = sqlite3.connect(DB_PATH)
    cur = conn.cursor()
    cur.execute('''
        CREATE TABLE IF NOT EXISTS requests (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            audio_file TEXT NOT NULL,
            transcript TEXT,
            tts_file TEXT
        )
    ''')
    conn.commit()
    conn.close()

if __name__ == '__main__':
    init_db()
    print('Database initialized at', DB_PATH)
3

Phase 3: Build the Flask API (core logic)

In this step we expose two HTTP endpoints that the rest of the app can call. The /transcribe route accepts a multipart‑form upload of a WAV file, runs Whisper locally, and returns the plain text inside a JSON object. The /speak route receives raw text in a JSON body, feeds it to a tiny TTS model (we use gtts for simplicity), and streams the resulting WAV file back to the caller. Keeping the logic in app.py makes the service easy to run, test, and later extend with authentication or logging.

app.py

import os
from flask import Flask, request, jsonify, send_file
import whisper
from gtts import gTTS
from pydub import AudioSegment
import io

app = Flask(__name__)

# Load the tiny Whisper model once at startup
model = whisper.load_model('tiny')

@app.route('/transcribe', methods=['POST'])
def transcribe():
    if 'audio' not in request.files:
        return jsonify({'error': 'No audio file provided'}), 400
    audio_file = request.files['audio']
    # Whisper expects a file path; save temporarily
    tmp_path = 'tmp.wav'
    audio_file.save(tmp_path)
    result = model.transcribe(tmp_path)
    os.remove(tmp_path)
    return jsonify({'text': result['text'].strip()})

@app.route('/speak', methods=['POST'])
def speak():
    data = request.get_json(silent=True) or {}
    text = data.get('text', '')
    if not text:
        return jsonify({'error': 'No text provided'}), 400
    # Generate MP3 with gTTS, then convert to WAV in memory
    tts = gTTS(text=text, lang='en')
    mp3_io = io.BytesIO()
    tts.write_to_fp(mp3_io)
    mp3_io.seek(0)
    wav_io = io.BytesIO()
    AudioSegment.from_file(mp3_io, format='mp3').export(wav_io, format='wav')
    wav_io.seek(0)
    return send_file(wav_io, mimetype='audio/wav', as_attachment=True, download_name='speech.wav')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
4

Phase 4: Run locally

Now that the API code is ready, start the Flask development server. The command below launches the service on port 5000 and keeps it alive so you can hit the endpoints with curl or a browser. If you modify app.py later, simply restart the process to pick up changes.

export FLASK_APP=app.py
export FLASK_ENV=development
flask run --host=0.0.0.0 --port=5000
5

Phase 5: Verify & extend

With the server running, use curl to send a short WAV sample and confirm you receive a JSON transcript. Then test the TTS endpoint by posting a JSON payload and playing the returned WAV file. These quick checks prove the core functionality works and give you a solid base to add features like logging, authentication, or a richer UI.

# Test transcription (replace samples/hello.wav with any short wav file)
curl -X POST -F "audio=@samples/hello.wav" http://127.0.0.1:5000/transcribe | jq .

# Test text‑to‑speech
echo '{"text":"Hello from the demo!"}' > payload.json
curl -X POST -H "Content-Type: application/json" -d @payload.json http://127.0.0.1:5000/speak --output hello.wav
aplay hello.wav  # or any wav player

Verifying the Stack

CheckCommand / Why it matters
SQLite DB file data.db exists with a logs tablels -l data.db && sqlite3 data.db "SELECT count(*) FROM logs;"
/transcribe returns a JSON transcriptcurl -X POST -F 'audio=@samples/hello.wav' http://127.0.0.1:5000/transcribe | jq .
/speak returns a playable wav filecurl -X POST -H 'Content-Type: application/json' -d '{"text":"test"}' http://127.0.0.1:5000/speak --output test.wav && aplay test.wav

You did it!

Your minimal speech‑to‑text / text‑to‑speech service is up and running. Feel free to swap the tiny Whisper model for base for better accuracy, add authentication, or hook it into a chatbot for a full voice agent. Happy hacking!