diff --git a/apps/tools.js b/apps/tools.js index a65523f..4f5e685 100644 --- a/apps/tools.js +++ b/apps/tools.js @@ -101,7 +101,7 @@ import { saveTDL, startTDL } from "../utils/tdl-util.js"; import { genVerifyFp } from "../utils/tiktok.js"; import Translate from "../utils/trans-strategy.js"; import { mid2id } from "../utils/weibo.js"; -import { ytDlpGetTilt, ytDlpHelper } from "../utils/yt-dlp-util.js"; +import { ytDlpGetTilt, ytDlpHelper, ytDlpGetThumbnail, ytDlpGetDuration } from "../utils/yt-dlp-util.js"; import { textArrayToMakeForward } from "../utils/yunzai-util.js"; import puppeteer from "../../../lib/puppeteer/puppeteer.js"; @@ -283,6 +283,8 @@ export class tools extends plugin { this.biliDownloadMethod = this.toolsConfig.biliDownloadMethod; // 加载哔哩哔哩最高分辨率 this.biliResolution = this.toolsConfig.biliResolution; + // 加载youtube的限制时长 + this.youtubeDuration = this.toolsConfig.youtubeDuration // 加载油管下载画质选项 this.YouTubeGraphicsOptions = this.toolsConfig.YouTubeGraphicsOptions // 加载抖音Cookie @@ -1910,6 +1912,19 @@ export class tools extends plugin { // 油管解析 async sy2b(e) { + function formatTime(seconds) { + // 计算小时、分钟和秒 + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + // 将小时、分钟和秒格式化为两位数 + const formattedHours = String(hours).padStart(2, '0'); + const formattedMinutes = String(minutes).padStart(2, '0'); + const formattedSeconds = String(secs).padStart(2, '0'); + // 构造时间范围字符串 + return `00:00:00-${formattedHours}:${formattedMinutes}:${formattedSeconds}`; + } + const timeRange = await formatTime(this.youtubeDuration) const isOversea = await this.isOverseasServer(); if (!isOversea && !(await testProxy(this.proxyAddr, this.proxyPort))) { e.reply("检测到没有梯子,无法解析油管"); @@ -1941,9 +1956,25 @@ export class tools extends plugin { } const path = this.getCurDownloadPath(e); await checkAndRemoveFile(path + "/temp.mp4") - const title = await ytDlpGetTilt(url, isOversea, this.myProxy); - e.reply(`${ this.identifyPrefix }识别:油管,视频下载中请耐心等待 \n${ title }`); - await ytDlpHelper(path, url, isOversea, this.myProxy, true, graphics); + await checkAndRemoveFile(path + "/Thumbnail.png") + await ytDlpGetThumbnail(path, url, isOversea, this.myProxy) + const Duration = await ytDlpGetDuration(url, isOversea, this.myProxy).toString().replace(/\n/g, '') + // logger.info('时长------',Duration) + const title = await ytDlpGetTilt(url, isOversea, this.myProxy).toString().replace(/\n/g, ''); + // logger.info('标题------',title) + function convertToSeconds(timeStr) { + const [minutes, seconds] = timeStr.split(':').map(Number); // 拆分并转换为数字 + return minutes * 60 + seconds; // 分钟转化为秒并加上秒数 + } + if(await convertToSeconds(Duration) > this.youtubeDuration){ + e.reply([ + segment.image(`${path}/Thumbnail.png`), + `${this.identifyPrefix}识别:油管,视频下载中请耐心等待 \n视频标题:${title}${DIVIDING_LINE.replace('{}', '限制说明')}\n视频时长:${Duration}分钟\n大于管理员限定时长:${(this.youtubeDuration / 60).toFixed(2).replace(/\.00$/, '')} 分钟\n将截取限定时间部分` + ]); + } else { + e.reply([segment.image(`${ path }/Thumbnail.png`),`${ this.identifyPrefix }识别:油管,视频下载中请耐心等待 \n视频标题:${ title }\n视频时长:${Duration} 分钟`]); + } + await ytDlpHelper(path, url, isOversea, this.myProxy, true, graphics, timeRange); this.sendVideoToUpload(e, `${ path }/temp.mp4`); } catch (error) { logger.error(error); diff --git a/config/tools.yaml b/config/tools.yaml index 019c01f..6a2c75d 100644 --- a/config/tools.yaml +++ b/config/tools.yaml @@ -22,12 +22,13 @@ biliCDN: 0 # 哔哩哔哩 CDN,默认为0表示不使用 biliDownloadMethod: 0 # 哔哩哔哩的下载方式:0默认使用原生稳定的下载方式,如果你在乎内存可以使用轻量的wget和axel下载方式,如果在乎性能可以使用Aria2下载 biliResolution: 1 # 哔哩哔哩的下载画质,0为原画,1为清晰画,2为流畅画(默认为0) -useLocalNeteaseAPI: false #是否使用网易云解析自建API -neteaseCookie: '' #网易云ck -neteaseCloudAPIServer: '' #网易云自建服务器地址 -neteaseCloudAudioQuality: exhigh #网易云解析最高音质 默认exhigh(极高) 分类:standard => 标准,higher => 较高, exhigh=>极高, lossless=>无损, hires=>Hi-Res, jyeffect => 高清环绕声, sky => 沉浸环绕声, dolby => 杜比全景声, jymaster => 超清母带 +useLocalNeteaseAPI: false # 是否使用网易云解析自建API +neteaseCookie: '' # 网易云ck +neteaseCloudAPIServer: '' # 网易云自建服务器地址 +neteaseCloudAudioQuality: exhigh # 网易云解析最高音质 默认exhigh(极高) 分类:standard => 标准,higher => 较高, exhigh=>极高, lossless=>无损, hires=>Hi-Res, jyeffect => 高清环绕声, sky => 沉浸环绕声, dolby => 杜比全景声, jymaster => 超清母带 -YouTubeGraphicsOptions: 720 #YouTobe的下载画质,0为原画,1080,720,480,自定义画面高度(默认为720) +YouTubeGraphicsOptions: 720 # YouTobe的下载画质,0为原画,1080,720,480,自定义画面高度(默认为720) +youtubeDuration: 300 # YouTobe限制的最大视频时长(默认5分钟),单位:秒 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-不压缩;是否使用压缩视频格式的抖音(默认使用),使用后加速视频发送 diff --git a/guoba.support.js b/guoba.support.js index e00e4f6..13e31ff 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -220,6 +220,17 @@ export function supportGuoba() { options: BILI_RESOLUTION_LIST, } }, + { + field: "tools.youtubeDuration", + label: "YouTuBe最大时长", + bottomHelpMessage: + "超过时长会截取指定时间(单位:秒),保护魔法的流量,计算公式:5分钟 x 60秒 = 300秒", + component: "InputNumber", + required: false, + componentProps: { + placeholder: "请输入YouTuBe视频最大时长限制(默认5分钟)", + }, + }, { field: "tools.YouTubeGraphicsOptions", label: "YouTube最高分辨率", diff --git a/utils/yt-dlp-util.js b/utils/yt-dlp-util.js index 283f87a..d9eb488 100644 --- a/utils/yt-dlp-util.js +++ b/utils/yt-dlp-util.js @@ -10,6 +10,18 @@ function constructProxyParam(isOversea, proxy) { return isOversea ? "" : `--proxy ${proxy}`; } + +/** + * 获取时长 + * @param url + * @param isOversea + * @param proxy + * @returns string + */ +export function ytDlpGetDuration(url, isOversea, proxy) { + return execSync(`yt-dlp --get-duration ${constructProxyParam(isOversea, proxy)} ${url}`); +} + /** * 获取标题 * @param url @@ -21,6 +33,17 @@ export function ytDlpGetTilt(url, isOversea, proxy) { return execSync(`yt-dlp --get-title ${constructProxyParam(isOversea, proxy)} ${url} --encoding utf8`); } +/** + * 获取封面 + * @param path + * @param url + * @param isOversea + * @param proxy + */ +export function ytDlpGetThumbnail(path, url, isOversea, proxy) { + return execSync(`yt-dlp --write-thumbnail --convert-thumbnails png --skip-download ${constructProxyParam(isOversea, proxy)} ${url} -P ${path} -o "thumbnail.%(ext)s"`); +} + /** * yt-dlp 工具类 * @returns {Promise} @@ -30,12 +53,13 @@ export function ytDlpGetTilt(url, isOversea, proxy) { * @param proxy 代理地址 * @param merge 是否合并输出为 mp4 格式 (仅适用于视频合并需求) * @param graphics YouTube画质参数 + * @param timeRange 截取时间段 */ -export async function ytDlpHelper(path, url, isOversea, proxy, merge = false, graphics) { +export async function ytDlpHelper(path, url, isOversea, proxy, merge = false, graphics, timeRange) { return new Promise((resolve, reject) => { const mergeOption = merge ? '--merge-output-format "mp4"' : ''; - const fParam = url.includes("youtu") ? `-f "bv${graphics}[ext=mp4]+ba[ext=m4a]"` : ""; + const fParam = url.includes("youtu") ? `--download-sections "*${timeRange}" -f "bv${graphics}[ext=mp4]+ba[ext=m4a]"` : ""; const command = `yt-dlp ${fParam} ${constructProxyParam(isOversea, proxy)} -P ${path} -o "temp.%(ext)s" ${url}`;