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}"')