修复默认下载画质无法同步的问题

This commit is contained in:
CSSZYF 2025-06-05 20:32:16 +08:00 committed by GitHub
parent 63d24efd6b
commit fdc109e3a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -205,21 +205,97 @@ async function axelDownloadBFile(url, fullFileName, progressCallback, videoDownl
*/ */
export async function getDownloadUrl(url, SESSDATA, qn) { export async function getDownloadUrl(url, SESSDATA, qn) {
const videoId = /video\/[^\?\/ ]+/.exec(url)[0].split("/")[1]; const videoId = /video\/[^\?\/ ]+/.exec(url)[0].split("/")[1];
// 转换画质数字为分辨率
let qualityText;
switch(parseInt(qn)) {
case 80: qualityText = "1080P"; break;
case 64: qualityText = "720P"; break;
case 32: qualityText = "480P"; break;
case 16: qualityText = "360P"; break;
default: qualityText = "480P"; break;
}
logger.info(`[R插件][BILI下载] 开始获取视频下载链接视频ID: ${videoId}, 请求画质: ${qualityText}`);
const dash = await getBiliVideoWithSession(videoId, "", SESSDATA, qn); const dash = await getBiliVideoWithSession(videoId, "", SESSDATA, qn);
// 获取关键信息 // 获取关键信息
const { video, audio } = dash; const { video, audio } = dash;
const videoData = video?.[0];
const audioData = audio?.[0]; // 根据请求的画质选择对应的视频流
// saveJsonToFile(dash); let targetHeight;
switch(parseInt(qn)) {
case 80: targetHeight = 1080; break; // 1080P
case 64: targetHeight = 720; break; // 720P
case 32: targetHeight = 480; break; // 480P
case 16: targetHeight = 360; break; // 360P
default: targetHeight = 480; // 默认480P
}
// 获取目标分辨率的所有视频流
let matchingVideos = video.filter(v => v.height === targetHeight);
// 如果找不到完全匹配的,找最接近但不超过目标分辨率的
if (matchingVideos.length === 0) {
matchingVideos = video
.filter(v => v.height <= targetHeight)
.sort((a, b) => b.height - a.height);
// 获取最高的可用分辨率的所有视频流
if (matchingVideos.length > 0) {
const maxHeight = matchingVideos[0].height;
matchingVideos = matchingVideos.filter(v => v.height === maxHeight);
}
}
// 如果还是找不到,使用所有可用的最低分辨率视频流
if (matchingVideos.length === 0) {
const minHeight = Math.min(...video.map(v => v.height));
matchingVideos = video.filter(v => v.height === minHeight);
}
// 在相同分辨率中选择编码优先级hevc > av1 > avc
let videoData;
if (matchingVideos.length > 0) {
// 记录编码信息
const codecInfo = matchingVideos.map(v =>
`${v.height}p(${v.codecs}): ${Math.round(v.bandwidth / 1024)}kbps`
).join(', ');
logger.debug(`[R插件][BILI下载] 可选编码: ${codecInfo}`);
// 按照编码和码率排序
const codecPriority = { hevc: 1, av1: 2, avc: 3 };
videoData = matchingVideos.sort((a, b) => {
const codecA = a.codecs.toLowerCase();
const codecB = b.codecs.toLowerCase();
// 获取编码类型的优先级
const priorityA = Object.entries(codecPriority).find(([key]) => codecA.includes(key))?.[1] || 999;
const priorityB = Object.entries(codecPriority).find(([key]) => codecB.includes(key))?.[1] || 999;
// 如果编码优先级相同,选择码率较低的
if (priorityA === priorityB) {
return a.bandwidth - b.bandwidth;
}
return priorityA - priorityB;
})[0];
}
if (!videoData) {
logger.error(`[R插件][BILI下载] 获取视频数据失败,请检查画质参数是否正确`);
return { videoUrl: null, audioUrl: null };
}
logger.debug(`[R插件][BILI下载] 请求画质: ${qualityText}, 实际获取画质: ${videoData.height}p分辨率: ${videoData.width}x${videoData.height}, 编码: ${videoData.codecs}, 码率: ${Math.round(videoData.bandwidth / 1024)}kbps`);
// 提取信息 // 提取信息
const { backupUrl: videoBackupUrl, baseUrl: videoBaseUrl } = videoData; const { backupUrl: videoBackupUrl, baseUrl: videoBaseUrl } = videoData;
const videoUrl = selectAndAvoidMCdnUrl(videoBaseUrl, videoBackupUrl); const videoUrl = selectAndAvoidMCdnUrl(videoBaseUrl, videoBackupUrl);
// 有部分视频可能存在没有音频例如https://www.bilibili.com/video/BV1CxvseRE8n/?p=1
// 音频处理 - 选择对应画质的音频流
const audioData = audio?.[0];
let audioUrl = null; let audioUrl = null;
if (audioData != null || audioData !== undefined) { if (audioData != null && audioData !== undefined) {
const { backupUrl: audioBackupUrl, baseUrl: audioBaseUrl } = audioData; const { backupUrl: audioBackupUrl, baseUrl: audioBaseUrl } = audioData;
audioUrl = selectAndAvoidMCdnUrl(audioBaseUrl, audioBackupUrl); audioUrl = selectAndAvoidMCdnUrl(audioBaseUrl, audioBackupUrl);
logger.debug(`[R插件][BILI下载] 音频码率: ${Math.round(audioData.bandwidth / 1024)}kbps`);
} }
return { videoUrl, audioUrl }; return { videoUrl, audioUrl };
} }
@ -330,23 +406,38 @@ export async function getBiliVideoWithSession(bvid, cid, SESSDATA, qn) {
if (!cid) { if (!cid) {
cid = await fetchCID(bvid).catch((err) => logger.error(err)) cid = await fetchCID(bvid).catch((err) => logger.error(err))
} }
logger.info(`[R插件][BILI请求审计]${ BILI_PLAY_STREAM const apiUrl = BILI_PLAY_STREAM
.replace("{bvid}", bvid) .replace("{bvid}", bvid)
.replace("{cid}", cid) .replace("{cid}", cid)
.replace("{qn}", qn) }`); .replace("{qn}", qn);
// 返回一个fetch的promise logger.debug(`[R插件][BILI请求审计] 请求URL: ${apiUrl}`);
return (new Promise((resolve, reject) => { return (new Promise((resolve, reject) => {
fetch(BILI_PLAY_STREAM fetch(apiUrl, {
.replace("{bvid}", bvid)
.replace("{cid}", cid)
.replace("{qn}", qn), {
headers: { headers: {
...BILI_HEADER, ...BILI_HEADER,
Cookie: `SESSDATA=${ SESSDATA }` Cookie: `SESSDATA=${SESSDATA}`
} }
}) })
.then(res => res.json()) .then(res => res.json())
.then(json => resolve(json.data.dash)); .then(json => {
if (json.code !== 0) {
logger.error(`[R插件][BILI请求审计] 请求失败: ${json.message}`);
reject(new Error(json.message));
} else {
// 记录每个视频流的画质信息
const qualityInfo = json.data.dash.video
.sort((a, b) => b.height - a.height) // 按分辨率从高到低排序
.map(v => `${v.height}p(${v.codecs}): ${Math.round(v.bandwidth / 1024)}kbps`)
.join(', ');
logger.debug(`[R插件][BILI请求审计] 请求成功,可用画质列表: ${qualityInfo}`);
resolve(json.data.dash);
}
})
.catch(err => {
logger.error(`[R插件][BILI请求审计] 请求异常: ${err.message}`);
reject(err);
});
})) }))
} }