""" TTS Cache using Redis or in-memory fallback """ import hashlib import logging import os from typing import Optional logger = logging.getLogger(__name__) # Try to import redis, fallback to in-memory cache try: import redis.asyncio as redis REDIS_AVAILABLE = True except ImportError: REDIS_AVAILABLE = False logger.warning("Redis not available, using in-memory cache") class TTSCache: def __init__(self): self.ttl = 86400 * 7 # 7 days self.redis_client = None self.memory_cache: dict[str, str] = {} self.max_memory_items = 1000 # Try to connect to Redis redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379") if REDIS_AVAILABLE: try: self.redis_client = redis.from_url(redis_url, decode_responses=True) logger.info(f"Redis cache initialized: {redis_url}") except Exception as e: logger.warning(f"Redis connection failed, using memory cache: {e}") self.redis_client = None def _key(self, text: str) -> str: """Generate cache key from text hash""" return f"tts:{hashlib.md5(text.encode()).hexdigest()}" async def get(self, text: str) -> Optional[str]: """Get cached audio (base64) for text""" key = self._key(text) # Try Redis first if self.redis_client: try: result = await self.redis_client.get(key) if result: logger.debug(f"Redis cache hit for key: {key}") return result except Exception as e: logger.warning(f"Redis get failed: {e}") # Fallback to memory cache result = self.memory_cache.get(key) if result: logger.debug(f"Memory cache hit for key: {key}") return result async def set(self, text: str, audio_b64: str): """Cache audio (base64) for text""" key = self._key(text) # Try Redis first if self.redis_client: try: await self.redis_client.setex(key, self.ttl, audio_b64) logger.debug(f"Cached to Redis: {key}") return except Exception as e: logger.warning(f"Redis set failed: {e}") # Fallback to memory cache with LRU eviction if len(self.memory_cache) >= self.max_memory_items: # Remove oldest item (simple FIFO, not true LRU) oldest_key = next(iter(self.memory_cache)) del self.memory_cache[oldest_key] logger.debug(f"Evicted from memory cache: {oldest_key}") self.memory_cache[key] = audio_b64 logger.debug(f"Cached to memory: {key}") async def clear(self): """Clear all cached items""" self.memory_cache.clear() if self.redis_client: try: # Clear only TTS keys async for key in self.redis_client.scan_iter("tts:*"): await self.redis_client.delete(key) except Exception as e: logger.warning(f"Redis clear failed: {e}")