🎈 pref: 优化直播切片,兼容 icqq 和 llo

This commit is contained in:
zhiyu1998 2024-10-03 14:25:29 +08:00
parent 44e3027f6c
commit 7ed883f18d
4 changed files with 49 additions and 16 deletions

View File

@ -85,6 +85,7 @@ import {
truncateString, truncateString,
urlTransformShortLink urlTransformShortLink
} from "../utils/common.js"; } from "../utils/common.js";
import { convertFlvToMp4 } from "../utils/ffmpeg-util.js";
import { checkAndRemoveFile, deleteFolderRecursive, getMediaFilesAndOthers, mkdirIfNotExists } from "../utils/file.js"; import { checkAndRemoveFile, deleteFolderRecursive, getMediaFilesAndOthers, mkdirIfNotExists } from "../utils/file.js";
import GeneralLinkAdapter from "../utils/general-link-adapter.js"; import GeneralLinkAdapter from "../utils/general-link-adapter.js";
import { LagrangeAdapter } from "../utils/lagrange-adapter.js"; import { LagrangeAdapter } from "../utils/lagrange-adapter.js";
@ -260,6 +261,8 @@ export class tools extends plugin {
this.douyinCompression = this.toolsConfig.douyinCompression; this.douyinCompression = this.toolsConfig.douyinCompression;
// 加载抖音是否开启评论 // 加载抖音是否开启评论
this.douyinComments = this.toolsConfig.douyinComments; this.douyinComments = this.toolsConfig.douyinComments;
// 加载抖音的是否开启兼容模式
this.douyinStreamCompatibility = this.toolsConfig.douyinStreamCompatibility;
// 加载小红书Cookie // 加载小红书Cookie
this.xiaohongshuCookie = this.toolsConfig.xiaohongshuCookie; this.xiaohongshuCookie = this.toolsConfig.xiaohongshuCookie;
// 翻译引擎 // 翻译引擎
@ -352,7 +355,6 @@ export class tools extends plugin {
const webcastResp = await fetch(dyApi); const webcastResp = await fetch(dyApi);
const webcastData = await webcastResp.json(); const webcastData = await webcastResp.json();
const item = webcastData.data.room; const item = webcastData.data.room;
logger.info(item);
const { title, cover, user_count, stream_url } = item; const { title, cover, user_count, stream_url } = item;
const dySendContent = `${ this.identifyPrefix }识别:抖音直播,${ title }` const dySendContent = `${ this.identifyPrefix }识别:抖音直播,${ title }`
e.reply([segment.image(cover?.url_list?.[0]), dySendContent, `\n🏄‍♂️在线人数:${ user_count }人正在观看`]); e.reply([segment.image(cover?.url_list?.[0]), dySendContent, `\n🏄‍♂️在线人数:${ user_count }人正在观看`]);
@ -481,8 +483,13 @@ export class tools extends plugin {
* @param second * @param second
*/ */
async sendStreamSegment(e, stream_url, second = this.streamDuration) { async sendStreamSegment(e, stream_url, second = this.streamDuration) {
const outputFilePath = `${ this.getCurDownloadPath(e) }/stream_${second}s.flv`; let outputFilePath = `${ this.getCurDownloadPath(e) }/stream_${second}s.flv`;
// 删除临时文件
if (this.douyinStreamCompatibility) {
await checkAndRemoveFile(outputFilePath.replace("flv", "mp4"));
} else {
await checkAndRemoveFile(outputFilePath); await checkAndRemoveFile(outputFilePath);
}
// 创建一个取消令牌 // 创建一个取消令牌
const CancelToken = axios.CancelToken; const CancelToken = axios.CancelToken;
@ -502,9 +509,17 @@ export class tools extends plugin {
setTimeout(async () => { setTimeout(async () => {
logger.info(`[R插件][发送直播流] 直播下载 ${ second } 秒钟到,停止下载!`); logger.info(`[R插件][发送直播流] 直播下载 ${ second } 秒钟到,停止下载!`);
// 取消请求 // 取消请求
source.cancel('下载时间到,停止请求'); source.cancel('[R插件][发送直播流] 下载时间到,停止请求');
response.data.unpipe(file); // 取消管道连接 response.data.unpipe(file); // 取消管道连接
file.end(); // 结束写入 file.end(); // 结束写入
// 这里判断是否开启兼容模式
if (this.douyinStreamCompatibility) {
logger.info(`[R插件][发送直播流] 开启兼容模式开始转换mp4格式...`);
const resolvedOutputPath = await convertFlvToMp4(outputFilePath, outputFilePath.replace(".flv", ".mp4"));
fs.unlinkSync(outputFilePath);
outputFilePath = resolvedOutputPath;
logger.info(`[R插件][发送直播流] 转换完成,开始发送视频...`);
}
await this.sendVideoToUpload(e, outputFilePath); await this.sendVideoToUpload(e, outputFilePath);
}, second * 1000); }, second * 1000);

View File

@ -26,6 +26,7 @@ YouTubeGraphicsOptions: 720 #YouTobe的下载画质0为原画1080720
douyinCookie: '' # douyin's cookie, 格式odin_tt=xxx;passport_fe_beating_status=xxx;sid_guard=xxx;uid_tt=xxx;uid_tt_ss=xxx;sid_tt=xxx;sessionid=xxx;sessionid_ss=xxx;sid_ucp_v1=xxx;ssid_ucp_v1=xxx;passport_assist_user=xxx;ttwid=xxx; douyinCookie: '' # douyin's cookie, 格式odin_tt=xxx;passport_fe_beating_status=xxx;sid_guard=xxx;uid_tt=xxx;uid_tt_ss=xxx;sid_tt=xxx;sessionid=xxx;sessionid_ss=xxx;sid_ucp_v1=xxx;ssid_ucp_v1=xxx;passport_assist_user=xxx;ttwid=xxx;
douyinCompression: true # true-压缩false-不压缩;是否使用压缩视频格式的抖音(默认使用),使用后加速视频发送 douyinCompression: true # true-压缩false-不压缩;是否使用压缩视频格式的抖音(默认使用),使用后加速视频发送
douyinComments: false # true-开启评论false-关闭评论 douyinComments: false # true-开启评论false-关闭评论
douyinStreamCompatibility: false # 兼容模式NCQQ不用开其他ICQQ、LLO需要开启
xiaohongshuCookie: '' # 2024-8-2后反馈必须使用ck不然无法解析 xiaohongshuCookie: '' # 2024-8-2后反馈必须使用ck不然无法解析

View File

@ -245,6 +245,12 @@ export function supportGuoba() {
component: "Switch", component: "Switch",
required: false, required: false,
}, },
{
field: "tools.douyinStreamCompatibility",
label: "抖音直播是否开启兼容模式",
component: "Switch",
required: false,
},
{ {
field: "tools.xiaohongshuCookie", field: "tools.xiaohongshuCookie",
label: "小红书的Cookie", label: "小红书的Cookie",

View File

@ -1,5 +1,6 @@
import path from 'path'; import path from 'path';
import { exec } from 'child_process'; import { exec } from 'child_process';
import fs from "fs";
/** /**
* 提取关键帧 * 提取关键帧
@ -34,22 +35,32 @@ export async function extractKeyframes(inputFilePath, outputFolderPath, frameCou
* @param {string} outputFilePath - 输出的 MP4 文件路径 * @param {string} outputFilePath - 输出的 MP4 文件路径
* @returns {Promise<string>} - 返回一个 Promise成功时返回输出文件路径失败时返回错误信息 * @returns {Promise<string>} - 返回一个 Promise成功时返回输出文件路径失败时返回错误信息
*/ */
function convertFlvToMp4(inputFilePath, outputFilePath) { export function convertFlvToMp4(inputFilePath, outputFilePath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const command = `ffmpeg -i ${inputFilePath} ${outputFilePath}`; const resolvedInputPath = path.resolve(inputFilePath);
const resolvedOutputPath = path.resolve(outputFilePath);
// 检查文件是否存在
fs.access(resolvedInputPath, fs.constants.F_OK, (err) => {
if (err) {
reject(`[R插件][ffmpeg工具]输入文件不存在: ${resolvedInputPath}`);
return;
}
const command = `ffmpeg -y -i "${resolvedInputPath}" "${resolvedOutputPath}"`;
logger.info(`[R插件][ffmpeg工具]执行命令:${command}`); logger.info(`[R插件][ffmpeg工具]执行命令:${command}`);
// 执行 ffmpeg 转换
exec(command, (error, stdout, stderr) => { exec(command, (error, stdout, stderr) => {
if (error) { if (error) {
reject(`执行 ffmpeg 命令时出错: ${error.message}`); reject(`[R插件][ffmpeg工具]执行 ffmpeg 命令时出错: ${error.message}`);
return; return;
} }
if (stderr) { if (stderr) {
reject(`ffmpeg 标准错误输出: ${stderr}`); logger.warn(`[R插件][ffmpeg工具]ffmpeg 标准错误输出: ${stderr}`);
return;
} }
resolve(outputFilePath); resolve(resolvedOutputPath);
});
}); });
}); });
} }