crazy-goat / piper-php
Text-to-speech in PHP using Piper via FFI
Requires
- php: >=8.2
- ext-ffi: *
Requires (Dev)
- phpstan/phpstan: 2.1.45
- phpunit/phpunit: 10.5.63
- rector/rector: 2.3.9
- slevomat/coding-standard: 8.22.1
- squizlabs/php_codesniffer: 3.13.5
README
Text-to-speech in PHP using Piper via FFI.
Fast, local text-to-speech synthesis without external services. Piper runs entirely on your machine using ONNX Runtime.
Features
- 🚀 Fast - Local synthesis, no network latency
- 🔊 High quality - Uses Piper neural TTS models
- 📦 Easy installation - Composer package with FFI
- 🎯 Simple API - Load model once, synthesize many times
- âš¡ Warm-up support - Avoid first-chunk delay in production
- 🔄 Streaming - Generate audio chunk by chunk for real-time playback
Requirements
- PHP >= 8.2 with FFI extension enabled
- Piper library (libpiper.so) and ONNX Runtime
- Voice models (.onnx files)
Installation
composer require crazy-goat/piper-php
# Download pre-built libraries
vendor/bin/piper-tts install-deps
This will download and extract to vendor/crazy-goat/piper-php/libs/:
libpiper.so- Piper librarylibonnxruntime.so- ONNX Runtimeespeak-ng-data/- Phoneme data
Manual Download
If you prefer to download manually or need a specific version:
# Download individual libraries wget https://github.com/crazy-goat/piper-php/releases/latest/download/libpiper-linux-x86_64.tar.gz wget https://github.com/crazy-goat/piper-php/releases/latest/download/libonnxruntime-linux-x86_64.tar.gz wget https://github.com/crazy-goat/piper-php/releases/latest/download/espeak-ng-data.tar.gz # Extract tar -xzf libpiper-linux-x86_64.tar.gz tar -xzf libonnxruntime-linux-x86_64.tar.gz tar -xzf espeak-ng-data.tar.gz
Building from Source
If you prefer to build from source or need a different architecture:
# Clone with submodules git clone --recursive https://github.com/crazy-goat/piper-php.git cd piper-php # Or initialize submodules if already cloned git submodule update --init --recursive # Build libpiper and copy to libs/ (like composer does) make build-libs # Or build everything including espeakbridge make build-piper1
The build will create:
libs/libpiper.so- Piper library (copied to project root)libs/libonnxruntime.so- ONNX Runtime (with all versioned files)libs/espeak-ng-data/- Phoneme data
Running Examples Locally
If you're developing locally (not via composer), build libraries and run examples:
# Build libraries and copy to libs/ directory make examples # Download a voice model ./bin/piper-tts download en_US-lessac-medium ./models # Run example php examples/speak.php "Hello world"
This is equivalent to running vendor/bin/piper-tts install-deps for composer users.
Downloading Voice Models
Use the included CLI tool to download voices from HuggingFace:
# List available voices ./bin/piper-tts list # Filter by language ./bin/piper-tts list --language=pl # Download a voice ./bin/piper-tts download en_US-lessac-medium ./models # List installed voices ./bin/piper-tts installed ./models
Or download manually from HuggingFace.
Quick Start
<?php require_once 'vendor/autoload.php'; use CrazyGoat\PiperTTS\PiperTTS; // Libraries are auto-detected from vendor/crazy-goat/piper-php/libs/ // (run: vendor/bin/piper-tts install-deps to download) $piper = new PiperTTS(modelsPath: __DIR__ . '/models'); // Load voice with automatic warm-up (recommended for production) $model = $piper->loadModel('en_US-lessac-medium', warmUp: true); // Synthesize text to WAV $wav = $model->speak('Hello! This is Piper text to speech in PHP.'); file_put_contents('output.wav', $wav);
Custom Library Paths (Optional)
If you need to use custom library locations:
$piper = new PiperTTS( modelsPath: __DIR__ . '/models', libpiperPath: '/custom/path/libpiper.so', // optional onnxrtPath: '/custom/path/libonnxruntime.so', // optional espeakDataPath: '/custom/path/espeak-ng-data', // optional );
API Reference
PiperTTS
Main factory class for loading models.
$piper = new PiperTTS( modelsPath: '/path/to/models', // Directory with .onnx files libpiperPath: '/path/to/libpiper.so', // Optional: auto-detected if null onnxrtPath: '/path/to/libonnxruntime.so', // Optional: auto-detected if null espeakDataPath: '/path/to/espeak-ng-data', // Optional: auto-detected if null );
Methods
loadModel(string $voice, bool $warmUp = false): LoadedModel- Load a voice modelvoices(): VoiceInfo[]- List available voices in models directory
LoadedModel
Represents a loaded voice model. Reuse this instance for multiple synthesis calls.
Methods
speak(string $text, float $speed = 1.0, int $speakerId = 0): string- Synthesize to WAVspeakStreaming(string $text, float $speed = 1.0, int $speakerId = 0): \Generator- Stream chunkswarmUp(): int- Warm up the model, returns time in millisecondsfree(): void- Explicitly free resources (called automatically in destructor)
Speed Control
Adjust speech speed with the speed parameter:
// 2x faster $fast = $model->speak('Hello world', speed: 2.0); // 0.5x slower (half speed) $slow = $model->speak('Hello world', speed: 0.5);
Streaming
For real-time applications, use streaming to get audio chunks as they're generated:
foreach ($model->speakStreaming('First sentence. Second sentence.') as $chunk) { // $chunk->pcmData - Raw 16-bit PCM audio // $chunk->sampleRate - Sample rate (e.g., 22050) // $chunk->isLast - True if this is the final chunk // Send to audio player, WebSocket, etc. $player->play($chunk->pcmData); }
Production Tips
Warm-up
The first inference in ONNX Runtime is slow (~900ms) due to initialization. Use warm-up to avoid this delay:
// Option 1: Auto warm-up on load (recommended) $model = $piper->loadModel('voice', warmUp: true); // Option 2: Manual warm-up with timing $model = $piper->loadModel('voice'); $ms = $model->warmUp(); echo "Warmed up in {$ms}ms";
Reuse Models
Load the model once and reuse for multiple synthesis calls:
// Good: Load once, use many times $model = $piper->loadModel('en_US-lessac-medium', warmUp: true); foreach ($texts as $text) { $wav = $model->speak($text); // ... } // Bad: Loading model on every request (slow!) foreach ($texts as $text) { $model = $piper->loadModel('en_US-lessac-medium'); // Don't do this $wav = $model->speak($text); }
Long-Running Processes
For daemons or workers, load models at startup:
// At application startup $piper = new PiperTTS('/path/to/models'); $voiceCache = []; function getVoice($piper, $voiceKey) { global $voiceCache; if (!isset($voiceCache[$voiceKey])) { $voiceCache[$voiceKey] = $piper->loadModel($voiceKey, warmUp: true); } return $voiceCache[$voiceKey]; } // In request handler $model = getVoice($piper, 'en_US-lessac-medium'); $wav = $model->speak($userText);
Examples
See the examples/ directory:
speak.php- Basic text-to-speech with timingstream.php- Streaming synthesis with per-chunk timingwarmup.php- Manual warm-up demonstrationautowarmup.php- Automatic warm-up on model load
Run examples:
php examples/speak.php "Hello world" php examples/stream.php "First sentence. Second sentence." php examples/autowarmup.php "Test text"
Performance
Typical performance on modern CPU:
| Operation | Time |
|---|---|
| Model loading | ~900ms |
| Warm-up | ~70ms |
| Synthesis (after warm-up) | ~70-150ms per sentence |
| First chunk (without warm-up) | ~900ms |
| First chunk (with warm-up) | ~70ms |
Troubleshooting
FFI extension not found
Enable FFI in your php.ini:
extension=ffi
Library not found errors
Specify full paths to libraries in the PiperTTS constructor:
$piper = new PiperTTS( modelsPath: '/full/path/to/models', libpiperPath: '/full/path/to/libpiper.so', onnxrtPath: '/full/path/to/libonnxruntime.so', espeakDataPath: '/full/path/to/espeak-ng-data', );
ONNX Runtime errors
Make sure all library dependencies are available:
# Check library dependencies
ldd /path/to/libonnxruntime.so
ldd /path/to/libpiper.so
License
MIT License - see LICENSE file.
Credits
- Piper - Fast, local neural text-to-speech
- ONNX Runtime - Cross-platform ML inference
- espeak-ng - Text-to-phoneme conversion
Contributing
Contributions welcome! Please submit issues and pull requests on GitHub.