mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-26 00:53:54 +08:00
音乐播放功能添加p3文件格式支持,添加文件目录(含子目录)扫描和文件列表刷新功能 (#157)
Co-authored-by: hrz <1710360675@qq.com>
This commit is contained in:
+6
-1
@@ -327,4 +327,9 @@ music:
|
|||||||
- "我想听歌"
|
- "我想听歌"
|
||||||
- "我要听歌"
|
- "我要听歌"
|
||||||
- "放点音乐"
|
- "放点音乐"
|
||||||
music_dir: "./music" # 音乐文件存放路径
|
music_dir: "./music" # 音乐文件存放路径,将从该目录及子目录下搜索音乐文件
|
||||||
|
music_ext: # 音乐文件类型,p3格式效率最高
|
||||||
|
- ".mp3"
|
||||||
|
- ".wav"
|
||||||
|
- ".p3"
|
||||||
|
refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import random
|
|||||||
import difflib
|
import difflib
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
from pathlib import Path
|
||||||
|
import time
|
||||||
from core.handle.sendAudioHandle import send_stt_message
|
from core.handle.sendAudioHandle import send_stt_message
|
||||||
|
from core.utils import p3
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
@@ -33,6 +36,24 @@ def _find_best_match(potential_song, music_files):
|
|||||||
best_match = music_file
|
best_match = music_file
|
||||||
return best_match
|
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:
|
class MusicHandler:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
@@ -45,10 +66,19 @@ class MusicHandler:
|
|||||||
self.music_config.get("music_dir", "./music") # 默认路径修改
|
self.music_config.get("music_dir", "./music") # 默认路径修改
|
||||||
)
|
)
|
||||||
self.music_related_keywords = self.music_config.get("music_commands", [])
|
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:
|
else:
|
||||||
self.music_dir = os.path.abspath("./music")
|
self.music_dir = os.path.abspath("./music")
|
||||||
self.music_related_keywords = ["来一首歌", "唱一首歌", "播放音乐", "来点音乐", "背景音乐", "放首歌",
|
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):
|
async def handle_music_command(self, conn, text):
|
||||||
"""处理音乐播放指令"""
|
"""处理音乐播放指令"""
|
||||||
@@ -57,12 +87,15 @@ class MusicHandler:
|
|||||||
|
|
||||||
# 尝试匹配具体歌名
|
# 尝试匹配具体歌名
|
||||||
if os.path.exists(self.music_dir):
|
if os.path.exists(self.music_dir):
|
||||||
music_files = [f for f in os.listdir(self.music_dir) if f.endswith('.mp3')]
|
if time.time() - self.scan_time > self.refresh_time:
|
||||||
logger.bind(tag=TAG).debug(f"找到的音乐文件: {music_files}")
|
# 刷新音乐文件列表
|
||||||
|
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)
|
potential_song = _extract_song_name(clean_text)
|
||||||
if potential_song:
|
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:
|
if best_match:
|
||||||
logger.bind(tag=TAG).info(f"找到最匹配的歌曲: {best_match}")
|
logger.bind(tag=TAG).info(f"找到最匹配的歌曲: {best_match}")
|
||||||
await self.play_local_music(conn, specific_file=best_match)
|
await self.play_local_music(conn, specific_file=best_match)
|
||||||
@@ -90,19 +123,29 @@ class MusicHandler:
|
|||||||
return
|
return
|
||||||
selected_music = specific_file
|
selected_music = specific_file
|
||||||
else:
|
else:
|
||||||
music_files = [f for f in os.listdir(self.music_dir) if f.endswith('.mp3')]
|
if time.time() - self.scan_time > self.refresh_time:
|
||||||
if not music_files:
|
# 刷新音乐文件列表
|
||||||
|
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音乐文件")
|
logger.bind(tag=TAG).error("未找到MP3音乐文件")
|
||||||
return
|
return
|
||||||
selected_music = random.choice(music_files)
|
selected_music = random.choice(self.music_files)
|
||||||
music_path = os.path.join(self.music_dir, selected_music)
|
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}"
|
text = f"正在播放{selected_music}"
|
||||||
await send_stt_message(conn, text)
|
await send_stt_message(conn, text)
|
||||||
conn.tts_first_text = selected_music
|
conn.tts_first_text = selected_music
|
||||||
conn.tts_last_text = selected_music
|
conn.tts_last_text = selected_music
|
||||||
conn.llm_finish_task = True
|
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))
|
conn.audio_play_queue.put((opus_packets, selected_music))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user