|
|
from config.logger import setup_logging
|
|
|
import os
|
|
|
import re
|
|
|
import time
|
|
|
import random
|
|
|
import asyncio
|
|
|
import difflib
|
|
|
import traceback
|
|
|
from pathlib import Path
|
|
|
from core.utils import p3
|
|
|
from core.handle.sendAudioHandle import send_stt_message
|
|
|
from plugins_func.register import register_function,ToolType, ActionResponse, Action
|
|
|
|
|
|
|
|
|
TAG = __name__
|
|
|
logger = setup_logging()
|
|
|
|
|
|
MUSIC_CACHE = {}
|
|
|
|
|
|
play_music_function_desc = {
|
|
|
"type": "function",
|
|
|
"function": {
|
|
|
"name": "play_music",
|
|
|
"description": "唱歌、听歌、播放音乐的方法。",
|
|
|
"parameters": {
|
|
|
"type": "object",
|
|
|
"properties": {
|
|
|
"song_name": {
|
|
|
"type": "string",
|
|
|
"description": "歌曲名称,如果用户没有指定具体歌名则为'random', 明确指定的时返回音乐的名字 示例: ```用户:播放两只老虎\n参数:两只老虎``` ```用户:播放音乐 \n参数:random ```"
|
|
|
}
|
|
|
},
|
|
|
"required": ["song_name"]
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
@register_function('play_music', play_music_function_desc, ToolType.SYSTEM_CTL)
|
|
|
def play_music(conn, song_name: str):
|
|
|
try:
|
|
|
music_intent = f"播放音乐 {song_name}" if song_name != "random" else "随机播放音乐"
|
|
|
|
|
|
# 检查事件循环状态
|
|
|
if not conn.loop.is_running():
|
|
|
logger.bind(tag=TAG).error("事件循环未运行,无法提交任务")
|
|
|
return ActionResponse(action=Action.RESPONSE, result="系统繁忙", response="请稍后再试")
|
|
|
|
|
|
# 提交异步任务
|
|
|
future = asyncio.run_coroutine_threadsafe(
|
|
|
handle_music_command(conn, music_intent),
|
|
|
conn.loop
|
|
|
)
|
|
|
|
|
|
# 非阻塞回调处理
|
|
|
def handle_done(f):
|
|
|
try:
|
|
|
f.result() # 可在此处理成功逻辑
|
|
|
logger.bind(tag=TAG).info("播放完成")
|
|
|
except Exception as e:
|
|
|
logger.bind(tag=TAG).error(f"播放失败: {e}")
|
|
|
|
|
|
future.add_done_callback(handle_done)
|
|
|
|
|
|
return ActionResponse(action=Action.RESPONSE, result="指令已接收", response="正在为您播放音乐")
|
|
|
except Exception as e:
|
|
|
logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}")
|
|
|
return ActionResponse(action=Action.RESPONSE, result=str(e), response="播放音乐时出错了")
|
|
|
|
|
|
|
|
|
def _extract_song_name(text):
|
|
|
"""从用户输入中提取歌名"""
|
|
|
for keyword in ["播放音乐"]:
|
|
|
if keyword in text:
|
|
|
parts = text.split(keyword)
|
|
|
if len(parts) > 1:
|
|
|
return parts[1].strip()
|
|
|
return None
|
|
|
|
|
|
|
|
|
def _find_best_match(potential_song, music_files):
|
|
|
"""查找最匹配的歌曲"""
|
|
|
best_match = None
|
|
|
highest_ratio = 0
|
|
|
|
|
|
for music_file in music_files:
|
|
|
song_name = os.path.splitext(music_file)[0]
|
|
|
ratio = difflib.SequenceMatcher(None, potential_song, song_name).ratio()
|
|
|
if ratio > highest_ratio and ratio > 0.4:
|
|
|
highest_ratio = ratio
|
|
|
best_match = music_file
|
|
|
return best_match
|
|
|
|
|
|
|
|
|
def get_music_files(music_dir, music_ext):
|
|
|
music_dir = Path(music_dir)
|
|
|
music_files = []
|
|
|
music_file_names = []
|
|
|
for file in music_dir.rglob("*"):
|
|
|
# 判断是否是文件
|
|
|
if file.is_file():
|
|
|
# 获取文件扩展名
|
|
|
ext = file.suffix.lower()
|
|
|
# 判断扩展名是否在列表中
|
|
|
if ext in music_ext:
|
|
|
# 添加相对路径
|
|
|
music_files.append(str(file.relative_to(music_dir)))
|
|
|
music_file_names.append(os.path.splitext(str(file.relative_to(music_dir)))[0])
|
|
|
return music_files, music_file_names
|
|
|
|
|
|
|
|
|
def initialize_music_handler(conn):
|
|
|
global MUSIC_CACHE
|
|
|
if MUSIC_CACHE == {}:
|
|
|
if "play_music" in conn.config["plugins"]:
|
|
|
MUSIC_CACHE["music_config"] = conn.config["plugins"]["play_music"]
|
|
|
MUSIC_CACHE["music_dir"] = os.path.abspath(
|
|
|
MUSIC_CACHE["music_config"].get("music_dir", "./music") # 默认路径修改
|
|
|
)
|
|
|
MUSIC_CACHE["music_ext"] = MUSIC_CACHE["music_config"].get("music_ext", (".mp3", ".wav", ".p3"))
|
|
|
MUSIC_CACHE["refresh_time"] = MUSIC_CACHE["music_config"].get("refresh_time", 60)
|
|
|
else:
|
|
|
MUSIC_CACHE["music_dir"] = os.path.abspath("./music")
|
|
|
MUSIC_CACHE["music_ext"] = (".mp3", ".wav", ".p3")
|
|
|
MUSIC_CACHE["refresh_time"] = 60
|
|
|
# 获取音乐文件列表
|
|
|
MUSIC_CACHE["music_files"], MUSIC_CACHE["music_file_names"] = get_music_files(MUSIC_CACHE["music_dir"],
|
|
|
MUSIC_CACHE["music_ext"])
|
|
|
MUSIC_CACHE["scan_time"] = time.time()
|
|
|
return MUSIC_CACHE
|
|
|
|
|
|
|
|
|
async def handle_music_command(conn, text):
|
|
|
initialize_music_handler(conn)
|
|
|
global MUSIC_CACHE
|
|
|
|
|
|
"""处理音乐播放指令"""
|
|
|
clean_text = re.sub(r'[^\w\s]', '', text).strip()
|
|
|
logger.bind(tag=TAG).debug(f"检查是否是音乐命令: {clean_text}")
|
|
|
|
|
|
# 尝试匹配具体歌名
|
|
|
if os.path.exists(MUSIC_CACHE["music_dir"]):
|
|
|
if time.time() - MUSIC_CACHE["scan_time"] > MUSIC_CACHE["refresh_time"]:
|
|
|
# 刷新音乐文件列表
|
|
|
MUSIC_CACHE["music_files"], MUSIC_CACHE["music_file_names"] = get_music_files(MUSIC_CACHE["music_dir"],
|
|
|
MUSIC_CACHE["music_ext"])
|
|
|
MUSIC_CACHE["scan_time"] = time.time()
|
|
|
|
|
|
potential_song = _extract_song_name(clean_text)
|
|
|
if potential_song:
|
|
|
best_match = _find_best_match(potential_song, MUSIC_CACHE["music_files"])
|
|
|
if best_match:
|
|
|
logger.bind(tag=TAG).info(f"找到最匹配的歌曲: {best_match}")
|
|
|
await play_local_music(conn, specific_file=best_match)
|
|
|
return True
|
|
|
# 检查是否是通用播放音乐命令
|
|
|
await play_local_music(conn)
|
|
|
return True
|
|
|
|
|
|
|
|
|
async def play_local_music(conn, specific_file=None):
|
|
|
global MUSIC_CACHE
|
|
|
"""播放本地音乐文件"""
|
|
|
try:
|
|
|
if not os.path.exists(MUSIC_CACHE["music_dir"]):
|
|
|
logger.bind(tag=TAG).error(f"音乐目录不存在: " + MUSIC_CACHE["music_dir"])
|
|
|
return
|
|
|
|
|
|
# 确保路径正确性
|
|
|
if specific_file:
|
|
|
selected_music = specific_file
|
|
|
music_path = os.path.join(MUSIC_CACHE["music_dir"], specific_file)
|
|
|
else:
|
|
|
if not MUSIC_CACHE["music_files"]:
|
|
|
logger.bind(tag=TAG).error("未找到MP3音乐文件")
|
|
|
return
|
|
|
selected_music = random.choice(MUSIC_CACHE["music_files"])
|
|
|
music_path = os.path.join(MUSIC_CACHE["music_dir"], selected_music)
|
|
|
|
|
|
if not os.path.exists(music_path):
|
|
|
logger.bind(tag=TAG).error(f"选定的音乐文件不存在: {music_path}")
|
|
|
return
|
|
|
text = f"正在播放{selected_music}"
|
|
|
await send_stt_message(conn, text)
|
|
|
conn.tts_first_text_index = 0
|
|
|
conn.tts_last_text_index = 0
|
|
|
conn.llm_finish_task = True
|
|
|
if music_path.endswith(".p3"):
|
|
|
opus_packets, duration = p3.decode_opus_from_file(music_path)
|
|
|
else:
|
|
|
opus_packets, duration = conn.tts.audio_to_opus_data(music_path)
|
|
|
conn.audio_play_queue.put((opus_packets, selected_music, 0))
|
|
|
|
|
|
except Exception as e:
|
|
|
logger.bind(tag=TAG).error(f"播放音乐失败: {str(e)}")
|
|
|
logger.bind(tag=TAG).error(f"详细错误: {traceback.format_exc()}")
|