linux_tts 0.1.0
linux_tts: ^0.1.0 copied to clipboard
Offline, on-device text-to-speech for embedded Linux via spd-say (speech-dispatcher). Pure Dart, no Flutter dependency. Degrades silently when the speech backend is absent.
linux_tts #
Offline, on-device text-to-speech for embedded Linux, in pure Dart with no Flutter dependency.
linux_tts wraps spd-say (the speech-dispatcher command-line
client) behind a small TtsEngine abstraction. Speech synthesis happens
entirely on-device through speech-dispatcher's local backend (e.g. eSpeak-NG),
so it keeps working when there is no network and no GPS — the scenario it
was built for: a driver in unexpected snow when the screen can no longer be
relied on and the audio channel is the one that survives.
Advisory only. This package announces; it does not actuate. Do not wire it as a safety-actuator or treat its output as a control-class signal.
Provenance #
Extracted from the voice_guidance package of
SNGNav (aki1770-del), where the same
LinuxTtsEngine drives in-vehicle voice guidance in production. This is an
extraction of proven, dogfooded code, not a greenfield rewrite.
The source voice_guidance package is BSD-3-Clause (copyright "Snow Guard Nav
Contributors"); linux_tts is published under MIT. Both the SNGNav source and
this extraction are authored solely by Akihiko Komada (aki1770-del), who as sole
copyright holder relicenses this extracted subset under MIT.
Prior art in the same niche: dart_speechd
(the-byte-bender, MPL-2.0) provides lower-level Dart bindings to speech-dispatcher.
linux_tts does not depend on or copy it — it ships SNGNav's own subprocess-based
engine — but acknowledges it as fellow-weaver prior work.
Install #
dependencies:
linux_tts: ^0.1.0
Native dependency #
pub.flutter-io.cn ships Dart code, not native binaries. linux_tts shells out to
spd-say, so the target must have speech-dispatcher installed and its
daemon reachable, plus a synthesis backend (eSpeak-NG is the common default).
Debian / Ubuntu:
sudo apt install speech-dispatcher espeak-ng
Fedora:
sudo dnf install speech-dispatcher espeak-ng
Yocto / Buildroot (embedded images): add the speechd (speech-dispatcher)
and espeak-ng recipes/packages to the image. Ensure the daemon can start
under the image's init system; on locked-down or read-only-rootfs images verify
speech-dispatcher's socket/runtime-dir assumptions hold.
If spd-say is absent the engine degrades silently (speak/stop become
no-ops) — a missing daemon never crashes the consuming UI. Callers that
require speech should detect capability up front (below).
Usage #
import 'package:linux_tts/linux_tts.dart';
Future<void> main() async {
final TtsEngine engine = LinuxTtsEngine();
// Capability detection — never assume the backend is present.
if (!await engine.isAvailable()) {
// Degrade gracefully, or fail loudly if speech is required:
// (engine as LinuxTtsEngine).ensureAvailable() throws
// TtsUnavailableException with remediation text.
return;
}
await engine.setLanguage('ja-JP');
await engine.setVolume(0.8);
await engine.setSpeechRate(0.9); // slightly slower for an older driver
await engine.speak('まもなく右折です。');
// Cancel a stale prompt the instant conditions change:
await engine.stop();
await engine.dispose();
}
Failing loudly when speech is required #
For a life-critical audio channel (e.g. a vision-impaired driver), silent
degradation is not acceptable. Use ensureAvailable():
final engine = LinuxTtsEngine();
try {
await engine.ensureAvailable(); // throws TtsUnavailableException if absent
} on TtsUnavailableException catch (e) {
// e.message carries remediation guidance (apt install ...).
}
Engines #
| Engine | Platform | Behavior |
|---|---|---|
LinuxTtsEngine |
Linux (dart:io) |
Speaks via spd-say; degrades to silent if absent. |
NoOpTtsEngine |
Any | Records spokenTexts; produces no audio. Test double. |
LinuxTtsEngine |
Non-dart:io (e.g. web) |
Conditional export resolves to a silent NoOp. |
Japanese #
spd-say with the eSpeak-NG backend supports Japanese (setLanguage('ja-JP'),
normalized internally to ja). On a machine with speech-dispatcher + eSpeak-NG,
spd-say -L lists Japanese voices, so capability is real.
Quality caveat: eSpeak-NG Japanese is a formant/dictionary synthesizer — intelligible but robotic, and kanji-heavy road names (県道/国道, place names) are NEEDS-VERIFICATION by listening test before any "production Japanese" claim. speech-dispatcher is backend-swappable: an integrator can route to a higher-quality module (e.g. Open JTalk) without changing this package's API.
Testing without a daemon #
The engine's Process.run / Process.start / executable-resolver hooks are
injectable, so the unit suite runs in CI with no speech-dispatcher daemon:
final engine = LinuxTtsEngine(
resolveExecutable: (_) => '/usr/bin/spd-say',
startProcess: (exe, args) async { /* record + return a fake Process */ },
runProcess: (exe, args) async => ProcessResult(0, 0, '', ''),
);
See test/ for the full pattern.
License #
MIT. The native dependencies it talks to (speech-dispatcher libspeechd
LGPL-2.1+, the speechd daemon, eSpeak-NG GPL-3+) are installed by the
integrator as separate system components — this Dart package does not link or
relicense them, it shells out to spd-say across a process boundary.