File size: 3,163 Bytes
a3fa620
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""
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}")