Skip to content

fix(tts): repair placeholder RIFF/data sizes in streamed MiniMax WAV output - #9867

Open
kilisamemarisaaa wants to merge 2 commits into
AstrBotDevs:masterfrom
kilisamemarisaaa:fix/9860
Open

fix(tts): repair placeholder RIFF/data sizes in streamed MiniMax WAV output#9867
kilisamemarisaaa wants to merge 2 commits into
AstrBotDevs:masterfrom
kilisamemarisaaa:fix/9860

Conversation

@kilisamemarisaaa

@kilisamemarisaaa kilisamemarisaaa commented Aug 29, 2026

Copy link
Copy Markdown

Problem

minimax_tts_api hardcodes stream: true and format: "wav", so the WAV bytes come from MiniMax's server-side ffmpeg. A streamed WAV cannot know its total length up front, so ffmpeg writes 0xFFFFFFFF placeholders into both the RIFF size field and the data chunk size field. _audio_play() only concatenates the hex-decoded SSE chunks and get_audio() writes the bytes to disk untouched, so every saved minimax_tts_api_*.wav carries an invalid header.

Browsers tolerate the placeholder sizes, which is why playback looks fine in the WebUI, but strict decoders (Android WebView / system players) reject the file — and nothing appears in AstrBot's logs. Hex dump from the issue:

00000000  52 49 46 46 FF FF FF FF 57 41 56 45 66 6D 74 20   RIFFÿÿÿÿWAVEfmt

Closes #9860

Fix

Add a module-level _patch_streamed_wav_header() and call it in get_audio() on the assembled bytes right before they are written to disk:

  • walks the RIFF chunk list (respecting even-length chunk padding) to locate the data chunk;
  • rewrites the data chunk size and the RIFF size only when the field holds the 0xFFFFFFFF placeholder, so already-valid files are returned byte-for-byte unchanged;
  • bails out conservatively (input returned unchanged) for non-WAVE input, truncated input, or an un-walkable placeholder-sized chunk before data.

Tests

tests/unit/test_minimax_tts_wav_header.py (pytest, no network):

  • placeholder sizes are repaired to the real len(file) - 8 / payload sizes;
  • a fully valid WAV passes through byte-for-byte unchanged;
  • non-WAV / short / truncated input is unchanged;
  • a placeholder-sized chunk before data is not guessed around (conservative bail-out);
  • standard chunks before data (e.g. LIST) are walked correctly;
  • end-to-end: get_audio() with a mocked hex SSE stream writes a file whose repaired header decodes cleanly.

ruff format / ruff check pass; the repository's neo profile suite (scripts/pr_test_env.sh --profile neo) passes except for two pre-existing failures in tests/test_computer_skill_sync.py, which fail identically on a clean master checkout.

Historical context

#7144 / #7795 / #7565 reported the earlier file does not start with RIFF id failures (MP3 payload under a .wav name), fixed by #7797 switching the default output format to WAV. That left the streamed path producing a correctly-named but header-invalid file; this PR closes that remaining gap.

Summary by Sourcery

Repair streamed MiniMax WAV headers before writing audio files so strict decoders can open the generated output.

Bug Fixes:

  • Repair invalid placeholder RIFF and data chunk sizes in streamed MiniMax WAV files before saving them, improving compatibility with strict audio decoders while preserving already-valid files.

Enhancements:

  • Add conservative RIFF chunk walking that handles padded chunks and leaves malformed or unsafe inputs unchanged.

Tests:

  • Add unit and end-to-end coverage for WAV header repair, valid and invalid inputs, chunk traversal, truncation, and streamed audio output.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Aug 29, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/provider/sources/minimax_tts_api_source.py" line_range="46-47" />
<code_context>
+        payload_start = pos + 8
+        if chunk_id == b"data":
+            if declared == WAV_SIZE_PLACEHOLDER:
+                actual = len(patched) - payload_start
+                patched[pos + 4 : pos + 8] = actual.to_bytes(4, "little")
+                data_patched = True
+            break
</code_context>
<issue_to_address>
**issue (bug_risk):** When the `data` chunk is followed by a padding byte or any later RIFF chunk, `actual = len(patched) - payload_start` includes those bytes in the `data` size. The repaired header therefore claims that padding or subsequent chunks are audio data.

**Triggers:** When a WAV has an odd-sized data payload or contains chunks after `data`.

**Suggested fix:** Determine the data payload boundary from the RIFF chunk structure, or conservatively bail out when a placeholder-sized `data` chunk is not the final chunk.
</issue_to_address>

### Comment 2
<location path="astrbot/core/provider/sources/minimax_tts_api_source.py" line_range="56-57" />
<code_context>
+            return audio
+        pos = payload_start + declared + (declared & 1)
+
+    if data_patched and riff_size == WAV_SIZE_PLACEHOLDER:
+        patched[4:8] = (len(patched) - 8).to_bytes(4, "little")
+    return bytes(patched)
+

</code_context>
<issue_to_address>
**issue (bug_risk):** A file with a placeholder RIFF size but an already-valid `data` size is returned with the invalid RIFF placeholder unchanged because the RIFF field is patched only when `data_patched` is true. This violates the stated behavior of repairing each placeholder field independently.

**Triggers:** When the RIFF size is `0xFFFFFFFF` and the `data` chunk size is already valid.

**Suggested fix:** Patch the RIFF size whenever `riff_size == WAV_SIZE_PLACEHOLDER`, independently of whether the data size was patched.

```suggestion
    if riff_size == WAV_SIZE_PLACEHOLDER:
        patched[4:8] = (len(patched) - 8).to_bytes(4, "little")
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and if the chunk walk or size calculation is wrong, the generated WAV can be written with an incorrect header and fail in a strict decoder or include an incorrect data length. The affected audio file can be discarded and regenerated, so reverting the change stops further occurrences but does not repair files already produced.

Blocking findings: astrbot/core/provider/sources/minimax_tts_api_source.py:47, astrbot/core/provider/sources/minimax_tts_api_source.py:57


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/core/provider/sources/minimax_tts_api_source.py
Comment thread astrbot/core/provider/sources/minimax_tts_api_source.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

1 participant