Split text into sentences to stream audio chunk-by-chunk
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m15s
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m15s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
29
server.py
29
server.py
@@ -4,6 +4,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@@ -22,6 +23,8 @@ from wyoming.tts import Synthesize
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
KOKORO_SAMPLE_RATE = 24000
|
KOKORO_SAMPLE_RATE = 24000
|
||||||
|
|
||||||
|
_SENTENCE_RE = re.compile(r'(?<=[.!?])\s+')
|
||||||
SAMPLE_WIDTH = 2 # 16-bit PCM
|
SAMPLE_WIDTH = 2 # 16-bit PCM
|
||||||
CHANNELS = 1
|
CHANNELS = 1
|
||||||
|
|
||||||
@@ -125,16 +128,22 @@ class KokoroEventHandler(AsyncEventHandler):
|
|||||||
chunk_count = 0
|
chunk_count = 0
|
||||||
try:
|
try:
|
||||||
_LOGGER.debug("Pipeline thread started")
|
_LOGGER.debug("Pipeline thread started")
|
||||||
for _, _, audio in self.pipeline(text, voice=voice_name, speed=speed):
|
sentences = [s for s in _SENTENCE_RE.split(text.strip()) if s]
|
||||||
if audio is None:
|
if not sentences:
|
||||||
continue
|
sentences = [text]
|
||||||
# float32 [-1, 1] → int16
|
_LOGGER.debug("Split into %d sentence(s)", len(sentences))
|
||||||
audio_np = audio.cpu().numpy() if hasattr(audio, 'cpu') else audio
|
for sentence in sentences:
|
||||||
pcm = (np.clip(audio_np, -1.0, 1.0) * 32767).astype(np.int16)
|
_LOGGER.debug("Synthesizing: %r", sentence[:60])
|
||||||
chunk_count += 1
|
for _, _, audio in self.pipeline(sentence, voice=voice_name, speed=speed):
|
||||||
_LOGGER.debug("Queueing chunk %d (%d bytes)", chunk_count, len(pcm.tobytes()))
|
if audio is None:
|
||||||
fut = asyncio.run_coroutine_threadsafe(chunk_queue.put(pcm.tobytes()), loop)
|
continue
|
||||||
fut.result() # propagate any queue errors
|
# float32 [-1, 1] → int16
|
||||||
|
audio_np = audio.cpu().numpy() if hasattr(audio, 'cpu') else audio
|
||||||
|
pcm = (np.clip(audio_np, -1.0, 1.0) * 32767).astype(np.int16)
|
||||||
|
chunk_count += 1
|
||||||
|
_LOGGER.debug("Queueing chunk %d (%d bytes)", chunk_count, len(pcm.tobytes()))
|
||||||
|
fut = asyncio.run_coroutine_threadsafe(chunk_queue.put(pcm.tobytes()), loop)
|
||||||
|
fut.result() # propagate any queue errors
|
||||||
_LOGGER.debug("Pipeline finished, %d chunks generated", chunk_count)
|
_LOGGER.debug("Pipeline finished, %d chunks generated", chunk_count)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_LOGGER.exception("Pipeline thread error")
|
_LOGGER.exception("Pipeline thread error")
|
||||||
|
|||||||
Reference in New Issue
Block a user