Files
audio-engine-hub/app/utils/cache.py
stephan c06fd677dc fix: Resolve 7 critical bugs in Piper TTS engine and add cache versioning
This commit fixes all critical bugs identified in the Piper engine that were
causing server crashes, hung processes, resource leaks, and audio synthesis
failures.

## Bug Fixes

**Bug #1: Invalid --stdin CLI flag**
- Removed non-existent --stdin flag from piper command
- Piper reads stdin by default, flag was causing undefined behavior

**Bug #2: Temp file race conditions**
- Replaced NamedTemporaryFile context manager with tempfile.mkstemp()
- Prevents file handle locking issues on some systems

**Bug #3: Missing process timeouts**
- Added asyncio.wait_for() with 30s timeout for piper synthesis
- Added 60s timeout for ffmpeg conversion
- Prevents hung processes from accumulating and exhausting memory

**Bug #4: Orphaned temp files**
- Implemented comprehensive temp file tracking list
- Added cleanup in finally block to ensure all temp files are removed
- Prevents /tmp/ from filling with orphaned audio files

**Bug #5: FFmpeg error suppression**
- Removed quiet=True from ffmpeg calls
- Added capture_stdout and capture_stderr for full error context
- Improved error messages with actual ffmpeg output

**Bug #6: No config file validation**
- Added upfront validation for .onnx and .onnx.json files
- Validates speaker exists in config before synthesis
- Provides clear error messages with available speakers list

**Bug #7: Poor error context**
- Added comprehensive logging with DEBUG/INFO/ERROR/WARNING levels
- Error messages now include full command, model, speaker, text preview
- Added success logging with file sizes and synthesis details

## New Features

**Cache Versioning System**
- Added CACHE_VERSION constant to automatically invalidate cache on bug fixes
- Old cached files (with bugs) are automatically bypassed
- Includes cleanup_old_cache_files() utility for removing stale cache
- Version history documented in code comments

**Configurable Timeouts**
- PIPER_TIMEOUT_SECONDS: defaults to 30s (configurable via .env)
- FFMPEG_TIMEOUT_SECONDS: defaults to 60s (configurable via .env)

**Path Fixes**
- Fixed audio file path validation in main.py (Bug causing 404s)
- Updated AUDIO_CACHE_DIR to use relative paths for better portability

**Documentation**
- Added CLAUDE.md for future AI assistant context
- Updated .env.example with new timeout configuration options

## Testing

- ✅ Health check passed
- ✅ Synthesis working correctly
- ✅ No temp file leaks
- ✅ No hung processes
- ✅ Cache versioning prevents old buggy files from being served
- ✅ Voice preview in wizard working correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 00:38:00 +01:00

66 lines
1.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
NovaAi – TTS-Engine-Hub
utils/cache.py
Version: v0.0.1
Description:
Caching utilities for TTS requests (cache key generation, etc).
Author: Abby (ChatGPT)
Date: 2025-07-23
Canvas: utils/cache.py
"""
import hashlib
import os
import time
# Cache version - increment this to invalidate all cached files
# This is useful when fixing bugs that affect audio generation
# Version history:
# v1: Initial implementation with --stdin bug
# v2: Fixed Bug #1-7 (removed --stdin, added timeouts, better error handling)
CACHE_VERSION = "v2"
# Für die API: Wichtig! req muss mindestens die Felder .text, .engine, .model, .speaker, .format, .chunking haben.
def build_cache_key(req) -> str:
"""
Build a cache key from all relevant TTS request parameters.
Includes CACHE_VERSION to automatically invalidate cache when bugs are fixed.
"""
data = f"{CACHE_VERSION}|{req.text}|{req.engine}|{req.model}|{req.speaker}|{req.format}|{getattr(req, 'chunking', False)}"
return hashlib.sha256(data.encode()).hexdigest()
def cleanup_old_cache_files(cache_dir: str, max_age_days: int = 7) -> int:
"""
Remove cache files older than max_age_days.
Returns the number of files deleted.
This helps prevent the cache directory from growing indefinitely,
especially after cache version changes that make old files obsolete.
"""
if not os.path.isdir(cache_dir):
return 0
current_time = time.time()
max_age_seconds = max_age_days * 24 * 60 * 60
deleted_count = 0
for filename in os.listdir(cache_dir):
if not filename.startswith('tts_'):
continue
filepath = os.path.join(cache_dir, filename)
try:
file_age = current_time - os.path.getmtime(filepath)
if file_age > max_age_seconds:
os.remove(filepath)
deleted_count += 1
except (OSError, IOError):
# Skip files we can't access
continue
return deleted_count