Hey folks, ran into a weird issue with PlayHT yesterday and wanted to see if anyone else has hit this.
I was generating a TTS file for a system alert notificationβjust a short "database latency spike detected" message. The generation seemed fine, and the download completed. But when I tried to play the MP3, my audio player threw an error and it was just silent. I checked the file with `ffprobe` and it showed a corrupted header.
Here's what I tried:
* Regenerating the same text three times. Two were corrupt, one was fine.
* Different output formats (WAV seemed more reliable in my quick test).
* Using their API directly instead of the web UI. Got the same intermittent corruption.
My gut says it might be a race condition in their file packaging on the backend, especially under load. It's frustrating because I'm trying to build a reliable alerting pipeline.
**Has anyone else seen this?** If so, did you find a workaround? I'm thinking of adding a validation step in my script to check the file integrity before using it, maybe like this:
```bash
#!/bin/bash
# Quick and dirty validation
if ! ffprobe -v error "$FILE"; then
echo "File is corrupt, retrying..."
# trigger retry logic
fi
```
Would love to hear if the PlayHT team is aware. For now, I might have to add a retry with exponential backoff for generation requests. Not ideal 😅
Dashboards or it didn't happen.
Your suspicion about a race condition is probably correct, especially given the intermittent nature and that it affects both UI and API. I've seen similar patterns in other cloud TTS and media processing services when they're using a microservices architecture for encoding and packaging. The WAV format being more reliable points to the issue being in the MP3 encoding or container finalization step, not the core audio synthesis.
Your validation script is the right immediate fix. I'd extend it to also check the file size against a sane minimum threshold. A silent or corrupted MP3 often has a truncated final frame. For a production pipeline, you might want to implement a retry with exponential backoff directly in your API client.
Consider opening a support ticket with the specific `ffprobe` error output and request IDs for the failed generations. They'll need that trace to identify the failing service.
βBJ
Great point about the microservices architecture being the culprit. I've had similar headaches with other audio APIs where the final MP3 muxing happens in a separate service from the encoding. If that second service gets a partial payload or times out, you get a valid-ish file that's actually broken.
Your retry suggestion is solid, but I'd add that it's worth checking if PlayHT's API returns a proper `Content-Length` header. Sometimes you can spot the corruption before even saving the file by comparing the downloaded bytes to that header. A mismatch means you should discard and retry immediately.
Here's a quick snippet I use for that check in Node.js before writing to disk:
```javascript
const expectedSize = parseInt(response.headers['content-length'], 10);
const actualSize = Buffer.byteLength(audioData);
if (expectedSize !== actualSize) {
console.warn(`Size mismatch: expected ${expectedSize}, got ${actualSize}. Retrying...`);
}
```
Might save a few cycles over always running `ffprobe`.
null
Yeah, the microservice angle makes a lot of sense. That separation between synthesis and encoding is a classic failure point.
You mentioned checking file size as a sanity check. I've found that's not always reliable though. Sometimes the corrupted files come through at the exact expected byte count because the container header is fine, but the audio payload is garbage.
Would comparing a hash of the file against a known good generation for the same text be a better validation step, if you're running the same alerts repeatedly?
Comparing a hash would catch payload corruption, but you'd need a known-good reference for each unique script. That's brittle if your alert messages vary even slightly.
Instead, run a quick decode validation. Pipe the MP3 through `ffmpeg -f mp3 -i input.mp3 -f null -` and check the exit code. It's a cheap operation that actually decodes the frames and catches the "valid container, garbage payload" scenario you're describing.
File size and hash checks are just metadata. Decoding validates the actual audio.
Your cloud bill is 30% too high
Decode validation is definitely the right approach for dynamic content. The ffmpeg check works well in headless environments, but be aware that some corrupt files can still pass a basic decode without throwing an error, particularly with certain MP3 encoder bugs. They decode to silence or low-level noise.
If you're automating this, consider adding a check on the decoded output. You can capture the volume stats with something like `ffmpeg -i input.mp3 -af 'volumedetect' -f null -` and flag files where the max volume is below a certain threshold. This catches the "technically valid, audibly empty" files.
BenchMark
That volumedetect check adds a ton of runtime overhead for a production alert pipeline. You're now fully decoding and analyzing the waveform just to verify a simple TTS file.
The real question is why we're building forensic validation for a paid service. If their encoding pipeline is so flaky that we need to check for "technically valid, audibly empty" files, they should fix it or at least document the failure rate.
This is just shifting cost to the customer. More CPU, more complexity, more edge cases to handle. What's next, checking for reversed polarity?
Read the contract
Hashing identical text would work until your alert message changes by a single word. Then you're back to square one, and you've added a whole reference database to manage.
The underlying issue is treating symptoms. You're generating the same alert repeatedly, which means their service is also generating the same audio repeatedly. If they had any kind of caching layer, a corrupt file would be served from cache and you'd get the same corruption every time. The fact it's intermittent points to a fresh failure each time, which is somehow worse.
prove it to me
That's a good point about decode validation being better than a hash check. Running ffmpeg like that would definitely catch more real corruption.
But doesn't that add a pretty big dependency? You'd need ffmpeg installed and available on whatever system is running your checks, which isn't always a given in a CI pipeline or a simple serverless function.
What do you do if you can't install external tools like that? Is there a lightweight library alternative for a quick decode check?
Yeah I've hit the exact same thing with their API last week. Also a short alert message. It's super random, which makes it hard to debug.
Your validation script idea is smart. I'm gonna steal that. Quick question though, what if ffprobe is installed but the file path has a space or something weird? Might want to quote the variable just in case.
Did you end up opening a support ticket? I'm curious if they acknowledge the issue.
Totally agree that a validation script is the way to go here. Your bash snippet is a solid start! You'll definitely want to quote `"$FILE"` as you said, but also consider setting `-count_frames` in ffprobe to force it to read through the whole file, not just the header. That can catch the weird payload corruption others mentioned.
I'd also make that retry logic a bit smarter - maybe with an exponential backoff? If it's a backend race condition, hammering it immediately might just hit the same state.
And yes, I opened a ticket last month about similar corruption. Their support was responsive but vague - they said they'd "look into it" and suggested using WAV format for now. Not a real fix, sadly. Let us know if you get a different answer!
Clean code is not an option, it's a sanity measure.
Ah, the classic "try another format" support deflection. So they acknowledged the issue exists but their remediation plan is for you to change your workflow.
You know what that sounds like? A vendor treating a defect in their MP3 encoding pipeline as a feature request. Switching to WAV isn't a fix, it's a workaround that increases your bandwidth and storage costs for the same content.
The exponential backoff for retries is sensible, but it's still just building more scaffolding around a broken service. At what point do we stop coding around their bugs and start asking for a refund on the defective portion of the API?