import sys
import os
import json
import datetime
import time
import threading
import random
import requests
import speech_recognition as sr
import pyttsx3
import pythoncom
import win32gui
import win32con
import win32api
from PIL import Image, ImageDraw
import pystray
from pystray import MenuItem as item
import atexit
import traceback
import ctypes
from ctypes import wintypes
import tempfile
# Live2DViewerEX 控制类
class Live2DViewerEXController:
def __init__(self):
self.window_name = "Live2DViewerEX"
self.hwnd = None
self.find_window()
def find_window(self):
"""查找 Live2DViewerEX 窗口句柄"""
try:
# 使用更可靠的方法查找窗口
def enum_windows_proc(hwnd, lParam):
if win32gui.IsWindowVisible(hwnd):
window_text = win32gui.GetWindowText(hwnd)
if self.window_name.lower() in window_text.lower():
self.hwnd = hwnd
return False # 停止枚举
return True # 继续枚举
# 使用ctypes定义回调函数类型
enum_windows_func = ctypes.WINFUNCTYPE(
wintypes.BOOL,
wintypes.HWND,
wintypes.LPARAM
)
# 创建回调函数
callback = enum_windows_func(enum_windows_proc)
# 调用EnumWindows
ctypes.windll.user32.EnumWindows(callback, 0)
if self.hwnd is None:
print("未找到Live2DViewerEX窗口,请确保程序已运行")
else:
print(f"找到Live2DViewerEX窗口,句柄: {self.hwnd}")
return self.hwnd is not None
except Exception as e:
print(f"查找窗口时出错: {e}")
traceback.print_exc()
return False
def send_keystroke(self, key):
"""向 Live2DViewerEX 发送按键模拟"""
try:
if self.hwnd:
# 确保窗口处于前台
win32gui.ShowWindow(self.hwnd, win32con.SW_RESTORE)
win32gui.SetForegroundWindow(self.hwnd)
# 发送按键
win32api.keybd_event(key, 0, 0, 0)
time.sleep(0.05)
win32api.keybd_event(key, 0, win32con.KEYEVENTF_KEYUP, 0)
except Exception as e:
print(f"发送按键时出错: {e}")
def trigger_expression(self, expression_id):
"""触发特定表情"""
try:
if 1 <= expression_id <= 9:
self.send_keystroke(0x30 + expression_id)
except Exception as e:
print(f"触发表情时出错: {e}")
def trigger_motion(self, motion_id):
"""触发特定动作"""
try:
if 1 <= motion_id <= 9:
self.send_keystroke(win32con.VK_F1 + motion_id - 1)
except Exception as e:
print(f"触发动作时出错: {e}")
# 配置管理
class ConfigManager:
def __init__(self, config_file=None):
if config_file is None:
config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
self.config_file = config_file
self.default_config = {
"user_name": "主人",
"volume": 70,
"speech_rate": 150,
"auto_start": False,
"enable_startup_greeting": True,
"enable_shutdown_greeting": True,
"reminder_times": ["09:00", "12:00", "15:00", "18:00", "21:00"],
"live2d_window_title": "Live2DViewerEX"
}
self.config = self.load_config()
def load_config(self):
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
return {**self.default_config, **json.load(f)}
return self.default_config.copy()
except Exception as e:
print(f"加载配置时出错: {e}")
return self.default_config.copy()
def save_config(self):
try:
# 确保配置目录存在
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, ensure_ascii=False, indent=4)
except Exception as e:
print(f"保存配置时出错: {e}")
def get(self, key, default=None):
return self.config.get(key, default)
def set(self, key, value):
self.config[key] = value
self.save_config()
# 桌面助手主类
class DesktopAssistant:
def __init__(self):
self.config_manager = ConfigManager()
self.live2d_controller = Live2DViewerEXController()
self.recognizer = sr.Recognizer()
self.microphone = None
self.is_listening = False
self.last_announce_time = None
self.scheduler_running = True
# 人性化问候语库
self.greetings = self.init_greetings()
self.init_tts()
self.init_microphone()
self.init_scheduler()
# 注册退出时的处理函数
atexit.register(self.shutdown_greeting)
# 显示启动问候
if self.config_manager.get("enable_startup_greeting", True):
self.show_welcome()
def init_greetings(self):
return {
"dawn": [
"{name},已经{hour}点{minute}分了,您还在工作吗?要注意休息哦~",
"夜深了,{name},现在是{hour}点{minute}分,早点休息对身体好",
"{name},凌晨{hour}点{minute}分了,您真是辛苦呢"
],
"morning": [
"早上好,{name}!新的一天开始啦,现在是{hour}点{minute}分,记得吃早餐哦~",
"早安,{name}!阳光正好,现在是{hour}点{minute}分,愿你有个美好的一天!",
"{name},早上好!现在是{hour}点{minute}分,今天也要元气满满哦!"
],
"late_morning": [
"上午好,{name}!现在是{hour}点{minute}分,工作学习要加油哦~",
"{name},上午好!已经{hour}点{minute}分了,保持专注,但也别忘了休息一下",
"上午{hour}点{minute}分,{name}今天的目标完成得怎么样啦?"
],
"noon": [
"中午好,{name}!现在是{hour}点{minute}分,午饭时间到啦,记得好好吃饭~",
"午安,{name}!已经{hour}点{minute}分了,休息一下,补充能量吧!",
"{name},中午了哦~ {hour}点{minute}分,吃饱了才有力气继续战斗呢!"
],
"afternoon": [
"下午好,{name}!现在是{hour}点{minute}分,下午茶时间到啦~",
"下午好呀,{name}!已经{hour}点{minute}分了,工作学习累了记得休息一下哦",
"{name},下午好!{hour}点{minute}分,保持专注,但也别忘了站起来活动活动~"
],
"evening": [
"晚上好,{name}!现在是{hour}点{minute}分,今天过得怎么样呀?",
"傍晚好,{name}!{hour}点{minute}分了,晚餐吃了吗?",
"{name},晚上好~ 已经{hour}点{minute}分了,忙碌一天辛苦了!"
],
"night": [
"晚上好,{name}!现在是{hour}点{minute}分,今天早点休息哦~",
"夜深了,{name}!{hour}点{minute}分了,不要熬夜太晚呀",
"{name},晚上{hour}点{minute}分了,明天还要继续努力,记得早点睡哦"
],
"startup": [
"您好,{name}!我已经准备好为您服务了~",
"{name},欢迎回来!今天有什么需要帮忙的吗?",
"很高兴再次见到您,{name}!我已经启动完毕,随时待命~"
],
"shutdown": [
"{name},我要休息啦,下次再见哦~",
"再见,{name}!期待与您的下一次相遇~",
"关机中...{name},别忘了保存重要文件哦,明天见!"
]
}
def init_tts(self):
try:
self.tts_engine = pyttsx3.init()
voices = self.tts_engine.getProperty('voices')
if voices:
for voice in voices:
if "Microsoft" in voice.name and "Xiaoxiao" in voice.name:
self.tts_engine.setProperty('voice', voice.id)
break
else:
self.tts_engine.setProperty('voice', voices[0].id)
self.tts_engine.setProperty('rate', self.config_manager.get("speech_rate", 150))
self.tts_engine.setProperty('volume', self.config_manager.get("volume", 70) / 100.0)
except Exception as e:
print(f"初始化TTS引擎失败: {e}")
self.tts_engine = None
def init_microphone(self):
"""初始化麦克风"""
try:
self.microphone = sr.Microphone()
print("麦克风初始化成功")
except Exception as e:
print(f"麦克风初始化失败: {e}")
self.microphone = None
def init_scheduler(self):
self.schedule_thread = threading.Thread(target=self.run_scheduler)
self.schedule_thread.daemon = True
self.schedule_thread.start()
def run_scheduler(self):
while self.scheduler_running:
try:
now = datetime.datetime.now()
current_time = now.strftime("%H:%M")
reminder_times = self.config_manager.get("reminder_times", [])
if current_time in reminder_times:
if self.last_announce_time is None or (now - self.last_announce_time).total_seconds() > 300:
self.announce_time()
self.last_announce_time = now
if now.minute == 0 and (self.last_announce_time is None or
(now - self.last_announce_time).total_seconds() > 300):
self.announce_time()
self.last_announce_time = now
time.sleep(30)
except Exception as e:
print(f"调度器错误: {e}")
time.sleep(30)
def get_time_period(self, hour):
if 0 <= hour < 5:
return "dawn"
elif 5 <= hour < 9:
return "morning"
elif 9 <= hour < 12:
return "late_morning"
elif 12 <= hour < 14:
return "noon"
elif 14 <= hour < 17:
return "afternoon"
elif 17 <= hour < 19:
return "evening"
else:
return "night"
def announce_time(self, test=False):
try:
now = datetime.datetime.now()
hour = now.hour
minute = now.minute
if test:
hour = now.hour
minute = now.minute
else:
hour = now.hour
minute = 0
time_period = self.get_time_period(hour)
if time_period in self.greetings:
greeting = random.choice(self.greetings[time_period])
message = greeting.format(
name=self.config_manager.get("user_name"),
hour=hour,
minute=minute
)
else:
message = f"现在是{hour}点{minute}分"
if time_period == "morning":
self.live2d_controller.trigger_expression(1)
elif time_period == "noon":
self.live2d_controller.trigger_expression(2)
elif time_period in ["afternoon", "evening"]:
self.live2d_controller.trigger_expression(3)
else:
self.live2d_controller.trigger_expression(4)
self.speak(message)
except Exception as e:
print(f"报时功能出错: {e}")
def show_welcome(self):
try:
now = datetime.datetime.now()
hour = now.hour
if 5 <= hour < 12:
welcome = f"早上好,{self.config_manager.get('user_name')}!很高兴为您服务~"
expression = 1
elif 12 <= hour < 18:
welcome = f"下午好,{self.config_manager.get('user_name')}!今天过得怎么样?"
expression = 3
else:
welcome = f"晚上好,{self.config_manager.get('user_name')}!今天辛苦啦~"
expression = 4
# 添加启动问候语
startup_greeting = random.choice(self.greetings["startup"])
welcome = f"{welcome} {startup_greeting.format(name=self.config_manager.get('user_name'))}"
self.live2d_controller.trigger_expression(expression)
self.speak(welcome, priority=True)
except Exception as e:
print(f"欢迎语功能出错: {e}")
def shutdown_greeting(self):
"""关机问候语"""
if not self.config_manager.get("enable_shutdown_greeting", True):
return
try:
self.scheduler_running = False
shutdown_message = random.choice(self.greetings["shutdown"])
formatted_message = shutdown_message.format(name=self.config_manager.get("user_name"))
self.live2d_controller.trigger_expression(5) # 使用一个温和的表情
self.speak(formatted_message, priority=True)
# 给语音足够的时间播放
time.sleep(3)
except Exception as e:
print(f"关机问候出错: {e}")
def start_listening(self):
if self.is_listening:
return
self.is_listening = True
threading.Thread(target=self.voice_listen_thread).start()
def voice_listen_thread(self):
try:
if self.microphone is None:
self.speak("麦克风未初始化,无法进行语音识别", priority=True)
return
self.speak("我在听呢,请说吧~", priority=True)
with self.microphone as source:
self.recognizer.adjust_for_ambient_noise(source)
audio = self.recognizer.listen(source, timeout=5, phrase_time_limit=5)
text = self.recognizer.recognize_google(audio, language='zh-CN')
self.process_user_input(text)
except sr.WaitTimeoutError:
self.speak("好像没有说话呢~", priority=True)
except sr.UnknownValueError:
self.speak("抱歉,我没有听清楚,能再说一次吗?", priority=True)
except Exception as e:
print(f"语音识别错误: {e}")
self.speak("出了点小问题,请再试一次~", priority=True)
finally:
self.is_listening = False
def process_user_input(self, text):
if "几点" in text or "时间" in text:
self.announce_time()
elif "你好" in text or "嗨" in text:
self.speak(f"{self.config_manager.get('user_name')},你好呀!")
self.live2d_controller.trigger_expression(1)
elif "谢谢" in text:
self.speak("不客气,很高兴能帮到你~")
self.live2d_controller.trigger_expression(3)
else:
self.speak("让我想想...", priority=True)
response = self.get_chat_response(text)
self.speak(response)
def get_chat_response(self, message):
try:
api_url = "https://api.ownthink.com/bot"
data = {"spoken": message}
response = requests.post(api_url, data=data, timeout=5)
response.encoding = 'utf-8'
result = response.json()
return result['data']['info']['text']
except Exception as e:
print(f"聊天API错误: {e}")
return "抱歉,我现在有点困惑,请稍后再试。"
def speak(self, text, priority=False):
if self.tts_engine is None:
print(f"TTS引擎未初始化,无法朗读: {text}")
return
try:
self.tts_engine.say(text)
self.tts_engine.runAndWait()
except Exception as e:
print(f"语音输出错误: {e}")
# 系统托盘图标类
class SystemTrayIcon:
def __init__(self, assistant):
self.assistant = assistant
self.icon = None
self.create_tray_icon()
def create_tray_icon(self):
try:
# 创建一个更明显的图标
image = Image.new('RGBA', (64, 64), (255, 255, 255, 0))
draw = ImageDraw.Draw(image)
# 绘制一个更显眼的图标
draw.rectangle((16, 16, 48, 48), fill=(255, 105, 180, 255)) # 热粉色背景
draw.ellipse((24, 24, 40, 40), fill=(255, 255, 255, 255)) # 白色圆形
draw.ellipse((28, 28, 36, 36), fill=(0, 0, 0, 255)) # 黑色中心
menu = (
item('语音聊天', self.voice_chat),
item('测试报时', self.test_announce),
item('设置', self.open_settings),
item('退出', self.quit)
)
self.icon = pystray.Icon("live2d_assistant", image, "Live2D桌面助手", menu)
except Exception as e:
print(f"创建系统托盘图标时出错: {e}")
traceback.print_exc()
# 创建备用图标
try:
image = Image.new('RGB', (16, 16), color='red')
self.icon = pystray.Icon("live2d_assistant", image, "Live2D桌面助手", menu)
except Exception as e2:
print(f"创建备用图标也失败: {e2}")
def voice_chat(self, icon, item):
self.assistant.start_listening()
def test_announce(self, icon, item):
self.assistant.announce_time(test=True)
def open_settings(self, icon, item):
# 这里可以添加打开设置窗口的功能
print("打开设置功能待实现")
self.assistant.speak("设置功能尚未实现")
def quit(self, icon, item):
# 退出前执行关机问候
self.assistant.shutdown_greeting()
self.icon.stop()
os._exit(0) # 确保程序完全退出
def run(self):
try:
if self.icon:
self.icon.run()
else:
print("图标创建失败,程序将继续在后台运行")
# 程序继续运行但没有图标
while True:
time.sleep(10)
except Exception as e:
print(f"运行系统托盘时出错: {e}")
traceback.print_exc()
def check_single_instance():
"""确保只有一个程序实例在运行"""
try:
# 创建互斥体
mutex = win32api.CreateMutex(None, False, "Live2DDesktopAssistantMutex")
if win32api.GetLastError() == win32con.ERROR_ALREADY_EXISTS:
mutex = None
print("程序已经在运行中")
# 显示提示信息
ctypes.windll.user32.MessageBoxW(0, "Live2D桌面助手已经在运行中,请检查系统托盘区域", "提示", 0x40)
return False
return True
except Exception as e:
print(f"检查单实例时出错: {e}")
return True
def main():
try:
# 检查是否已有实例运行
if not check_single_instance():
return
print("正在初始化COM...")
pythoncom.CoInitialize()
print("COM初始化完成")
print("正在创建DesktopAssistant实例...")
assistant = DesktopAssistant()
print("DesktopAssistant实例创建完成")
print("正在创建系统托盘图标...")
tray_icon = SystemTrayIcon(assistant)
print("系统托盘图标创建完成")
print("程序已启动,请在系统托盘中查看图标")
# 添加启动通知
try:
if tray_icon.icon:
tray_icon.icon.visible = True
# 显示通知
tray_icon.icon.notify("Live2D桌面助手已启动", "点击托盘图标与助手互动")
except Exception as e:
print(f"显示通知时出错: {e}")
tray_icon.run()
except Exception as e:
print(f"程序运行出错: {e}")
traceback.print_exc()
# 显示错误消息
ctypes.windll.user32.MessageBoxW(0, f"程序启动失败: {str(e)}", "错误", 0x10)
finally:
try:
pythoncom.CoUninitialize()
except:
pass
if __name__ == "__main__":
# 如果是直接运行,而不是被导入
if hasattr(sys, 'frozen'): # 如果是打包后的可执行文件
# 改变当前工作目录到可执行文件所在目录
os.chdir(os.path.dirname(sys.executable))
main()为什么在系统托盘没看见图标
最新发布