wangzhibo
2026-08-30 c8515170fd399b4d2dfaebb59128d4674e58915e
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import os
import re
import yaml
import time
import hashlib
import portalocker
from typing import Dict
 
 
class FileLock:
    def __init__(self, file, timeout=5):
        self.file = file
        self.timeout = timeout
        self.start_time = None
 
    def __enter__(self):
        self.start_time = time.time()
        while True:
            try:
                portalocker.lock(self.file, portalocker.LOCK_EX | portalocker.LOCK_NB)
                return self.file
            except portalocker.LockException:
                if time.time() - self.start_time > self.timeout:
                    raise TimeoutError("获取文件锁超时")
                time.sleep(0.1)
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        portalocker.unlock(self.file)
 
 
class WakeupWordsConfig:
    def __init__(self):
        self.config_file = "data/.wakeup_words.yaml"
        self.assets_dir = "config/assets/wakeup_words"
        self._ensure_directories()
        self._config_cache = None
        self._last_load_time = 0
        self._cache_ttl = 1  # 缓存有效期(秒)
        self._lock_timeout = 5  # 文件锁超时时间(秒)
 
    def _ensure_directories(self):
        """确保必要的目录存在"""
        os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
        os.makedirs(self.assets_dir, exist_ok=True)
 
    def _load_config(self) -> Dict:
        """加载配置文件,使用缓存机制"""
        current_time = time.time()
 
        # 如果缓存有效,直接返回缓存
        if (
            self._config_cache is not None
            and current_time - self._last_load_time < self._cache_ttl
        ):
            return self._config_cache
 
        try:
            with open(self.config_file, "a+", encoding="utf-8") as f:
                with FileLock(f, timeout=self._lock_timeout):
                    f.seek(0)
                    content = f.read()
                    config = yaml.safe_load(content) if content else {}
                    self._config_cache = config
                    self._last_load_time = current_time
                    return config
        except (TimeoutError, IOError) as e:
            print(f"加载配置文件失败: {e}")
            return {}
        except Exception as e:
            print(f"加载配置文件时发生未知错误: {e}")
            return {}
 
    def _save_config(self, config: Dict):
        """保存配置到文件,使用文件锁保护"""
        try:
            with open(self.config_file, "w", encoding="utf-8") as f:
                with FileLock(f, timeout=self._lock_timeout):
                    yaml.dump(config, f, allow_unicode=True)
                    self._config_cache = config
                    self._last_load_time = time.time()
        except (TimeoutError, IOError) as e:
            print(f"保存配置文件失败: {e}")
            raise
        except Exception as e:
            print(f"保存配置文件时发生未知错误: {e}")
            raise
 
    def get_wakeup_response(self, voice: str) -> Dict:
        voice = hashlib.md5(voice.encode()).hexdigest()
        """获取唤醒词回复配置"""
        config = self._load_config()
 
        if not config or voice not in config:
            return None
 
        # 检查文件大小
        file_path = config[voice]["file_path"]
        if not os.path.exists(file_path) or os.stat(file_path).st_size < (15 * 1024):
            return None
 
        return config[voice]
 
    def update_wakeup_response(self, voice: str, file_path: str, text: str):
        """更新唤醒词回复配置"""
        try:
            # 过滤表情符号
            filtered_text = re.sub(r'[\U0001F600-\U0001F64F\U0001F900-\U0001F9FF]', '', text)
            
            config = self._load_config()
            voice_hash = hashlib.md5(voice.encode()).hexdigest()
            config[voice_hash] = {
                "voice": voice,
                "file_path": file_path,
                "time": time.time(),
                "text": filtered_text,
            }
            self._save_config(config)
        except Exception as e:
            print(f"更新唤醒词回复配置失败: {e}")
            raise
 
    def generate_file_path(self, voice: str) -> str:
        """生成音频文件路径,使用voice的哈希值作为文件名"""
        try:
            # 生成voice的哈希值
            voice_hash = hashlib.md5(voice.encode()).hexdigest()
            file_path = os.path.join(self.assets_dir, f"{voice_hash}.wav")
 
            # 如果文件已存在,先删除
            if os.path.exists(file_path):
                try:
                    os.remove(file_path)
                except Exception as e:
                    print(f"删除已存在的音频文件失败: {e}")
                    raise
 
            return file_path
        except Exception as e:
            print(f"生成音频文件路径失败: {e}")
            raise