Construire un outil de transcription audio web

mai 2026 · 9 min de lecture

Tester l'outil en live

Avant de plonger dans le guide technique, vous pouvez tester directement l'outil qui a inspiré ce guide :

transcript.nathbuild.com

C'est un transcripteur audio gratuit construit avec exactement la stack décrite ici. Il permet de :

  • Coller un lien TikTok, Twitter/X ou Facebook et récupérer la transcription en quelques secondes
  • Uploader un fichier audio ou vidéo (MP3, MP4, WAV, OGG, WebM, M4A, FLAC — jusqu'à 25 Mo) pour toute autre source : YouTube, Instagram, podcast, réunion enregistrée, voice memo…

Limites : 3 transcriptions gratuites par IP par jour (protection anti-abus).

Contact / questions : natblavo@ceepia.com

Ce projet est open à l'échange — si vous avez des questions sur l'implémentation après avoir lu ce guide, n'hésitez pas.


Ce guide explique comment construire un outil web qui convertit l'audio de vidéos (ou de fichiers uploadés) en texte, et le déployer publiquement. Il couvre les grandes lignes — architecture, choix techniques, déploiement, sécurité — avec des liens vers les ressources utiles.


Vue d'ensemble

L'idée est simple : l'utilisateur colle un lien ou uploade un fichier → le serveur extrait l'audio → une API de transcription renvoie le texte.

Utilisateur
    │
    ▼
Interface web (HTML/JS)
    │  POST /transcribe (URL ou fichier)
    ▼
Backend Python (FastAPI)
    ├── yt-dlp → télécharge l'audio depuis l'URL
    └── API de transcription (Voxtral, Whisper, Azure…)
            │
            ▼
        Texte renvoyé à l'utilisateur

Stack recommandée

Backend

FastAPI — framework Python moderne, async natif, idéal pour des APIs légères. Gère à la fois les routes JSON et les uploads de fichiers multipart.

pip install fastapi uvicorn[standard] httpx python-multipart

Extraction audio depuis une URL

yt-dlp — le successeur de youtube-dl. Supporte plusieurs centaines de plateformes (TikTok, Twitter/X, Facebook, Twitch, Reddit…). Nécessite ffmpeg pour la conversion audio.

pip install yt-dlp
apt install ffmpeg  # ou brew install ffmpeg sur Mac

⚠️ Limitation importante : YouTube et Instagram exigent désormais une authentification (cookies d'un compte connecté) pour tout téléchargement automatisé. Pour un outil public, préférez l'upload de fichier plutôt que de dépendre de ces plateformes.

Pour exporter des cookies depuis votre navigateur si nécessaire : voir cette section du wiki yt-dlp.

Transcription

Plusieurs options selon votre budget et vos besoins :

Service Type Avantages Inconvénients
Voxtral (Mistral AI) API cloud Très précis, multilingue, rapide Plan Experiment gratuit (voir note ci-dessous)
OpenAI Whisper Local ou API Open source, gratuit en local Lent sur CPU, GPU recommandé
Groq Whisper API cloud Très rapide, généreux en free tier Moins de langues
Azure Speech API cloud Intégration facile, SLA enterprise Interface complexe
AssemblyAI API cloud Fonctionnalités avancées (chapitres, sentiment…) Payant

💡 Mistral AI propose un plan "Experiment" entièrement gratuit sur AI Studio. Il donne accès à tous leurs modèles frontier (dont Voxtral pour la transcription), à la création et au déploiement d'agents, sans carte bancaire. En contrepartie, les requêtes peuvent être utilisées pour améliorer leurs modèles. C'est le moyen le plus rapide de démarrer sans dépenser un centime.

Pour une intégration rapide avec une API compatible OpenAI (Voxtral, Groq) :

import httpx

async def transcribe(audio_bytes: bytes, filename: str, api_key: str) -> str:
    async with httpx.AsyncClient(timeout=120) as client:
        response = await client.post(
            "https://api.mistral.ai/v1/audio/transcriptions",  # ou votre endpoint
            headers={"Authorization": f"Bearer {api_key}"},
            files={"file": (filename, audio_bytes, "audio/mpeg")},
            data={"model": "voxtral-mini-2507"},
        )
    return response.json()["text"]

Pour faire tourner Whisper en local :

import whisper

model = whisper.load_model("base")  # ou "small", "medium", "large"
result = model.transcribe("audio.mp3")
print(result["text"])

Rate limiting

Pour éviter les abus sur un outil public, limitez les requêtes par IP. Deux approches :

In-memory (simple, sans dépendance)

Adapté pour un serveur unique. Se réinitialise au redémarrage. Stockez un compteur par IP et par jour, incrémentez-le à chaque requête, et renvoyez une erreur HTTP 429 quand la limite est atteinte.

Redis (robuste, multi-instances)

Avec slowapi (wrapper de limits pour FastAPI) :

from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address, storage_uri="redis://localhost:6379")

@app.post("/transcribe")
@limiter.limit("3/day")
async def transcribe(request: Request, ...):
    ...

Bonne pratique : si votre app est derrière un reverse proxy (Nginx, Caddy), configurez le proxy pour injecter l'IP réelle du client dans un header dédié, et lisez ce header côté backend plutôt que de vous fier aux headers bruts de la requête.


Upload de fichier

Pour accepter des fichiers audio/vidéo directement (contourne les restrictions des plateformes) :

from fastapi import File, UploadFile

ALLOWED_MIME = {"audio/mpeg", "audio/mp4", "audio/wav", "audio/ogg",
                "video/mp4", "video/webm", "audio/webm"}
MAX_BYTES = 25 * 1024 * 1024  # 25 Mo

@app.post("/transcribe/file")
async def transcribe_file(file: UploadFile = File(...)):
    if file.content_type not in ALLOWED_MIME:
        raise HTTPException(415, "Format non supporté.")
    content = await file.read()
    if len(content) > MAX_BYTES:
        raise HTTPException(413, "Fichier trop volumineux.")
    # → envoyer content à votre API de transcription

Côté frontend, avec un drag & drop :

const dropzone = document.getElementById("dropzone");
dropzone.addEventListener("drop", async (e) => {
    e.preventDefault();
    const file = e.dataTransfer.files[0];
    const form = new FormData();
    form.append("file", file);
    const res = await fetch("/transcribe/file", { method: "POST", body: form });
    const { transcript } = await res.json();
});

Containerisation avec Docker

Un Dockerfile minimal pour une app FastAPI + ffmpeg + yt-dlp :

FROM python:3.12-slim

RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Lancer le container en n'exposant le port que sur localhost (le reverse proxy s'en charge) :

