mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
feat: convert wav by ffmpeg
This commit is contained in:
parent
d9d7e9e830
commit
730294236c
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
node-version: 18
|
||||
|
||||
- name: install dependenies
|
||||
run: npm install
|
||||
run: export ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install
|
||||
|
||||
- name: build
|
||||
run: npm run build
|
||||
|
776
package-lock.json
generated
776
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,6 @@
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"postinstall": "cross-env ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save",
|
||||
"build": "npm run build-main && npm run build-preload && npm run build-renderer",
|
||||
"build-main": "webpack --config webpack.main.config.js",
|
||||
"build-preload": "webpack --config webpack.preload.config.js",
|
||||
@ -19,6 +18,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
"music-metadata": "^8.1.4",
|
||||
"silk-wasm": "^3.2.3",
|
||||
@ -28,12 +28,14 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^29.0.1",
|
||||
"ts-loader": "^9.5.0",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.89.0",
|
||||
|
@ -5,6 +5,8 @@ import util from "util";
|
||||
import {encode, getDuration} from "silk-wasm";
|
||||
import fs from 'fs';
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {exec} from "node:child_process";
|
||||
import ffmpeg from "fluent-ffmpeg"
|
||||
|
||||
export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
|
||||
|
||||
@ -136,7 +138,22 @@ export function mergeNewProperties(newObj: any, oldObj: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export function checkFFMPEG(): boolean {
|
||||
let ffmpegExist: boolean;
|
||||
exec('ffmpeg -version', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
log('ffmpeg is not installed or not found in PATH:', error);
|
||||
ffmpegExist = false
|
||||
}
|
||||
log('ffmpeg is installed. Version info:', stdout);
|
||||
ffmpegExist = true;
|
||||
});
|
||||
return ffmpegExist;
|
||||
}
|
||||
|
||||
export async function encodeSilk(filePath: string) {
|
||||
const fsp = require("fs").promises
|
||||
|
||||
function getFileHeader(filePath: string) {
|
||||
// 定义要读取的字节数
|
||||
const bytesToRead = 7;
|
||||
@ -154,6 +171,35 @@ export async function encodeSilk(filePath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function isWavFile(filePath: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.open(filePath, 'r', (err, fd) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
// 读取前12个字节
|
||||
const buffer = Buffer.alloc(12);
|
||||
fs.read(fd, buffer, 0, 12, 0, (err, bytesRead, buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
fs.close(fd, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
// 检查RIFF头和WAVE格式标识
|
||||
const isRIFF = buffer.toString('utf8', 0, 4) === 'RIFF';
|
||||
const isWAVE = buffer.toString('utf8', 8, 12) === 'WAVE';
|
||||
resolve(isRIFF && isWAVE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getAudioSampleRate(filePath: string) {
|
||||
try {
|
||||
const mm = await import('music-metadata');
|
||||
@ -172,22 +218,38 @@ export async function encodeSilk(filePath: string) {
|
||||
const pttPath = path.join(CONFIG_DIR, uuidv4());
|
||||
if (getFileHeader(filePath) !== "02232153494c4b") {
|
||||
log(`语音文件${filePath}需要转换`)
|
||||
const sampleRate = await getAudioSampleRate(filePath) || 44100;
|
||||
const silk = await encode(pcm, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
log(`语音文件${filePath}转换成功!`)
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration,
|
||||
};
|
||||
} else {
|
||||
const duration = getDuration(pcm);
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration: duration,
|
||||
};
|
||||
const isWav = await isWavFile(filePath);
|
||||
if (!isWav) {
|
||||
log(`语音文件${filePath}正在转换成wav`)
|
||||
let voiceData = await fsp.readFile(filePath)
|
||||
const wavPath = pttPath + ".wav"
|
||||
await new Promise((resolve, reject) => {
|
||||
ffmpeg(filePath).toFormat("wav").on('end', function () {
|
||||
// console.log('转换完成');
|
||||
filePath = wavPath
|
||||
})
|
||||
.on('error', function (err) {
|
||||
// console.log('转换出错: ' + err.message);
|
||||
})
|
||||
.save(wavPath);
|
||||
})
|
||||
const sampleRate = await getAudioSampleRate(filePath) || 44100;
|
||||
const silk = await encode(pcm, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
log(`语音文件${filePath}转换成功!`, pttPath)
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration,
|
||||
};
|
||||
} else {
|
||||
const duration = getDuration(pcm);
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration: duration,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log("convert silk failed", error.stack);
|
||||
|
@ -8,8 +8,7 @@ import {
|
||||
SendTextElement
|
||||
} from "./types";
|
||||
import {NTQQApi} from "./ntcall";
|
||||
import {encodeSilk, log} from "../common/utils";
|
||||
import fs from "fs";
|
||||
import {encodeSilk} from "../common/utils";
|
||||
|
||||
|
||||
export class SendMsgElementConstructor {
|
||||
@ -83,9 +82,9 @@ export class SendMsgElementConstructor {
|
||||
static async ptt(pttPath: string): Promise<SendPttElement> {
|
||||
const {converted, path: silkPath, duration} = await encodeSilk(pttPath);
|
||||
// log("生成语音", silkPath, duration);
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(silkPath);
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(silkPath, ElementType.PTT);
|
||||
if (converted){
|
||||
fs.unlink(silkPath, ()=>{});
|
||||
// fs.unlink(silkPath, ()=>{});
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.PTT,
|
||||
|
@ -3,7 +3,9 @@ import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} fr
|
||||
import {log} from "../common/utils";
|
||||
import {
|
||||
ChatType,
|
||||
Friend, FriendRequest,
|
||||
ElementType,
|
||||
Friend,
|
||||
FriendRequest,
|
||||
Group,
|
||||
GroupMember,
|
||||
GroupNotifies,
|
||||
@ -329,7 +331,7 @@ export class NTQQApi {
|
||||
}
|
||||
|
||||
// 上传文件到QQ的文件夹
|
||||
static async uploadFile(filePath: string) {
|
||||
static async uploadFile(filePath: string, elementType: ElementType=ElementType.PIC) {
|
||||
const md5 = await NTQQApi.getFileMd5(filePath);
|
||||
let ext = (await NTQQApi.getFileType(filePath))?.ext
|
||||
if (ext) {
|
||||
@ -344,7 +346,7 @@ export class NTQQApi {
|
||||
path_info: {
|
||||
md5HexStr: md5,
|
||||
fileName: fileName,
|
||||
elementType: 2,
|
||||
elementType: elementType,
|
||||
elementSubType: 0,
|
||||
thumbSize: 0,
|
||||
needCreate: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user