Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

音乐播放功能添加p3文件格式支持,添加文件目录(含子目录)扫描和文件列表刷新功能 #157

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,9 @@ music:
- "我想听歌"
- "我要听歌"
- "放点音乐"
music_dir: "./music" # 音乐文件存放路径
music_dir: "./music" # 音乐文件存放路径,将从该目录及子目录下搜索音乐文件
music_ext: # 音乐文件类型,p3格式效率最高
- ".mp3"
- ".wav"
- ".p3"
refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒
59 changes: 51 additions & 8 deletions core/handle/musicHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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):
Expand All @@ -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):
"""处理音乐播放指令"""
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
33 changes: 33 additions & 0 deletions core/utils/p3.py
Original file line number Diff line number Diff line change
@@ -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