revolution/laravel-voicevox
Laravel wrapper for the VOICEVOX text-to-speech engine. Provides a simple client and configuration to synthesize Japanese speech, query speakers and styles, and integrate voice generation into Laravel applications via the VOICEVOX HTTP API.
invokable/laravel-voicevox wraps VOICEVOX Core — a Japanese text-to-speech engine — behind a Laravel facade, so you can synthesize speech in-process (FFI) instead of running the VOICEVOX ENGINE HTTP server. As of 0.2.0 the bundled/expected core is v0.17.0.
Minimal path to first audio:
composer require invokable/laravel-voicevoxphp artisan vendor:publish --tag="voicevox-config") and read config/voicevox.php first — it is where the shared library path, ONNX Runtime path, and model (.vvm) directory are declared. Almost every failure traces back to these three paths.voicevox_core shared library, ONNX Runtime, and at least one voice model file. These are not Composer dependencies and are not shipped by the package.ffi.enable=true in the relevant php.ini (CLI and FPM — they are different files).php artisan tinker: resolve the facade, list available speakers/styles, then synthesize a short string to a WAV.First real use case: an Artisan command that takes a string plus a style/speaker identifier and writes a WAV to a storage disk. Keep it a command rather than a controller — model loading is slow and you do not want it on a web request the first time you test.
Wrap the facade in your own service. Do not scatter Voicevox:: calls through controllers. A thin SpeechSynthesizer class that owns style selection, parameter presets, output formatting, and error handling gives you one place to swap engines and one place to mock in tests.
Treat synthesis as a two-stage operation. VOICEVOX conceptually works as audio query (accent phrases, pitch, speed, intonation — the JSON-ish intermediate representation) then synthesis (query + style → waveform). Use the two-stage form whenever you need to tweak speedScale, pitchScale, intonationScale, volumeScale, or pause length; the one-shot convenience call is for prototyping only.
Push generation to a queue. A job that takes (text, styleId, params, diskPath) and dispatches on a voicevox queue is the standard production shape. Long input blocks a worker for seconds; on a web request you will hit max_execution_time before you hit any package limit. Return the stored path, not raw bytes.
Cache aggressively by content hash. Key on sha256(text + styleId + serialized params + coreVersion). TTS output is fully deterministic for a given core/model version, and re-synthesizing identical UI strings is pure waste. Put the files on a disk with a public URL so the frontend can just <audio src>.
Build a small style registry. Speaker/style IDs are magic integers. Back them with an enum or config array (narration, assistant, chime) so a core upgrade that renumbers IDs is a one-file change rather than a grep across the codebase.
Use the user dictionary for domain vocabulary. Product names, acronyms, and proper nouns will be mispronounced with default accents. Registering readings once at boot (or in a seeder) beats string-hacking the input text with phonetic kana.
Batch and stream where it fits. For long articles, split on 。/!/? and newlines, synthesize per sentence, and concatenate — this also gives you per-sentence caching and lets you stream playback while later chunks render. For HTTP delivery, respond with audio/wav and Content-Disposition; if you need seekable output, convert to MP3/AAC in a post-step (the package gives you WAV).
Integration tips: keep a php artisan voicevox:health command that verifies library load, model discovery, and a zero-length round trip — run it in CI and in your container entrypoint. In tests, bind a fake synthesizer to the container so the suite never touches FFI.
ffi.enable=preload. Check with php -i | grep ffi in the exact SAPI that runs your app. This is the number-one reason the package "doesn't work."voicevox_core shared library, the ONNX Runtime build, and the .vvm model files must all be compatible with that core version. Mixing an older .so with a newer model produces opaque load errors at construction time, not at call time — so surface the exception with the resolved paths in the message.memory_limit accordingly and never load multiple model sets casually.php -i show FFI enabled, (2) does the config path resolve to an existing file (realpath), (3) does a known-good model load, (4) does a one-character string synthesize. Only then look at your own parameters.How can I help you explore Laravel packages today?