mirror of
https://github.com/Jerryplusy/rc-plugin.git
synced 2025-12-06 02:11:56 +00:00
🐞 fix: 1.6.6 修复多线程下载问题
This commit is contained in:
parent
129553a685
commit
434643b758
120
apps/tools.js
120
apps/tools.js
@ -1548,19 +1548,23 @@ export class tools extends plugin {
|
|||||||
// 用户设置优先策略,逻辑解释:如果使用了这个函数优先查看用户是否设置了大于1的线程,如果设置了优先使用,没设置就开发者设定的函数设置
|
// 用户设置优先策略,逻辑解释:如果使用了这个函数优先查看用户是否设置了大于1的线程,如果设置了优先使用,没设置就开发者设定的函数设置
|
||||||
numThreads = this.videoDownloadConcurrency !== 1 ? this.videoDownloadConcurrency : numThreads;
|
numThreads = this.videoDownloadConcurrency !== 1 ? this.videoDownloadConcurrency : numThreads;
|
||||||
|
|
||||||
|
const proxyOption = {
|
||||||
|
...(isProxy && {
|
||||||
|
httpAgent: tunnel.httpOverHttp({
|
||||||
|
proxy: { host: this.proxyAddr, port: this.proxyPort },
|
||||||
|
}),
|
||||||
|
httpsAgent: tunnel.httpsOverHttp({
|
||||||
|
proxy: { host: this.proxyAddr, port: this.proxyPort },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是用户设置了单线程,则不分片下载
|
// 如果是用户设置了单线程,则不分片下载
|
||||||
if (numThreads === 1) {
|
if (numThreads === 1) {
|
||||||
const axiosConfig = {
|
const axiosConfig = {
|
||||||
headers: headers || { "User-Agent": userAgent },
|
headers: headers || { "User-Agent": userAgent },
|
||||||
responseType: "stream",
|
responseType: "stream",
|
||||||
...(isProxy && {
|
...proxyOption
|
||||||
httpAgent: tunnel.httpOverHttp({
|
|
||||||
proxy: { host: this.proxyAddr, port: this.proxyPort },
|
|
||||||
}),
|
|
||||||
httpsAgent: tunnel.httpsOverHttp({
|
|
||||||
proxy: { host: this.proxyAddr, port: this.proxyPort },
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1579,62 +1583,80 @@ export class tools extends plugin {
|
|||||||
logger.error(`下载视频发生错误!\ninfo:${ err }`);
|
logger.error(`下载视频发生错误!\ninfo:${ err }`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 多线程分片下载
|
|
||||||
const execFilePromise = promisify(execFile);
|
|
||||||
try {
|
try {
|
||||||
await checkAndRemoveFile(target);
|
// Step 1: 请求视频资源获取 Content-Length
|
||||||
const sizeRes = await axios.head(url, {headers: {'User-Agent': userAgent, ...headers}});
|
const headRes = await axios.head(url, {
|
||||||
const contentLength = sizeRes.headers['content-length'];
|
headers: headers || { "User-Agent": userAgent },
|
||||||
const chunkSize = Math.ceil(contentLength / numThreads);
|
...proxyOption
|
||||||
|
});
|
||||||
|
const contentLength = headRes.headers['content-length'];
|
||||||
|
if (!contentLength) {
|
||||||
|
throw new Error("无法获取视频大小");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: 计算每个线程应该下载的文件部分
|
||||||
|
const partSize = Math.ceil(contentLength / numThreads);
|
||||||
let promises = [];
|
let promises = [];
|
||||||
|
|
||||||
for (let i = 0; i < numThreads; i++) {
|
for (let i = 0; i < numThreads; i++) {
|
||||||
let start = i * chunkSize;
|
const start = i * partSize;
|
||||||
let end = i === numThreads - 1 ? '' : (i + 1) * chunkSize - 1;
|
let end = start + partSize - 1;
|
||||||
logger.mark(`[R插件][视频下载引擎] 正在下载分片${ i }`);
|
if (i === numThreads - 1) {
|
||||||
const tempPath = `${groupPath}/temp-${i}.mp4`;
|
end = contentLength - 1; // 确保最后一部分可以下载完整
|
||||||
promises.push(this.downloadChunk(url, start, end, tempPath, userAgent, headers));
|
}
|
||||||
|
|
||||||
|
// Step 3: 并发下载文件的不同部分
|
||||||
|
const partAxiosConfig = {
|
||||||
|
headers: {
|
||||||
|
"User-Agent": userAgent,
|
||||||
|
"Range": `bytes=${start}-${end}`
|
||||||
|
},
|
||||||
|
responseType: "stream",
|
||||||
|
...proxyOption
|
||||||
|
};
|
||||||
|
|
||||||
|
promises.push(axios.get(url, partAxiosConfig).then(res => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const partPath = `${target}.part${i}`;
|
||||||
|
logger.mark(`[R插件][视频下载引擎] 正在下载 part${i}`)
|
||||||
|
const writer = fs.createWriteStream(partPath);
|
||||||
|
res.data.pipe(writer);
|
||||||
|
writer.on("finish", () => {
|
||||||
|
logger.mark(`[R插件][视频下载引擎] part${i + 1} 下载完成`); // 记录线程下载完成
|
||||||
|
resolve(partPath);
|
||||||
|
});
|
||||||
|
writer.on("error", reject);
|
||||||
|
});
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all downloads to complete
|
// 等待所有部分都下载完毕
|
||||||
await Promise.all(promises);
|
const parts = await Promise.all(promises);
|
||||||
|
|
||||||
// Use ffmpeg to concatenate files
|
// Step 4: 合并下载的文件部分
|
||||||
const fileList = promises.map((_, index) => `file '${ path.resolve(groupPath + "/temp-" + index + ".mp4") }'`).join('\n');
|
await checkAndRemoveFile(target); // 确保目标文件不存在
|
||||||
const fileListPath = path.resolve(`${ groupPath }/fileList.txt`);
|
const writer = fs.createWriteStream(target, {flags: 'a'});
|
||||||
fs.writeFileSync(fileListPath, fileList);
|
for (const partPath of parts) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const reader = fs.createReadStream(partPath);
|
||||||
|
reader.pipe(writer, { end: false });
|
||||||
|
reader.on('end', () => {
|
||||||
|
fs.unlinkSync(partPath); // 删除部分文件
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
reader.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await execFilePromise('ffmpeg', ['-f', 'concat', '-safe', '0', '-i', fileListPath, '-c', 'copy', target]);
|
writer.close();
|
||||||
|
|
||||||
// Cleanup
|
return groupPath;
|
||||||
promises.forEach((_, index) => {
|
|
||||||
fs.unlinkSync(`${groupPath}/temp-${index}.mp4`);
|
|
||||||
});
|
|
||||||
fs.unlinkSync(fileListPath);
|
|
||||||
|
|
||||||
logger.mark(`[R插件][视频下载引擎] 提醒你:视频已经下载完成,路径:${target}`);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`下载视频发生错误!\ninfo:${err}`);
|
logger.error(`下载视频发生错误!\ninfo:${err}`);
|
||||||
// 处理或抛出错误
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadChunk(url, start, end, tempPath, userAgent, headers) {
|
|
||||||
const response = await axios.get(url, {
|
|
||||||
headers: {'Range': `bytes=${start}-${end}`, 'User-Agent': userAgent, ...headers},
|
|
||||||
responseType: 'stream'
|
|
||||||
});
|
|
||||||
|
|
||||||
const writer = fs.createWriteStream(tempPath);
|
|
||||||
response.data.pipe(writer);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
writer.on('finish', resolve);
|
|
||||||
writer.on('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置海外模式
|
* 设置海外模式
|
||||||
* @param e
|
* @param e
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user