Module Speech-to-Text on-device pour les applications Files Tech.
Wrapper Flutter de whisper.cpp conçu pour l'écosystème Files Tech : transcription vocale 100 % locale, sans serveur, avec contrôle strict de l'intégrité du modèle (SHA-256) et du cycle de vie des données audio.
Utilisé par :
- Notes Tech — dictée vocale dans l'éditeur de notes (v0.6+)
- AI Tech — voice input pour le chat IA (planifié v0.5+)
- 🔒 100 % offline — aucun appel réseau au runtime ; le modèle est importé manuellement par l'utilisateur (cf. flux SAF dans Notes Tech)
- 🛡️ Intégrité cryptographique — SHA-256 vérifié au téléchargement et avant chaque chargement natif (anti-MITM, anti-substitution disque)
- 🗑️ Aucune persistance audio — le WAV de capture vit dans le tmp dir le temps de la transcription puis est supprimé (couvert dans tous les chemins : succès, annulation, erreur, kill)
- 🚫 Pas de FFmpeg embarqué — basé sur
whisper_ggml_plusqui sort la dépendance ffmpeg dans un companion optionnel non installé. Compatible Android 16 / API 36 (les autres wrappers cassent auJNI_OnLoad) - ✍️ API stable et minimale — interface
SpeechToTextswappable, sealed exception hierarchy, pas de fuite des types internes du wrapper bas-niveau
lib/
files_tech_voice.dart # barrel d'exports publics
src/
speech_to_text.dart # interface SpeechToText + SttTranscription + SttSegment
whisper_ggml_stt.dart # impl WhisperGgmlStt (singleton .instance)
stt_model.dart # SttModel (validation regex sur l'id)
stt_model_catalog.dart # whisper-base-q5_1 + whisper-tiny-q5_1
stt_model_downloader.dart # HTTP streaming + SHA-256 + tmp/rename atomique
stt_model_importer.dart # import SAF (apps sans permission INTERNET)
stt_session.dart # capture micro + transcribe (state machine)
stt_exceptions.dart # sealed SttException + 7 sous-types
| Type | Rôle |
|---|---|
SpeechToText |
Interface stable du moteur (initialize / transcribeFile / dispose) |
WhisperGgmlStt.instance |
Implémentation whisper_ggml_plus |
SttSession |
Capture micro WAV 16 kHz mono + orchestration transcription |
SttModel, SttModelCatalog |
Catalogue de modèles avec hashes SHA-256 attendus |
SttModelImporter.instance |
Import SAF avec vérif SHA-256 (apps offline-only) |
SttModelDownloader.instance |
Download HTTP avec SHA-256 (apps avec INTERNET) |
SttException (sealed) |
Hiérarchie d'erreurs typées : missing model, checksum mismatch, permission, recording, transcription, download |
import 'package:files_tech_voice/files_tech_voice.dart';
// 1. Choisir un modèle dans le catalogue
final model = SttModelCatalog.defaultModel; // whisper-base-q5_1, ~57 Mo
// 2a. Si l'app a la permission INTERNET : download direct
await SttModelDownloader.instance.ensureInstalled(
model,
onProgress: (p) => print('${(p.fraction * 100).toStringAsFixed(0)}%'),
);
// 2b. Sinon (apps "100% offline" comme Notes Tech) : import SAF
// L'utilisateur télécharge le .bin depuis HuggingFace,
// puis sélectionne le fichier via FilePicker.
final pickedPath = '/storage/.../ggml-base-q5_1.bin';
await SttModelImporter.instance.importFromPath(
pickedPath,
model: model,
onProgress: (p) => print('${p.phase} ${(p.fraction * 100).toStringAsFixed(0)}%'),
);
// 3. Initialiser le moteur (lazy-friendly, idempotent)
final stt = WhisperGgmlStt.instance;
await stt.initialize(model);
// 4. Capturer + transcrire
final session = SttSession(stt: stt);
await session.start();
// ... utilisateur parle ...
final result = await session.stopAndTranscribe(language: 'fr');
print(result.text);
// 5. Nettoyage
await session.dispose();| Aspect | Garantie |
|---|---|
| Intégrité du modèle | SHA-256 strict comparé à une constante hardcodée dans SttModelCatalog ; mismatch → fichier supprimé + SttModelChecksumMismatch |
| Atomicité du download | Écriture dans <id>.bin.tmp puis rename POSIX ; jamais de .bin partiel |
| Stockage privé | Modèle dans getApplicationSupportDirectory()/stt/<id>.bin, inaccessible aux autres apps |
| Audio capturé | getTemporaryDirectory()/stt_capture_<ts>.wav, supprimé après transcribe (try/finally couvre tous les chemins) |
| Mode panique | SttModelDownloader.uninstallAll() + purgeTempCaptures() — efface modèles + WAV orphelins |
| Permission RECORD_AUDIO | Demandée runtime, distinction denied / permanentlyDenied exposée via SttPermissionDenied.permanently |
| Path traversal | SttModel.fileName valide regex ^[a-z0-9_-]+$ |
| ID | Taille | Recommandation |
|---|---|---|
whisper-base-q5_1 |
~57 Mo | Défaut. Bonne qualité française, ~3 s pour 5 s d'audio sur milieu de gamme |
whisper-tiny-q5_1 |
~32 Mo | Plus rapide, qualité française approximative. Pour téléphones bas de gamme |
Source officielle des .bin :
huggingface.co/ggerganov/whisper.cpp
Le catalogue est juste une commodité pour l'UI standard — n'importe quel
SttModel valide construit à la main peut être passé à
SttModelDownloader.ensureInstalled ou SttModelImporter.importFromPath.
Apache License 2.0 — voir LICENSE et NOTICE.
Dépendances tierces et leurs licences : voir NOTICE.