feat: 新增批量下载歌曲工具
This commit is contained in:
parent
4951cea269
commit
4330f61888
1
.gitignore
vendored
1
.gitignore
vendored
@ -168,3 +168,4 @@ conf
|
||||
setting.json
|
||||
.DS_Store
|
||||
cache
|
||||
tmp/
|
||||
|
8
test/test_remove_common_prefix.py
Normal file
8
test/test_remove_common_prefix.py
Normal file
@ -0,0 +1,8 @@
|
||||
from xiaomusic.utils import (
|
||||
remove_common_prefix,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
remove_common_prefix(
|
||||
"./tmp/【无损音质】2024年9月酷狗热歌榜TOP100合集(只选热歌最高的)首首王炸,分P合集!"
|
||||
)
|
@ -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)
|
||||
|
@ -6,9 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Debug For XiaoMusic</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727033309">
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727434231">
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script src="./jquery-3.7.1.min.js?version=1727033309"></script>
|
||||
<script src="./jquery-3.7.1.min.js?version=1727434231"></script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
|
113
xiaomusic/static/default/downloadtool.html
Normal file
113
xiaomusic/static/default/downloadtool.html
Normal file
@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>歌曲下载工具</title>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727434231">
|
||||
<script src="./jquery-3.7.1.min.js?version=1727434231"></script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments)};
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-Z09NC1K7ZW');
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>歌曲下载工具</h1>
|
||||
|
||||
<div class="rows">
|
||||
<!-- 歌单的输入 -->
|
||||
<label for="playlistUrl">输入歌单 URL:</label>
|
||||
<input type="text" id="playlistUrl" value="https://m.bilibili.com/video/BV1WUsDezE88">
|
||||
|
||||
<label for="dirname">输入歌单名字:</label>
|
||||
<input type="text" id="dirname" placeholder="流行歌曲">
|
||||
|
||||
<button id="downloadPlaylistBtn">下载歌单</button>
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="rows">
|
||||
|
||||
<!-- 单曲的输入 -->
|
||||
<label for="songUrl">输入歌曲 URL:</label>
|
||||
<input type="text" id="songUrl" value="https://m.bilibili.com/video/BV1qD4y1U7fs">
|
||||
|
||||
<label for="songName">输入歌曲名字:</label>
|
||||
<input type="text" id="songName" placeholder="歌曲名">
|
||||
|
||||
<button id="downloadSongBtn">下载单曲</button>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// 下载歌单
|
||||
$('#downloadPlaylistBtn').click(function() {
|
||||
var playlistUrl = $('#playlistUrl').val();
|
||||
var dirname = $('#dirname').val();
|
||||
|
||||
if (!playlistUrl || !dirname) {
|
||||
alert('请填写完整的歌单 URL 和歌单名字');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
dirname: dirname,
|
||||
url: playlistUrl
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/downloadplaylist",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: (msg) => {
|
||||
alert('歌单下载请求已发送!');
|
||||
console.log(response);
|
||||
},
|
||||
error: (msg) => {
|
||||
alert('歌单下载请求失败,请重试。');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 下载单曲
|
||||
$('#downloadSongBtn').click(function() {
|
||||
var songName = $('#songName').val();
|
||||
var songUrl = $('#songUrl').val();
|
||||
|
||||
if (!songUrl || !songName) {
|
||||
alert('请填写完整的歌曲 URL 和歌曲名字');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
name: songName,
|
||||
url: songUrl
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/downloadonemusic",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: (msg) => {
|
||||
alert('单曲下载请求已发送!');
|
||||
console.log(response);
|
||||
},
|
||||
error: (msg) => {
|
||||
alert('单曲下载请求失败,请重试。');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -4,9 +4,9 @@
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="./jquery-3.7.1.min.js?version=1727033309"></script>
|
||||
<script src="./app.js?version=1727033309"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727033309">
|
||||
<script src="./jquery-3.7.1.min.js?version=1727434231"></script>
|
||||
<script src="./app.js?version=1727434231"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727434231">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>M3U to JSON Converter</title>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727033309">
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727434231">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
|
@ -4,9 +4,9 @@
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="./jquery-3.7.1.min.js?version=1727033309"></script>
|
||||
<script src="./setting.js?version=1727033309"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727033309">
|
||||
<script src="./jquery-3.7.1.min.js?version=1727434231"></script>
|
||||
<script src="./setting.js?version=1727434231"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1727434231">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
@ -195,11 +195,12 @@ var vConsole = new window.VConsole();
|
||||
|
||||
<button id="get_music_list">获取歌单</button>
|
||||
<button id="refresh_music_tag">刷新tag</button>
|
||||
<a class="button" href="/downloadlog" download="xiaomusic.txt">下载日志文件</a>
|
||||
<hr>
|
||||
|
||||
<a class="button" href="/downloadlog" download="xiaomusic.txt">下载日志文件</a>
|
||||
<button onclick="location.href='/docs';">查看接口文档</button>
|
||||
<a class="button" href="./m3u.html" target="_blank">m3u文件转换</a>
|
||||
<a class="button" href="./downloadtool.html" target="_blank">歌曲下载工具</a>
|
||||
<hr>
|
||||
|
||||
<a class="button" href="./debug.html" target="_blank">调试工具</a>
|
||||
|
@ -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}"')
|
||||
|
Loading…
Reference in New Issue
Block a user