From e285ab042e0b79ae40c414b2df960bd22693fa64 Mon Sep 17 00:00:00 2001 From: zhiyu1998 <542716863@qq.com> Date: Tue, 1 Oct 2024 13:37:21 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=88=20pref:=20=E4=BC=98=E5=8C=96=20?= =?UTF-8?q?=E5=93=94=E5=93=A9=E5=93=94=E5=93=A9=20=E4=B8=93=E6=A0=8F?= =?UTF-8?q?=E5=8F=91=E9=80=81=E9=80=BB=E8=BE=91=20&&=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=9B=B4=E6=92=AD=E5=88=87=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/tools.js | 59 ++++++++++++++++++++++++++++++++------------ constants/tools.js | 7 ++++++ utils/ffmpeg-util.js | 34 ++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 17 deletions(-) diff --git a/apps/tools.js b/apps/tools.js index e56576c..5198414 100644 --- a/apps/tools.js +++ b/apps/tools.js @@ -8,6 +8,8 @@ import fs from "node:fs"; import PQueue from 'p-queue'; import path from "path"; import querystring from "querystring"; +import { pipeline } from 'stream'; +import { promisify } from 'util'; import { BILI_CDN_SELECT_LIST, BILI_DEFAULT_INTRO_LEN_LIMIT, @@ -28,6 +30,7 @@ import { import { ANIME_SERIES_SEARCH_LINK, ANIME_SERIES_SEARCH_LINK2, + BILI_ARTICLE_INFO, BILI_EP_INFO, BILI_ONLINE, BILI_SSID_INFO, @@ -362,7 +365,6 @@ export class tools extends plugin { // 普通类型 dyApi = DY_INFO.replace("{}", douId); } - logger.info(dyApi); // a-bogus参数 const abParam = aBogus.generate_a_bogus( new URLSearchParams(new URL(dyApi).search).toString(), @@ -481,28 +483,27 @@ export class tools extends plugin { * @param second */ async sendStreamSegment(e, stream_url, second = this.streamDuration) { + const pipelineAsync = promisify(pipeline); const outputFilePath = `${ this.getCurDownloadPath(e) }/stream_10s.flv`; await checkAndRemoveFile(outputFilePath); - const file = fs.createWriteStream(outputFilePath); - axios.get(stream_url, { - responseType: 'stream' - }).then(response => { - logger.info("[R插件][发送直播流] 正在下载直播流..."); - // 将流数据写入文件 - response.data.pipe(file); - // 设置 10 秒后停止下载 + try { + const response = await axios.get(stream_url, { responseType: 'stream' }); + logger.info("[R插件][发送直播流] 正在下载直播流..."); + + const file = fs.createWriteStream(outputFilePath); + await pipelineAsync(response.data, file); + + // 设置 streamDuration 秒后停止下载 setTimeout(async () => { logger.info(`[R插件][发送直播流] 直播下载 ${ this.streamDuration } 秒钟到,停止下载!`); response.data.destroy(); // 销毁流 await this.sendVideoToUpload(e, outputFilePath); - file.close(); // 关闭文件流 - }, second * 1000); // 10秒 = 10000毫秒 - }).catch(error => { - logger.error(`下载失败:${ error.message }`); - fs.unlink(outputFilePath, () => { - }); // 下载失败时删除文件 - }); + }, second * 1000); + } catch (error) { + logger.error(`下载失败: ${ error.message }`); + await fs.promises.unlink(outputFilePath); // 下载失败时删除文件 + } } /** @@ -648,6 +649,7 @@ export class tools extends plugin { } // 处理专栏 if (e.msg !== undefined && e.msg.includes("read\/cv")) { + await this.biliArticle(e); this.linkShareSummary(e); return true; } @@ -708,6 +710,31 @@ export class tools extends plugin { return true; } + /** + * 提取哔哩哔哩专栏 + * @param e + * @returns {Promise} + */ + async biliArticle(e) { + const cvid = e.msg.match(/read\/cv(\d+)/)?.[1]; + logger.info(BILI_ARTICLE_INFO.replace("{}", cvid)); + const articleResp = await fetch(BILI_ARTICLE_INFO.replace("{}", cvid), { + headers: BILI_HEADER + }); + const articleData = (await articleResp.json()).data; + const { title, author_name, origin_image_urls } = articleData; + e.reply(`${ this.identifyPrefix }识别:哔哩哔哩专栏,${ title }-${ author_name }\n`); + if (origin_image_urls) { + await e.reply(Bot.makeForwardMsg(origin_image_urls.map(item => { + return { + message: segment.image(item), + nickname: e.sender.card || e.user_id, + user_id: e.user_id, + } + }))) + } + } + /** * 构造哔哩哔哩信息 * @param videoInfo diff --git a/constants/tools.js b/constants/tools.js index 2f41a7e..8de1cf4 100644 --- a/constants/tools.js +++ b/constants/tools.js @@ -89,6 +89,13 @@ export const BILI_EP_INFO = "https://api.bilibili.com/pgc/view/web/season?ep_id= */ export const BILI_SSID_INFO = "https://api.bilibili.com/pgc/web/season/section?season_id={}" +/** + * 专栏信息 + * https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/article/info.md + * @type {string} + */ +export const BILI_ARTICLE_INFO = "https://api.bilibili.com/x/article/viewinfo?id={}" + /** * 米游社网页端获取文章 * https://github.com/UIGF-org/mihoyo-api-collect/blob/main/hoyolab/article/article.md#%E8%8E%B7%E5%8F%96%E5%AE%8C%E6%95%B4%E6%96%87%E7%AB%A0%E4%BF%A1%E6%81%AF diff --git a/utils/ffmpeg-util.js b/utils/ffmpeg-util.js index 180cba4..df277ad 100644 --- a/utils/ffmpeg-util.js +++ b/utils/ffmpeg-util.js @@ -1,7 +1,13 @@ import path from 'path'; import { exec } from 'child_process'; -// 写一个执行ffmpeg提取视频关键帧的函数,例如:ffmpeg -i input.mp4 -vf "select=eq(pict_type\,I)" -vsync drop -vframes 5 -qscale:v 2 keyframes\keyframe_%03d.jpg +/** + * 提取关键帧 + * @param inputFilePath + * @param outputFolderPath + * @param frameCount + * @returns {Promise} + */ export async function extractKeyframes(inputFilePath, outputFolderPath, frameCount = 20) { return new Promise((resolve, reject) => { // 创建输出文件夹路径 @@ -21,3 +27,29 @@ export async function extractKeyframes(inputFilePath, outputFolderPath, frameCou }); }); } + +/** + * 使用 ffmpeg 将 FLV 文件转换为 MP4 文件 + * @param {string} inputFilePath - 输入的 FLV 文件路径 + * @param {string} outputFilePath - 输出的 MP4 文件路径 + * @returns {Promise} - 返回一个 Promise,成功时返回输出文件路径,失败时返回错误信息 + */ +function convertFlvToMp4(inputFilePath, outputFilePath) { + return new Promise((resolve, reject) => { + const command = `ffmpeg -i ${inputFilePath} ${outputFilePath}`; + + logger.info(`[R插件][ffmpeg工具]执行命令:${command}`); + + exec(command, (error, stdout, stderr) => { + if (error) { + reject(`执行 ffmpeg 命令时出错: ${error.message}`); + return; + } + if (stderr) { + reject(`ffmpeg 标准错误输出: ${stderr}`); + return; + } + resolve(outputFilePath); + }); + }); +}