Files
xiaoli-cli/plugins/speech_recognition.py
T
Xiaoli Dev e22652fbbb feat: 增强编码能力 - 新增代码编辑器、代码搜索、Git集成
- 新增 plugins/code_editor.py: 精准代码编辑(搜索替换、批量编辑、diff对比)
- 新增 plugins/code_search.py: 代码搜索与理解(跨文件搜索、符号提取、依赖分析)
- 新增 plugins/git_tools.py: Git 版本控制(status/diff/log/commit/branch)
- 新增 core/ 模块: 配置管理、引擎管理、对话处理、显示输出、代码沙箱
- 修复 requirements.txt: 使用正确的 pip 包名(Pillow、opencv-python 等)
- 更新 unified_tool_manager.py: 兼容 Plugin 和 Liugin 两种类名
- 更新系统提示词: 增强编程工作流指导

本次更新由 mimo 自动化执行,与开发者无关。
2026-04-25 10:55:27 +08:00

550 lines
18 KiB
Python

"""
语音识别插件 - 弹窗录音界面
AI 触发后弹出独立录音窗口
"""
import os
import json
import subprocess
import sys
import time
from typing import Optional, Dict, Any, List
# 结果保存路径
RESULT_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "speech_temp", "last_result.txt")
TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "speech_temp")
class Liugin:
"""语音识别插件"""
def __init__(self):
self.plugin_dir = os.path.dirname(os.path.abspath(__file__))
self.temp_dir = TEMP_DIR
os.makedirs(self.temp_dir, exist_ok=True)
self.usage = """语音识别插件使用方法:
speech_recognition <操作> [参数]
录音识别:
- record - 弹出录音窗口,录音并识别
文件识别:
- file <音频文件路径> - 识别音频文件内容
其他:
- status - 查看状态
- result - 获取上次识别结果
支持的音频格式: wav, mp3, m4a, flac, ogg
"""
self.cli = None
def set_cli(self, cli):
self.cli = cli
def get_tool_info(self):
return {
"name": "speech_recognition",
"description": "语音识别插件,弹出录音窗口进行语音识别,将语音转换为文字",
"keywords": ["语音", "识别", "录音", "转文字", "speech", "voice", "recognition"],
"usage": self.usage
}
def get_mcp_definition(self):
return {
"name": "speech_recognition",
"description": "语音识别插件,弹出录音窗口进行语音识别",
"inputSchema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["record", "file", "status", "result"],
"description": "操作类型"
},
"file_path": {"type": "string", "description": "音频文件路径"}
},
"required": ["operation"]
}
}
def convert_mcp_args(self, arguments: Dict[str, Any]) -> str:
op = arguments.get("operation", "")
parts = [op]
if op == "file":
parts.append(arguments.get("file_path", ""))
return " ".join(filter(None, parts))
def handle(self, args: str) -> str:
try:
parts = self._parse_args(args)
if not parts:
return "错误:请提供操作类型。\n" + self.usage
operation = parts[0].lower()
handlers = {
"record": self._op_record,
"file": self._op_file,
"status": self._op_status,
"result": self._op_result,
}
if operation in handlers:
return handlers[operation](parts[1:])
else:
return f"错误:未知操作 '{operation}'\n可用操作: " + ", ".join(handlers.keys())
except Exception as e:
return f"处理请求失败: {e}"
def _parse_args(self, args: str) -> List[str]:
parts = []
current = ""
in_quotes = False
quote_char = None
for char in args:
if char in ('"', "'") and not in_quotes:
in_quotes = True
quote_char = char
elif char == quote_char and in_quotes:
in_quotes = False
quote_char = None
elif char == ' ' and not in_quotes:
if current:
parts.append(current)
current = ""
else:
current += char
if current:
parts.append(current)
return parts
# ==================== 弹窗录音 ====================
def _op_record(self, args: List[str]) -> str:
"""弹出录音窗口"""
# 创建录音窗口脚本
recorder_script = os.path.join(self.temp_dir, "recorder_gui.py")
gui_code = f'''import tkinter as tk
from tkinter import messagebox
import os
import sys
import threading
import time
RESULT_FILE = r"{RESULT_FILE}"
TEMP_DIR = r"{TEMP_DIR}"
class VoiceRecorder:
def __init__(self):
self.root = tk.Tk()
self.root.title("语音识别录音")
self.root.geometry("450x350")
self.root.resizable(False, False)
# 居中显示
self.root.update_idletasks()
x = (self.root.winfo_screenwidth() - 450) // 2
y = (self.root.winfo_screenheight() - 350) // 2
self.root.geometry(f"450x350+{{x}}+{{y}}")
# 录音状态
self.is_recording = False
self.record_file = None
self.frames = []
self.start_time = None
self.p = None
self.stream = None
# 创建界面
self._create_ui()
# 检查 pyaudio
self.has_pyaudio = self._check_pyaudio()
if not self.has_pyaudio:
self.status_label.config(text="请安装 pyaudio: pip install pyaudio", fg="red")
def _check_pyaudio(self):
try:
import pyaudio
return True
except:
return False
def _create_ui(self):
# 标题
title_label = tk.Label(self.root, text="🎙️ 语音识别录音", font=("Arial", 18, "bold"))
title_label.pack(pady=15)
# 状态标签
self.status_label = tk.Label(self.root, text="点击开始录音", font=("Arial", 11))
self.status_label.pack(pady=5)
# 时间标签
self.time_label = tk.Label(self.root, text="00:00", font=("Arial", 28, "bold"), fg="#2196F3")
self.time_label.pack(pady=10)
# 按钮框架
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=15)
# 开始/停止按钮
self.record_btn = tk.Button(
btn_frame, text="🎤 开始录音", font=("Arial", 12),
width=12, height=2, command=self.toggle_record,
bg="#4CAF50", fg="white", cursor="hand2"
)
self.record_btn.pack(side=tk.LEFT, padx=10)
# 识别按钮
self.recognize_btn = tk.Button(
btn_frame, text="📝 识别", font=("Arial", 12),
width=12, height=2, command=self.recognize,
bg="#2196F3", fg="white", cursor="hand2", state=tk.DISABLED
)
self.recognize_btn.pack(side=tk.LEFT, padx=10)
# 结果显示
result_frame = tk.LabelFrame(self.root, text="识别结果", font=("Arial", 10))
result_frame.pack(pady=10, padx=15, fill=tk.BOTH, expand=True)
self.result_text = tk.Text(result_frame, height=5, width=50, font=("Arial", 10))
self.result_text.pack(pady=5, padx=5, fill=tk.BOTH, expand=True)
def toggle_record(self):
if not self.has_pyaudio:
messagebox.showerror("错误", "请先安装 pyaudio:\npip install pyaudio")
return
if self.is_recording:
self.stop_record()
else:
self.start_record()
def start_record(self):
import pyaudio
self.is_recording = True
self.frames = []
self.start_time = time.time()
self.record_btn.config(text="⏹️ 停止录音", bg="#f44336")
self.status_label.config(text="🔴 正在录音...", fg="red")
self.recognize_btn.config(state=tk.DISABLED)
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
self.p = pyaudio.PyAudio()
self.stream = self.p.open(
format=FORMAT, channels=CHANNELS, rate=RATE,
input=True, frames_per_buffer=CHUNK
)
# 更新时间
self._update_time()
# 录音线程
def record_loop():
while self.is_recording:
try:
data = self.stream.read(CHUNK, exception_on_overflow=False)
self.frames.append(data)
except:
break
self.record_thread = threading.Thread(target=record_loop, daemon=True)
self.record_thread.start()
def stop_record(self):
self.is_recording = False
self.record_btn.config(text="🎤 开始录音", bg="#4CAF50")
self.status_label.config(text="录音完成,可以识别", fg="green")
self.recognize_btn.config(state=tk.NORMAL)
# 停止录音
if self.stream:
self.stream.stop_stream()
self.stream.close()
if self.p:
self.p.terminate()
# 保存文件
import wave
import pyaudio
self.record_file = os.path.join(TEMP_DIR, f"record_{{int(time.time())}}.wav")
wf = wave.open(self.record_file, "wb")
wf.setnchannels(1)
wf.setsampwidth(pyaudio.get_sample_size(pyaudio.paInt16))
wf.setframerate(16000)
wf.writeframes(b"".join(self.frames))
wf.close()
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, "✅ 录音已保存\n点击 [识别] 按钮进行语音识别")
def _update_time(self):
if self.is_recording:
elapsed = int(time.time() - self.start_time)
mins = elapsed // 60
secs = elapsed % 60
self.time_label.config(text=f"{{mins:02d}}:{{secs:02d}}")
self.root.after(100, self._update_time)
def recognize(self):
if not self.record_file or not os.path.exists(self.record_file):
messagebox.showerror("错误", "没有录音文件,请先录音")
return
self.status_label.config(text="正在识别...", fg="blue")
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, "🔍 识别中,请稍候...")
self.root.update()
# 尝试识别
result = self._do_recognize()
self.result_text.delete(1.0, tk.END)
if result:
self.result_text.insert(tk.END, result)
self.status_label.config(text="识别完成", fg="green")
# 保存结果
with open(RESULT_FILE, "w", encoding="utf-8") as f:
f.write(result)
else:
self.result_text.insert(tk.END, "❌ 识别失败\n\n请安装识别引擎:\n• pip install openai-whisper\n• pip install SpeechRecognition")
self.status_label.config(text="识别失败", fg="red")
def _do_recognize(self):
# 尝试 Whisper
try:
import whisper
self.status_label.config(text="使用 Whisper 识别...", fg="blue")
self.root.update()
model = whisper.load_model("base")
result = model.transcribe(self.record_file, language="zh")
text = result.get("text", "").strip()
if text:
return f"✅ {{text}}"
except Exception as e:
print(f"Whisper error: {{e}}")
# 尝试 SpeechRecognition
try:
import speech_recognition as sr
self.status_label.config(text="使用 Google 识别...", fg="blue")
self.root.update()
r = sr.Recognizer()
with sr.AudioFile(self.record_file) as source:
audio = r.record(source)
text = r.recognize_google(audio, language="zh-CN")
if text:
return f"✅ {{text}}"
except Exception as e:
print(f"SpeechRecognition error: {{e}}")
return None
def run(self):
self.root.mainloop()
# 清理
if self.is_recording:
self.is_recording = False
if self.stream:
self.stream.close()
if self.p:
self.p.terminate()
if __name__ == "__main__":
app = VoiceRecorder()
app.run()
'''
# 写入脚本
with open(recorder_script, "w", encoding="utf-8") as f:
f.write(gui_code)
# 启动录音窗口
try:
subprocess.Popen([sys.executable, recorder_script],
creationflags=subprocess.CREATE_NEW_CONSOLE)
return "🎙️ 已弹出录音窗口,请在窗口中进行录音和识别\n\n使用方法:\n1. 点击「开始录音」\n2. 对着麦克风说话\n3. 点击「停止录音」\n4. 点击「识别」获取文字结果"
except Exception as e:
return f"❌ 启动录音窗口失败: {e}"
# ==================== 文件识别 ====================
def _op_file(self, args: List[str]) -> str:
"""识别音频文件"""
if not args:
return "错误:请提供音频文件路径"
file_path = args[0]
if not os.path.exists(file_path):
return f"❌ 文件不存在: {file_path}"
return self._recognize_file(file_path)
def _recognize_file(self, file_path: str) -> str:
"""识别音频文件内容"""
# 尝试使用 whisper(本地)
result = self._try_whisper(file_path)
if result:
self._save_result(result)
return result
# 尝试使用 speech_recognition 库
result = self._try_speech_recognition(file_path)
if result:
self._save_result(result)
return result
return "❌ 语音识别失败:未安装识别引擎\n\n安装方法:\n1. Whisper (推荐): pip install openai-whisper\n2. SpeechRecognition: pip install SpeechRecognition\n3. PyAudio (录音): pip install pyaudio"
def _save_result(self, result: str):
"""保存识别结果"""
try:
with open(RESULT_FILE, "w", encoding="utf-8") as f:
f.write(result)
except:
pass
def _try_whisper(self, file_path: str) -> Optional[str]:
"""使用 Whisper 识别"""
try:
import whisper
print(f"[语音识别] 使用 Whisper 识别中...")
model = whisper.load_model("base")
result = model.transcribe(file_path, language="zh")
text = result.get("text", "").strip()
if text:
return f"✅ 识别结果:\n{text}"
return None
except ImportError:
return None
except Exception as e:
print(f"[语音识别] Whisper 错误: {e}")
return None
def _try_speech_recognition(self, file_path: str) -> Optional[str]:
"""使用 SpeechRecognition 库识别"""
wav_file = file_path
try:
import speech_recognition as sr
print(f"[语音识别] 使用 SpeechRecognition 识别中...")
r = sr.Recognizer()
# 转换为 wav 格式
wav_file = self._convert_to_wav(file_path)
with sr.AudioFile(wav_file) as source:
audio = r.record(source)
# 尝试 Google 语音识别
try:
text = r.recognize_google(audio, language="zh-CN")
if text:
return f"✅ 识别结果:\n{text}"
except:
pass
return None
except ImportError:
return None
except Exception as e:
print(f"[语音识别] SpeechRecognition 错误: {e}")
return None
finally:
# 清理转换的临时文件
if wav_file != file_path and os.path.exists(wav_file):
try:
os.remove(wav_file)
except:
pass
def _convert_to_wav(self, file_path: str) -> str:
"""转换音频为 wav 格式"""
if file_path.lower().endswith('.wav'):
return file_path
try:
wav_file = os.path.join(self.temp_dir, f"convert_{int(time.time())}.wav")
# 使用 ffmpeg 转换
subprocess.run([
'ffmpeg', '-y', '-i', file_path,
'-acodec', 'pcm_s16le',
'-ar', '16000',
'-ac', '1',
wav_file
], capture_output=True, check=True)
return wav_file
except Exception as e:
print(f"[语音识别] 转换错误: {e}")
return file_path
# ==================== 其他 ====================
def _op_status(self, args: List[str]) -> str:
"""查看状态"""
lines = ["📊 语音识别插件状态"]
# 检查可用引擎
engines = []
try:
import whisper
engines.append("whisper ✅")
except:
engines.append("whisper ❌ (pip install openai-whisper)")
try:
import speech_recognition
engines.append("speech_recognition ✅")
except:
engines.append("speech_recognition ❌ (pip install SpeechRecognition)")
try:
import pyaudio
engines.append("pyaudio ✅ (录音支持)")
except:
engines.append("pyaudio ❌ (pip install pyaudio)")
lines.append("\n可用引擎:")
for e in engines:
lines.append(f" - {e}")
return "\n".join(lines)
def _op_result(self, args: List[str]) -> str:
"""获取上次识别结果"""
if os.path.exists(RESULT_FILE):
try:
with open(RESULT_FILE, "r", encoding="utf-8") as f:
return f.read()
except:
pass
return "暂无识别结果"
if __name__ == "__main__":
plugin = Plugin()
print("语音识别插件")
print(f"临时目录: {plugin.temp_dir}")
print("\n" + plugin.usage)