From 4ab3c5cbee1fe99c3ccd3fea17d20fcab79b0c29 Mon Sep 17 00:00:00 2001
From: Hi-Jiajun <3138947285@qq.com>
Date: Sat, 24 Aug 2024 09:11:14 +0000
Subject: [PATCH] feat: Add feature as requested in issue #143
---
Dockerfile | 3 ++
config-example.json | 7 +++--
xiaomusic/config.py | 5 ++-
xiaomusic/convert_to_mp3.py | 59 +++++++++++++++++++++++++++++++++++
xiaomusic/static/setting.html | 8 ++++-
xiaomusic/utils.py | 1 +
xiaomusic/xiaomusic.py | 54 ++++++++++++++++++++++++++++++--
7 files changed, 129 insertions(+), 8 deletions(-)
create mode 100644 xiaomusic/convert_to_mp3.py
diff --git a/Dockerfile b/Dockerfile
index aedcd4b..a70aac0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,6 +12,9 @@ COPY install_dependencies.sh .
RUN bash install_dependencies.sh
FROM python:3.10-slim
+RUN pip install pydub
+RUN python3 -m venv /app/.venv
+RUN /app/.venv/bin/pip install pydub
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/ffmpeg /app/ffmpeg
diff --git a/config-example.json b/config-example.json
index 1444065..36c11e2 100644
--- a/config-example.json
+++ b/config-example.json
@@ -12,7 +12,7 @@
"public_port": 0,
"proxy": null,
"search_prefix": "bilisearch:",
- "ffmpeg_location": "./ffmpeg/bin",
+ "ffmpeg_location": "./ffmpeg/bin/ffmpeg",
"active_cmd": "play,set_random_play,playlocal,play_music_list,stop",
"exclude_dirs": "@eaDir",
"music_path_depth": 10,
@@ -77,5 +77,6 @@
},
"enable_force_stop": false,
"devices": {},
- "group_list": ""
-}
\ No newline at end of file
+ "group_list": "",
+ "convert_to_mp3": false
+}
diff --git a/xiaomusic/config.py b/xiaomusic/config.py
index cac3e5a..33544d9 100644
--- a/xiaomusic/config.py
+++ b/xiaomusic/config.py
@@ -83,7 +83,7 @@ class Config:
search_prefix: str = os.getenv(
"XIAOMUSIC_SEARCH", "bilisearch:"
) # "bilisearch:" or "ytsearch:"
- ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin")
+ ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin/ffmpeg")
active_cmd: str = os.getenv(
"XIAOMUSIC_ACTIVE_CMD", "play,set_random_play,playlocal,play_music_list,stop"
)
@@ -136,6 +136,9 @@ class Config:
remove_id3tag: bool = (
os.getenv("XIAOMUSIC_REMOVE_ID3TAG", "false").lower() == "true"
)
+ convert_to_mp3: bool = (
+ os.getenv("CONVERT_TO_MP3", "false").lower() == "true"
+ )
delay_sec: int = int(os.getenv("XIAOMUSIC_DELAY_SEC", 3)) # 下一首歌延迟播放秒数
def append_keyword(self, keys, action):
diff --git a/xiaomusic/convert_to_mp3.py b/xiaomusic/convert_to_mp3.py
new file mode 100644
index 0000000..959f3b3
--- /dev/null
+++ b/xiaomusic/convert_to_mp3.py
@@ -0,0 +1,59 @@
+# convert_to_mp3.py
+import os
+import subprocess
+import tempfile
+from pydub import AudioSegment
+from pydub.playback import play
+from xiaomusic.config import Config
+
+class Convert_To_MP3:
+ def __init__(self, config: Config):
+ self.config = config
+ self.music_path = self.config.music_path
+ self.ffmpeg_location = self.config.ffmpeg_location
+
+ @staticmethod
+ def convert_to_mp3(input_file: str, ffmpeg_location: str, music_path: str) -> str:
+ """
+ Convert the music file to MP3 format and return the path of the temporary MP3 file.
+ """
+ # 指定临时文件的目录为 music_path 目录下的 tmp 文件夹
+ temp_dir = os.path.join(music_path, 'tmp')
+ if not os.path.exists(temp_dir):
+ os.makedirs(temp_dir) # 确保目录存在
+
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3', dir=temp_dir)
+ temp_file.close()
+ temp_file_path = temp_file.name
+
+ command = [
+ ffmpeg_location,
+ '-i', input_file,
+ '-f', 'mp3',
+ '-y',
+ temp_file_path
+ ]
+
+ try:
+ subprocess.run(command, check=True)
+ except subprocess.CalledProcessError as e:
+ print(f"Error during conversion: {e}")
+ return None
+
+ return temp_file_path
+
+ @classmethod
+ def convert_and_play(cls, input_file: str, ffmpeg_location: str):
+ """
+ 将音乐文件转码为 MP3 格式,播放,然后不删除临时文件,依赖于 xiaomusic 启动时的清理逻辑。
+ """
+ temp_mp3_file = cls.convert_to_mp3(input_file, ffmpeg_location, cls.music_path)
+ if temp_mp3_file:
+ try:
+ # 假设 xiaomusic_playmusic 是一个播放 MP3 文件的函数
+ cls.xiaomusic.xiaomusic_playmusic(temp_mp3_file)
+ finally:
+ # 此处不再删除临时文件,依赖 xiaomusic 的清理逻辑
+ pass
+ else:
+ print("Conversion failed")
\ No newline at end of file
diff --git a/xiaomusic/static/setting.html b/xiaomusic/static/setting.html
index 07208de..30af917 100644
--- a/xiaomusic/static/setting.html
+++ b/xiaomusic/static/setting.html
@@ -98,7 +98,13 @@ var vConsole = new window.VConsole();
+
+
+
diff --git a/xiaomusic/utils.py b/xiaomusic/utils.py
index e1810c7..aa3b512 100644
--- a/xiaomusic/utils.py
+++ b/xiaomusic/utils.py
@@ -12,6 +12,7 @@ import re
import shutil
import string
import tempfile
+import subprocess
from collections.abc import AsyncIterator
from http.cookies import SimpleCookie
from urllib.parse import urlparse
diff --git a/xiaomusic/xiaomusic.py b/xiaomusic/xiaomusic.py
index 94b7f39..140667c 100644
--- a/xiaomusic/xiaomusic.py
+++ b/xiaomusic/xiaomusic.py
@@ -45,6 +45,8 @@ from xiaomusic.utils import (
traverse_music_directory,
)
+from xiaomusic.convert_to_mp3 import Convert_To_MP3
+
class XiaoMusic:
def __init__(self, config: Config):
@@ -64,6 +66,12 @@ class XiaoMusic:
self.devices = {} # key 为 did
self.running_task = []
+ # 在程序启动时调用清理函数
+ self.cleanup_old_temp_files()
+
+ self.convert_to_mp3 = self.config.convert_to_mp3
+ self.ffmpeg_location = self.config.ffmpeg_location
+ self.music_path = self.config.music_path
# 初始化配置
self.init_config()
@@ -107,6 +115,7 @@ class XiaoMusic:
self.exclude_dirs = set(self.config.exclude_dirs.split(","))
self.music_path_depth = self.config.music_path_depth
self.remove_id3tag = self.config.remove_id3tag
+ self.convert_to_mp3 = self.config.convert_to_mp3
def update_devices(self):
self.device_id_did = {} # key 为 device_id
@@ -318,6 +327,23 @@ class XiaoMusic:
return filename
return ""
+ def cleanup_old_temp_files(self):
+ """
+ 清理在 /tmp 目录下旧的临时 MP3 文件。
+ """
+ temp_dir = '/tmp' # 临时文件存储的目录
+ file_ext = '.mp3' # 临时文件的扩展名
+ try:
+ for filename in os.listdir(temp_dir):
+ if filename.endswith(file_ext):
+ file_path = os.path.join(temp_dir, filename)
+ # 如果文件是超过一天的旧文件,则删除它
+ if (time.time() - os.path.getmtime(file_path)) > 86400:
+ os.remove(file_path)
+ self.log.info(f"Deleted old temporary file: {file_path}")
+ except Exception as e:
+ self.log.error(f"Failed to cleanup old temp files: {e}")
+
# 判断本地音乐是否存在,网络歌曲不判断
def is_music_exist(self, name):
if name not in self.all_music:
@@ -381,15 +407,37 @@ class XiaoMusic:
else:
self.log.info("No ID3 tag remove needed")
+ # 如果开启了MP3转换功能,且文件不是MP3格式,则进行转换
+ if self.convert_to_mp3 and not is_mp3(filename):
+ self.log.info(f"convert_to_mp3 is enabled. Checking file: {filename}")
+ temp_mp3_file = self.convert_file_to_mp3(filename)
+ if temp_mp3_file:
+ # 转换成功后,修改文件名为music_path/tmp下的相对路径
+ relative_path = os.path.relpath(temp_mp3_file, self.config.music_path)
+ self.log.info(f"Converted file: {temp_mp3_file} to {relative_path}")
+ filename = relative_path
+ else:
+ self.log.warning(f"Failed to convert file to MP3 format: {filename}")
+ return "" # 转换失败,返回空字符串表示无法获取播放URL
+
+ # 构造音乐文件的URL
filename = filename.replace("\\", "/")
if filename.startswith(self.config.music_path):
- filename = filename[len(self.config.music_path) :]
+ filename = filename[len(self.config.music_path):]
if filename.startswith("/"):
filename = filename[1:]
- self.log.info(f"get_music_url local music. name:{name}, filename:{filename}")
encoded_name = urllib.parse.quote(filename)
return f"http://{self.hostname}:{self.public_port}/music/{encoded_name}"
+ def convert_file_to_mp3(self, input_file):
+ """
+ Convert the file to MP3 format using convert_to_mp3.py.
+ """
+ # 创建 Convert_To_MP3 类的实例,只传递 config 对象
+ converter = Convert_To_MP3(self.config)
+ # 调用静态方法 convert_to_mp3,并传递所需的文件路径和 ffmpeg 位置
+ return converter.convert_to_mp3(input_file, self.ffmpeg_location, self.music_path)
+
# 获取目录下所有歌曲,生成随机播放列表
def _gen_all_music_list(self):
self.all_music = {}
@@ -404,7 +452,7 @@ class XiaoMusic:
if len(files) == 0:
continue
if dir_name == os.path.basename(self.music_path):
- dir_name = "其他"
+ dir_name = "默认"
if self.music_path != self.download_path and dir_name == os.path.basename(
self.download_path
):