diff --git a/config.yaml b/config.yaml index fd8c260..fab43d0 100644 --- a/config.yaml +++ b/config.yaml @@ -327,4 +327,9 @@ music: - "我想听歌" - "我要听歌" - "放点音乐" - music_dir: "./music" # 音乐文件存放路径 \ No newline at end of file + music_dir: "./music" # 音乐文件存放路径,将从该目录及子目录下搜索音乐文件 + music_ext: # 音乐文件类型,p3格式效率最高 + - ".mp3" + - ".wav" + - ".p3" + refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒 diff --git a/core/handle/musicHandler.py b/core/handle/musicHandler.py index 672550c..f1eb37e 100644 --- a/core/handle/musicHandler.py +++ b/core/handle/musicHandler.py @@ -4,7 +4,10 @@ import difflib import re import traceback +from pathlib import Path +import time from core.handle.sendAudioHandle import send_stt_message +from core.utils import p3 TAG = __name__ logger = setup_logging() @@ -33,6 +36,24 @@ def _find_best_match(potential_song, music_files): best_match = music_file return best_match +class MusicManager: + def __init__(self, music_dir, music_ext): + self.music_dir = Path(music_dir) + self.music_ext = music_ext + + def get_music_files(self): + music_files = [] + for file in self.music_dir.rglob("*"): + # 判断是否是文件 + if file.is_file(): + # 获取文件扩展名 + ext = file.suffix.lower() + # 判断扩展名是否在列表中 + if ext in self.music_ext: + # music_files.append(str(file.resolve())) # 添加绝对路径 + # 添加相对路径 + music_files.append(str(file.relative_to(self.music_dir))) + return music_files class MusicHandler: def __init__(self, config): @@ -45,10 +66,19 @@ def __init__(self, config): self.music_config.get("music_dir", "./music") # 默认路径修改 ) self.music_related_keywords = self.music_config.get("music_commands", []) + self.music_ext = self.music_config.get("music_ext", (".mp3", ".wav", ".p3")) + self.refresh_time = self.music_config.get("refresh_time", 60) else: self.music_dir = os.path.abspath("./music") self.music_related_keywords = ["来一首歌", "唱一首歌", "播放音乐", "来点音乐", "背景音乐", "放首歌", "播放歌曲", "来点背景音乐", "我想听歌", "我要听歌", "放点音乐"] + self.music_ext = (".mp3", ".wav", ".p3") + self.refresh_time = 60 + + # 获取音乐文件列表 + self.music_files = MusicManager(self.music_dir, self.music_ext).get_music_files() + self.scan_time = time.time() + logger.bind(tag=TAG).debug(f"找到的音乐文件: {self.music_files}") async def handle_music_command(self, conn, text): """处理音乐播放指令""" @@ -57,12 +87,15 @@ async def handle_music_command(self, conn, text): # 尝试匹配具体歌名 if os.path.exists(self.music_dir): - music_files = [f for f in os.listdir(self.music_dir) if f.endswith('.mp3')] - logger.bind(tag=TAG).debug(f"找到的音乐文件: {music_files}") + if time.time() - self.scan_time > self.refresh_time: + # 刷新音乐文件列表 + self.music_files = MusicManager(self.music_dir, self.music_ext).get_music_files() + self.scan_time = time.time() + logger.bind(tag=TAG).debug(f"刷新的音乐文件: {self.music_files}") potential_song = _extract_song_name(clean_text) if potential_song: - best_match = _find_best_match(potential_song, music_files) + best_match = _find_best_match(potential_song, self.music_files) if best_match: logger.bind(tag=TAG).info(f"找到最匹配的歌曲: {best_match}") await self.play_local_music(conn, specific_file=best_match) @@ -90,19 +123,29 @@ async def play_local_music(self, conn, specific_file=None): return selected_music = specific_file else: - music_files = [f for f in os.listdir(self.music_dir) if f.endswith('.mp3')] - if not music_files: + if time.time() - self.scan_time > self.refresh_time: + # 刷新音乐文件列表 + self.music_files = MusicManager(self.music_dir, self.music_ext).get_music_files() + self.scan_time = time.time() + logger.bind(tag=TAG).debug(f"刷新的音乐文件列表: {self.music_files}") + + if not self.music_files: logger.bind(tag=TAG).error("未找到MP3音乐文件") return - selected_music = random.choice(music_files) + selected_music = random.choice(self.music_files) music_path = os.path.join(self.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 = selected_music conn.tts_last_text = selected_music conn.llm_finish_task = True - opus_packets, duration = conn.tts.wav_to_opus_data(music_path) - + if music_path.endswith(".p3"): + opus_packets, duration = p3.decode_opus_from_file(music_path) + else: + opus_packets, duration = conn.tts.wav_to_opus_data(music_path) conn.audio_play_queue.put((opus_packets, selected_music)) except Exception as e: diff --git a/core/utils/p3.py b/core/utils/p3.py new file mode 100644 index 0000000..a022a2d --- /dev/null +++ b/core/utils/p3.py @@ -0,0 +1,33 @@ +import struct + +def decode_opus_from_file(input_file): + """ + 从p3文件中解码 Opus 数据,并返回一个 Opus 数据包的列表以及总时长。 + """ + opus_datas = [] + total_frames = 0 + sample_rate = 16000 # 文件采样率 + frame_duration_ms = 60 # 帧时长 + frame_size = int(sample_rate * frame_duration_ms / 1000) + + with open(input_file, 'rb') as f: + while True: + # 读取头部(4字节):[1字节类型,1字节保留,2字节长度] + header = f.read(4) + if not header: + break + + # 解包头部信息 + _, _, data_len = struct.unpack('>BBH', header) + + # 根据头部指定的长度读取 Opus 数据 + opus_data = f.read(data_len) + if len(opus_data) != data_len: + raise ValueError(f"Data length({len(opus_data)}) mismatch({data_len}) in the file.") + + opus_datas.append(opus_data) + total_frames += 1 + + # 计算总时长 + total_duration = (total_frames * frame_duration_ms) / 1000.0 + return opus_datas, total_duration \ No newline at end of file