feat: convert wav by ffmpeg

This commit is contained in:
linyuchen 2024-02-25 12:46:37 +08:00
parent d9d7e9e830
commit 730294236c
6 changed files with 863 additions and 28 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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);

View File

@ -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,

View File

@ -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,