mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
feat: send video not need ffmpeg
This commit is contained in:
parent
8afe0af940
commit
a298377717
@ -6,7 +6,7 @@ import path from "node:path";
|
|||||||
import {selfInfo} from "./data";
|
import {selfInfo} from "./data";
|
||||||
import {DATA_DIR} from "./utils";
|
import {DATA_DIR} from "./utils";
|
||||||
|
|
||||||
export const HOOK_LOG = false;
|
export const HOOK_LOG = true;
|
||||||
|
|
||||||
export const ALLOW_SEND_TEMP_MSG = false;
|
export const ALLOW_SEND_TEMP_MSG = false;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import {getConfigUtil} from "../config";
|
|||||||
import {dbUtil} from "../db";
|
import {dbUtil} from "../db";
|
||||||
import * as fileType from "file-type";
|
import * as fileType from "file-type";
|
||||||
import {net} from "electron";
|
import {net} from "electron";
|
||||||
import ClientRequestConstructorOptions = Electron.Main.ClientRequestConstructorOptions;
|
|
||||||
|
|
||||||
export function isGIF(path: string) {
|
export function isGIF(path: string) {
|
||||||
const buffer = Buffer.alloc(4);
|
const buffer = Buffer.alloc(4);
|
||||||
@ -191,68 +191,7 @@ export async function encodeSilk(filePath: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVideoInfo(filePath: string) {
|
|
||||||
const size = fs.statSync(filePath).size;
|
|
||||||
return new Promise<{
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
time: number,
|
|
||||||
format: string,
|
|
||||||
size: number,
|
|
||||||
filePath: string
|
|
||||||
}>((resolve, reject) => {
|
|
||||||
ffmpeg(filePath).ffprobe((err, metadata) => {
|
|
||||||
if (err) {
|
|
||||||
resolve({
|
|
||||||
width: 720, height: 1080,
|
|
||||||
time: 15,
|
|
||||||
format: "mp4",
|
|
||||||
size: fs.statSync(filePath).size,
|
|
||||||
filePath
|
|
||||||
})
|
|
||||||
// reject(err);
|
|
||||||
} else {
|
|
||||||
const videoStream = metadata.streams.find(s => s.codec_type === 'video');
|
|
||||||
if (videoStream) {
|
|
||||||
console.log(`视频尺寸: ${videoStream.width}x${videoStream.height}`);
|
|
||||||
} else {
|
|
||||||
console.log('未找到视频流信息。');
|
|
||||||
}
|
|
||||||
resolve({
|
|
||||||
width: videoStream.width, height: videoStream.height,
|
|
||||||
time: parseInt(videoStream.duration),
|
|
||||||
format: metadata.format.format_name,
|
|
||||||
size,
|
|
||||||
filePath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function encodeMp4(filePath: string) {
|
|
||||||
let videoInfo = await getVideoInfo(filePath);
|
|
||||||
log("视频信息", videoInfo)
|
|
||||||
if (videoInfo.format.indexOf("mp4") === -1) {
|
|
||||||
log("视频需要转换为MP4格式", filePath)
|
|
||||||
// 转成mp4
|
|
||||||
const newPath: string = await new Promise<string>((resolve, reject) => {
|
|
||||||
const newPath = filePath + ".mp4"
|
|
||||||
ffmpeg(filePath)
|
|
||||||
.toFormat('mp4')
|
|
||||||
.on('error', (err) => {
|
|
||||||
reject(`转换视频格式失败: ${err.message}`);
|
|
||||||
})
|
|
||||||
.on('end', () => {
|
|
||||||
log('视频转换为MP4格式完成');
|
|
||||||
resolve(newPath); // 返回转换后的文件路径
|
|
||||||
})
|
|
||||||
.save(newPath);
|
|
||||||
});
|
|
||||||
return await getVideoInfo(newPath)
|
|
||||||
}
|
|
||||||
return videoInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateFileMD5(filePath: string): Promise<string> {
|
export function calculateFileMD5(filePath: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -12,4 +12,5 @@ export const TEMP_DIR = path.join(DATA_DIR, "temp");
|
|||||||
export const PLUGIN_DIR = global.LiteLoader.plugins["LLOneBot"].path.plugin;
|
export const PLUGIN_DIR = global.LiteLoader.plugins["LLOneBot"].path.plugin;
|
||||||
if (!fs.existsSync(TEMP_DIR)) {
|
if (!fs.existsSync(TEMP_DIR)) {
|
||||||
fs.mkdirSync(TEMP_DIR);
|
fs.mkdirSync(TEMP_DIR);
|
||||||
}
|
}
|
||||||
|
export {getVideoInfo} from "./video";
|
File diff suppressed because one or more lines are too long
@ -94,7 +94,7 @@ function onLoad() {
|
|||||||
}
|
}
|
||||||
ipcMain.handle(CHANNEL_ERROR, async (event, arg) => {
|
ipcMain.handle(CHANNEL_ERROR, async (event, arg) => {
|
||||||
const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg)
|
const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg)
|
||||||
llonebotError.ffmpegError = ffmpegOk ? "" : "没有找到ffmpeg,音频只能发送wav和silk,视频无法发送"
|
llonebotError.ffmpegError = ffmpegOk ? "" : "没有找到ffmpeg,音频只能发送wav和silk,视频尺寸可能异常"
|
||||||
let {httpServerError, wsServerError, otherError, ffmpegError} = llonebotError;
|
let {httpServerError, wsServerError, otherError, ffmpegError} = llonebotError;
|
||||||
let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}`
|
let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}`
|
||||||
error = error.replace("\n\n", "\n")
|
error = error.replace("\n\n", "\n")
|
||||||
|
@ -14,10 +14,9 @@ import {
|
|||||||
import {promises as fs} from "node:fs";
|
import {promises as fs} from "node:fs";
|
||||||
import ffmpeg from "fluent-ffmpeg"
|
import ffmpeg from "fluent-ffmpeg"
|
||||||
import {NTQQFileApi} from "./api/file";
|
import {NTQQFileApi} from "./api/file";
|
||||||
import {calculateFileMD5, encodeSilk, getVideoInfo, isGIF} from "../common/utils/file";
|
import {calculateFileMD5, encodeSilk, isGIF} from "../common/utils/file";
|
||||||
import {log} from "../common/utils/log";
|
import {log} from "../common/utils/log";
|
||||||
import {sleep} from "../common/utils/helper";
|
import {defaultVideoThumb, getVideoInfo} from "../common/utils/video";
|
||||||
import pathLib from "path";
|
|
||||||
|
|
||||||
|
|
||||||
export class SendMsgElementConstructor {
|
export class SendMsgElementConstructor {
|
||||||
@ -109,36 +108,45 @@ export class SendMsgElementConstructor {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async video(filePath: string, fileName: string = "", diyThumbPath: string=""): Promise<SendVideoElement> {
|
static async video(filePath: string, fileName: string = "", diyThumbPath: string = ""): Promise<SendVideoElement> {
|
||||||
let {fileName: _fileName, path, fileSize, md5} = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO);
|
let {fileName: _fileName, path, fileSize, md5} = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO);
|
||||||
if (fileSize === 0) {
|
if (fileSize === 0) {
|
||||||
throw "文件异常,大小为0";
|
throw "文件异常,大小为0";
|
||||||
}
|
}
|
||||||
// const videoInfo = await encodeMp4(path);
|
|
||||||
// path = videoInfo.filePath
|
|
||||||
// md5 = videoInfo.md5;
|
|
||||||
// fileSize = videoInfo.size;
|
|
||||||
// log("上传视频", md5, path, fileSize, fileName || _fileName)
|
|
||||||
const pathLib = require("path");
|
const pathLib = require("path");
|
||||||
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
||||||
thumb = pathLib.dirname(thumb)
|
thumb = pathLib.dirname(thumb)
|
||||||
// log("thumb 目录", thumb)
|
// log("thumb 目录", thumb)
|
||||||
const videoInfo = await getVideoInfo(path);
|
let videoInfo ={
|
||||||
log("视频信息", videoInfo)
|
width: 1920, height: 1080,
|
||||||
|
time: 15,
|
||||||
|
format: "mp4",
|
||||||
|
size: fileSize,
|
||||||
|
filePath
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
videoInfo = await getVideoInfo(path);
|
||||||
|
log("视频信息", videoInfo)
|
||||||
|
}catch (e) {
|
||||||
|
log("获取视频信息失败", e)
|
||||||
|
}
|
||||||
const createThumb = new Promise<string>((resolve, reject) => {
|
const createThumb = new Promise<string>((resolve, reject) => {
|
||||||
const thumbFileName = `${md5}_0.png`
|
const thumbFileName = `${md5}_0.png`
|
||||||
const thumbPath = pathLib.join(thumb, thumbFileName)
|
const thumbPath = pathLib.join(thumb, thumbFileName)
|
||||||
if (diyThumbPath) {
|
|
||||||
fs.copyFile(diyThumbPath, pathLib.join(thumb, thumbFileName)).then(() => {
|
|
||||||
resolve(thumbPath);
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ffmpeg(filePath)
|
ffmpeg(filePath)
|
||||||
.on("end", () => {
|
.on("end", () => {
|
||||||
})
|
})
|
||||||
.on("error", (err) => {
|
.on("error", (err) => {
|
||||||
reject(err);
|
log("获取视频封面失败,使用默认封面", err)
|
||||||
|
if (diyThumbPath) {
|
||||||
|
fs.copyFile(diyThumbPath, thumbPath).then(() => {
|
||||||
|
resolve(thumbPath);
|
||||||
|
}).catch(reject)
|
||||||
|
} else {
|
||||||
|
fs.writeFile(thumbPath, defaultVideoThumb).then(() => {
|
||||||
|
resolve(thumbPath);
|
||||||
|
}).catch(reject)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.screenshots({
|
.screenshots({
|
||||||
timestamps: [0],
|
timestamps: [0],
|
||||||
|
@ -157,8 +157,8 @@ ob-setting-select::part(option-list) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#llonebot-error {
|
#llonebot-error {
|
||||||
color: red;
|
padding-top: 10px;
|
||||||
height: 100px;
|
padding-bottom: 10px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user