diff --git a/.gitignore b/.gitignore
index d68d0a6..f697377 100644
--- a/.gitignore
+++ b/.gitignore
@@ -168,3 +168,4 @@ conf
setting.json
.DS_Store
cache
+tmp/
diff --git a/test/test_remove_common_prefix.py b/test/test_remove_common_prefix.py
new file mode 100644
index 0000000..33f177b
--- /dev/null
+++ b/test/test_remove_common_prefix.py
@@ -0,0 +1,8 @@
+from xiaomusic.utils import (
+ remove_common_prefix,
+)
+
+if __name__ == "__main__":
+ remove_common_prefix(
+ "./tmp/【无损音质】2024年9月酷狗热歌榜TOP100合集(只选热歌最高的)首首王炸,分P合集!"
+ )
diff --git a/xiaomusic/httpserver.py b/xiaomusic/httpserver.py
index 6b8acf3..edafc18 100644
--- a/xiaomusic/httpserver.py
+++ b/xiaomusic/httpserver.py
@@ -25,8 +25,11 @@ from starlette.responses import FileResponse, Response
from xiaomusic import __version__
from xiaomusic.utils import (
deepcopy_data_no_sensitive_info,
+ download_one_music,
+ download_playlist,
downloadfile,
get_latest_version,
+ remove_common_prefix,
)
xiaomusic = None
@@ -369,6 +372,58 @@ async def latest_version(Verifcation=Depends(verification)):
return {"ret": "Fetch version failed"}
+class DownloadPlayList(BaseModel):
+ dirname: str
+ url: str
+
+
+# 下载歌单
+@app.post("/downloadplaylist")
+async def downloadplaylist(data: DownloadPlayList, Verifcation=Depends(verification)):
+ try:
+ download_proc = await download_playlist(config, data.url, data.dirname)
+
+ async def check_download_proc():
+ # 等待子进程完成
+ exit_code = await download_proc.wait()
+ log.info(f"Download completed with exit code {exit_code}")
+
+ dir_path = os.path.join(config.download_path, data.dirname)
+ log.debug(f"Download dir_path: {exit_code}")
+
+ if exit_code == 0:
+ log.info("Download was successful.")
+ # 执行成功的后续逻辑:文件名处理
+ remove_common_prefix(dir_path)
+ else:
+ # 处理失败的情况
+ log.error("Download failed.")
+
+ asyncio.create_task(check_download_proc())
+ return {"ret": "OK"}
+ except Exception as e:
+ log.exception(f"Execption {e}")
+
+ return {"ret": "Failed download"}
+
+
+class DownloadOneMusic(BaseModel):
+ name: str = ""
+ url: str
+
+
+# 下载单首歌曲
+@app.post("/downloadonemusic")
+async def downloadonemusic(data: DownloadOneMusic, Verifcation=Depends(verification)):
+ try:
+ await download_one_music(config, data.url, data.name)
+ return {"ret": "OK"}
+ except Exception as e:
+ log.exception(f"Execption {e}")
+
+ return {"ret": "Failed download"}
+
+
async def file_iterator(file_path, start, end):
async with aiofiles.open(file_path, mode="rb") as file:
await file.seek(start)
diff --git a/xiaomusic/static/default/debug.html b/xiaomusic/static/default/debug.html
index 4a89882..cb02326 100644
--- a/xiaomusic/static/default/debug.html
+++ b/xiaomusic/static/default/debug.html
@@ -6,9 +6,9 @@
Debug For XiaoMusic
-
+
-
+
diff --git a/xiaomusic/static/default/downloadtool.html b/xiaomusic/static/default/downloadtool.html
new file mode 100644
index 0000000..022b2db
--- /dev/null
+++ b/xiaomusic/static/default/downloadtool.html
@@ -0,0 +1,113 @@
+
+
+
+
+
+ 歌曲下载工具
+
+
+
+
+
+
+
+
+
+
+ 歌曲下载工具
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xiaomusic/static/default/index.html b/xiaomusic/static/default/index.html
index ef23a2f..7fbd8a7 100644
--- a/xiaomusic/static/default/index.html
+++ b/xiaomusic/static/default/index.html
@@ -4,9 +4,9 @@
小爱音箱操控面板
-
-
-
+
+
+
diff --git a/xiaomusic/static/default/m3u.html b/xiaomusic/static/default/m3u.html
index 2a749df..b45d125 100644
--- a/xiaomusic/static/default/m3u.html
+++ b/xiaomusic/static/default/m3u.html
@@ -5,7 +5,7 @@
M3U to JSON Converter
-
+
diff --git a/xiaomusic/static/default/setting.html b/xiaomusic/static/default/setting.html
index 214457c..c067499 100644
--- a/xiaomusic/static/default/setting.html
+++ b/xiaomusic/static/default/setting.html
@@ -4,9 +4,9 @@
小爱音箱操控面板
-
-
-
+
+
+
@@ -195,11 +195,12 @@ var vConsole = new window.VConsole();
+ 下载日志文件
- 下载日志文件
m3u文件转换
+ 歌曲下载工具
调试工具
diff --git a/xiaomusic/utils.py b/xiaomusic/utils.py
index d186b1d..64b6113 100644
--- a/xiaomusic/utils.py
+++ b/xiaomusic/utils.py
@@ -674,3 +674,103 @@ def extract_audio_metadata(file_path, save_root):
metadata.artist = _get_tag_value(tags, "Artist")
return asdict(metadata)
+
+
+# 下载播放列表
+async def download_playlist(config, url, dirname):
+ title = f"{dirname}/%(title)s.%(ext)s"
+ sbp_args = (
+ "yt-dlp",
+ "--yes-playlist",
+ "-x",
+ "--audio-format",
+ "mp3",
+ "--paths",
+ config.download_path,
+ "-o",
+ title,
+ "--ffmpeg-location",
+ f"{config.ffmpeg_location}",
+ )
+
+ if config.proxy:
+ sbp_args += ("--proxy", f"{config.proxy}")
+
+ sbp_args += (url,)
+
+ cmd = " ".join(sbp_args)
+ logging.info(f"download_playlist: {cmd}")
+ download_proc = await asyncio.create_subprocess_exec(*sbp_args)
+ return download_proc
+
+
+# 下载一首歌曲
+async def download_one_music(config, url, name=""):
+ title = "%(title)s.%(ext)s"
+ if name:
+ title = f"{name}.%(ext)s"
+ sbp_args = (
+ "yt-dlp",
+ "--no-playlist",
+ "-x",
+ "--audio-format",
+ "mp3",
+ "--paths",
+ config.download_path,
+ "-o",
+ title,
+ "--ffmpeg-location",
+ f"{config.ffmpeg_location}",
+ )
+
+ if config.proxy:
+ sbp_args += ("--proxy", f"{config.proxy}")
+
+ sbp_args += (url,)
+
+ cmd = " ".join(sbp_args)
+ logging.info(f"download_one_music: {cmd}")
+ download_proc = await asyncio.create_subprocess_exec(*sbp_args)
+ return download_proc
+
+
+def _longest_common_prefix(file_names):
+ if not file_names:
+ return ""
+
+ # 将第一个文件名作为初始前缀
+ prefix = file_names[0]
+
+ for file_name in file_names[1:]:
+ while not file_name.startswith(prefix):
+ # 如果当前文件名不以prefix开头,则缩短prefix
+ prefix = prefix[:-1]
+ if not prefix:
+ return ""
+
+ return prefix
+
+
+# 移除目录下文件名前缀相同的
+def remove_common_prefix(directory):
+ files = os.listdir(directory)
+
+ # 获取所有文件的前缀
+ common_prefix = _longest_common_prefix(files)
+
+ logging.info(f'Common prefix identified: "{common_prefix}"')
+
+ for filename in files:
+ if filename == common_prefix:
+ continue
+ # 检查文件名是否以共同前缀开头
+ if filename.startswith(common_prefix):
+ # 构造新的文件名
+ new_filename = filename[len(common_prefix) :]
+ # 生成完整的文件路径
+ old_file_path = os.path.join(directory, filename)
+ new_file_path = os.path.join(directory, new_filename)
+
+ # 重命名文件
+ os.rename(old_file_path, new_file_path)
+ logging.debug(f'Renamed: "{filename}" to "{new_filename}"')