Search by

vuthaihoc / laravel-tts-client

vuthaihoc

Laravel client for the Piper TTS API (laravel-tts-api): signs temporary tokens locally, synthesizes and streams speech.

Package info

github.com/vuthaihoc/laravel-tts-client

pkg:composer/vuthaihoc/laravel-tts-client

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v0.2.0 2026-10-20 03:04 UTC

This package is auto-updated.

Last update: 2026-10-20 03:08:29 UTC


README

Client Laravel cho laravel-tts-api, dịch vụ chuyển văn bản thành giọng nói chạy Piper.

  • Token tạm được ký ngay trong app bằng client secret, không phải gọi lên server để xin token. Token được dùng lại cho tới khi gần hết hạn.
  • Tts::synthesize() trả về một file WAV hoàn chỉnh. Tts::stream() trả audio theo từng câu, có thể chuyển tiếp thẳng xuống trình duyệt.
  • Tts::url() / Tts::token() cấp URL hoặc token cho frontend tự gọi server, để audio không phải đi qua app.
  • Hỗ trợ Laravel 11–13, PHP 8.2+.

Cài đặt

composer require vuthaihoc/laravel-tts-client
php artisan vendor:publish --tag=tts-config   # không bắt buộc

.env:

TTS_BASE_URL=https://tts.example.com
TTS_CLIENT_ID=acme.k3f9
TTS_CLIENT_SECRET=...        # do admin server cấp bằng `php artisan tts:client acme`
# TTS_VOICE=vi_VN-csa-voice-piper-v3-medium   # voice mặc định, bỏ trống thì server tự chọn
# TTS_TOKEN_TTL=300          # không được vượt giới hạn của server (mặc định 300)
# TTS_TIMEOUT=120

TTS_CLIENT_SECRET chỉ nằm ở backend: ai có nó đều ký được token. Trình duyệt chỉ nhận token đã ký sẵn hoặc URL có gắn token.

Sử dụng

use Vuthaihoc\TtsClient\Facades\Tts;

// Danh sách voice và speaker (cache 5 phút)
$voices = Tts::voices();

// Một file WAV
$audio = Tts::synthesize('Xin chào!', voice: 'vi_VN-csa-voice-piper-v3-medium', speaker: 'yen_nhi');
$audio->content;     // bytes WAV
$audio->duration;    // giây
$audio->save('tts/hello.wav', 's3');

// Trả thẳng từ controller
Route::get('/hello.wav', fn () => Tts::synthesize('Xin chào!'));

Tuỳ chỉnh âm thanh

synthesize(), stream() và url() nhận thêm các named argument sau. Để null (mặc định) thì lấy giá trị trong config; config cũng không đặt thì server dùng mặc định của nó:

Tham số Server mặc định Ý nghĩa
normalize true Đưa đỉnh của mỗi câu về mức tối đa rồi mới nhân volume
volume 1.0 0.1–2
sentenceSilence 0.2 Giây lặng giữa các câu (0–3)
noiseScale theo voice (~0.667) Độ biến thiên của giọng; thấp hơn thì đều hơn nhưng phẳng hơn
noiseW theo voice (~0.8) Độ biến thiên nhịp; noiseScale: 0, noiseW: 0 cho kết quả lặp lại y hệt
Tts::synthesize('Xin chào!', volume: 0.8, sentenceSilence: 0.4);
Tts::stream($text, format: 'pcm', noiseScale: 0.5, noiseW: 0.6);

Đặt mặc định cho cả app trong .env: TTS_NORMALIZE, TTS_VOLUME, TTS_SENTENCE_SILENCE, TTS_NOISE_SCALE, TTS_NOISE_W.

IPA

Phần nằm giữa [[ ... ]] được đọc đúng theo âm vị IPA, dùng để sửa cách đọc tên riêng, từ viết tắt, hoặc chọn cách đọc Anh-Anh hay Anh-Mỹ cho một từ:

Tts::synthesize('I like [[təmˈɑːtəʊ]] sauce.', voice: 'en_US-amy-medium');
Tts::synthesize('Chào mừng quý khách đến [[tˈe-2ɲ fˈoɜ]] Hồ Chí Minh.');

Ký hiệu phải theo đúng quy ước của espeak-ng cho voice đó. Lấy IPA mẫu trên server bằng php artisan piper:ipa "thành phố" --voice=.... Cần server có libpiper build từ scripts/build-libpiper.sh (image Docker đã có sẵn), xem docs/api.md của server.