docker build -t mon-transcripteur .
docker run -d \
  --name mon-transcripteur \
  --restart unless-stopped \
  -p 127.0.0.1:8010:8000 \
  --env-file .env \
  mon-transcripteur

Déploiement sur un VPS

Choisir un hébergeur

N'importe quel VPS Linux fait l'affaire. Quelques options :

Reverse proxy avec Caddy

Caddy gère automatiquement les certificats TLS (HTTPS) via Let's Encrypt. Zéro configuration manuelle.

Installer Caddy sur Ubuntu :

apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install caddy

Config minimale (/etc/caddy/Caddyfile) :

monsite.com {
    reverse_proxy 127.0.0.1:8010
    encode zstd gzip
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        -Server
    }
}
systemctl enable --now caddy
systemctl reload caddy  # après chaque modification

Caddy obtient le certificat TLS automatiquement dès que le DNS pointe vers votre serveur.


Sécurité — bonnes pratiques

  • Headers HTTP : configurez Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, Content-Security-Policy et Permissions-Policy au niveau du reverse proxy
  • Rate limiting : limitez les requêtes par IP (voir section dédiée)
  • Validation des entrées : vérifiez le content-type et la taille des fichiers uploadés côté serveur, jamais uniquement côté client
  • Clés API : stockez-les dans des variables d'environnement (.env), jamais dans le code source ni dans le dépôt git
  • Fichiers temporaires : supprimez-les immédiatement après traitement — en Python, tempfile.TemporaryDirectory() s'en charge automatiquement
  • Réseau : n'exposez pas directement votre backend sur Internet ; laissez le reverse proxy gérer les connexions entrantes

Ressources

Sujet Lien
Extraction audio yt-dlp
Backend Python FastAPI
Transcription open source OpenAI Whisper
Transcription API rapide Groq (Whisper)
Transcription Mistral Voxtral
Rate limiting FastAPI slowapi
Reverse proxy + TLS auto Caddy
Containerisation Docker
Cookies yt-dlp (si nécessaire) Wiki yt-dlp

Ce guide vient de Build With Nath

La communauté est gratuite. J’y publie ces guides au fil de l’eau et on y répond aux questions.

Autres guides · Guides

Vous dirigez une entreprise et vous voulez savoir lequel de vos process mérite ce genre de traitement ? Le diagnostic vous le dit en trois minutes, ou on en parle trente minutes.