diff --git a/stacks/chatterbox-fast/app.py b/stacks/chatterbox-fast/app.py index 91f79d4..758456b 100644 --- a/stacks/chatterbox-fast/app.py +++ b/stacks/chatterbox-fast/app.py @@ -217,12 +217,15 @@ def _pcm16(wav: torch.Tensor) -> bytes: return (a * 32767.0).astype(" bytes: - """Streaming WAV header with unknown length (0xFFFFFFFF sizes).""" +def _wav_header(sr: int, data_len: int | None = None) -> bytes: + """WAV header. data_len=None → streaming (length unknown, 0xFFFFFFFF sizes, + player reads to EOF); an int → correct RIFF/data sizes for a complete file.""" + data_size = 0xFFFFFFFF if data_len is None else data_len + riff_size = 0xFFFFFFFF if data_len is None else 36 + data_len return ( - b"RIFF" + struct.pack(" StreamingResponse: # workload); concurrent callers queue rather than corrupt conditionals. with engine.lock: engine._prepare(voice_path, exaggeration=req.exaggeration) - if req.format == "wav": - yield _wav_header(engine.sr) - t_req = time.perf_counter() first_audio_ms: float | None = None if not req.stream: + # One-shot: the full length is known, so emit a correct-sized WAV + # header (a buffered consumer wants well-formed sizes). wav, audio_sec = engine.generate(req.text, req) first_audio_ms = (time.perf_counter() - t_req) * 1000 log.info("oneshot: %.0fms gen, %.2fs audio", first_audio_ms, audio_sec) - yield _pcm16(wav) + pcm = _pcm16(wav) + if req.format == "wav": + yield _wav_header(engine.sr, len(pcm)) + yield pcm return + # Streaming: length is unknown up front → open-ended WAV header. + if req.format == "wav": + yield _wav_header(engine.sr) + def _gen(text: str) -> tuple[torch.Tensor, float]: return engine.generate(text, req)