Lấy phiên âm (IPA)

Tts::ipa() gọi /api/tts/ipa: đưa vào một từ, một câu hoặc nhiều câu, nhận lại IPA theo từng câu.

$ipa = Tts::ipa('either', 'en-GB');
$ipa->cambridge();                                // "/ˈaɪ.ðə/"
array_column($ipa->alternatives(), 'cambridge');  // ["/ˈaɪ.ðə/", "/ˈiː.ðə/"] (chỉ có với từ lẻ)
$ipa->ipa();                                      // "ˈaɪðɐ", dán được vào [[ ]]

$ipa = Tts::ipa('The vegetable recipe.', 'en-US', engine: 'dictionary');
$ipa->cambridge();                                // "/ðə ˈvedʒ.tə.bəl ˈres.ə.pi/"
$ipa->words();                                    // từng từ: nguồn (cmudict/britfone/espeak) và mọi cách đọc
$ipa->sentences;                                  // dữ liệu gốc theo từng câu
  • language: en-GB, en-US hoặc vi-VN. Để trống thì lấy TTS_IPA_LANGUAGE (mặc định en-US).
  • engine: 'espeak' (mặc định) cho IPA đúng như voice sẽ đọc. engine: 'dictionary' tra từng từ trong CMUdict (Anh-Mỹ) hoặc Britfone (Anh-Anh), từ nào không có thì dùng espeak; chỉ dùng được với tiếng Anh, vi-VN sẽ ném TtsValidationException.
  • Bản ghi kiểu Cambridge (cambridge()) là null với tiếng Việt.

Stream

// Chuyển tiếp xuống trình duyệt theo từng câu
Route::get('/speak', fn (Request $r) => Tts::stream($r->input('text'), voice: 'en_US-amy-medium'));

// Tự đọc từng chunk
foreach (Tts::stream($text, format: 'pcm') as $chunk) {
    // s16le mono, Tts::stream(...)->sampleRate Hz
}

// NDJSON: mỗi câu một mảng ['i', 'sample_rate', 'duration', 'last', 'pcm' => base64]
foreach (Tts::stream($text, format: 'ndjson')->sentences() as $sentence) {
    // ...
}

Lỗi (voice sai, token bị từ chối, ...) được ném ra ngay khi gọi stream(), trước khi gửi byte audio nào xuống trình duyệt. Việc chuyển tiếp dùng echo + flush, nên stream được trên PHP-FPM và Octane/Swoole. Riêng Octane/RoadRunner sẽ gom hết rồi mới gửi.

Để frontend gọi server trực tiếp

{{-- URL dùng được trong token_ttl giây (mặc định 5 phút) --}}
<audio controls src="{{ Tts::url('Xin chào các bạn.', speaker: 'quang_huy') }}"></audio>
// Hoặc trả token cho JavaScript, để JS tự gọi fetch(`${base}/api/tts/stream`, { headers: { Authorization: `Bearer ${token}` } })
return ['token' => Tts::token(), 'expires_at' => Tts::tokenExpiresAt()];

Xử lý lỗi

Mọi lỗi đều kế thừa Vuthaihoc\TtsClient\Exceptions\TtsException (getCode() là HTTP status, ->response là response gốc):

Exception Khi nào
TtsAuthenticationException 401: sai client secret, server đã đổi TTS_SECRET, đồng hồ lệch quá 60 s, TTS_TOKEN_TTL lớn hơn giới hạn của server
TtsValidationException 422: ->errors() trả lỗi theo từng trường (text quá dài, voice hoặc speaker không tồn tại, ...)
TtsRateLimitException 429: ->retryAfter() là số giây phải chờ
TtsConnectionException Không kết nối được server hoặc quá TTS_TIMEOUT
TtsConfigurationException Thiếu TTS_BASE_URL, TTS_CLIENT_ID hoặc TTS_CLIENT_SECRET

Test

composer test                                  # unit test, dùng Http::fake
TTS_LIVE_BASE_URL=http://127.0.0.1:8088 TTS_LIVE_CLIENT_ID=... TTS_LIVE_CLIENT_SECRET=... \
  vendor/bin/phpunit --testsuite Live          # chạy với server thật

Trong app của bạn, chỉ cần Http::fake(['*/api/tts*' => Http::response('RIFF...')]) là gọi được Tts mà không cần server.

License

MIT