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>
66 lines
1.9 KiB
Python
66 lines
1.9 KiB
Python
"""
|
||
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
|