From 52850d172ef508382f6dc66885a35fb30bc90cf8 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 17 Jun 2024 16:05:38 +0800 Subject: [PATCH 1/8] feat: decode silk --- src/common/utils/audio.ts | 46 +++++++++++++++++++++++++-- src/onebot11/action/file/GetRecord.ts | 8 +++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index 3e14343..edd08b2 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -1,9 +1,10 @@ import fs from 'fs' -import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm' +import fsAsync from 'fs/promises' import fsPromise from 'fs/promises' +import { decode, encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm' import { log } from './log' import path from 'node:path' -import { DATA_DIR, TEMP_DIR } from './index' +import { TEMP_DIR } from './index' import { v4 as uuidv4 } from 'uuid' import { getConfigUtil } from '../config' import { spawn } from 'node:child_process' @@ -79,7 +80,8 @@ export async function encodeSilk(filePath: string) { if (code == null || EXIT_CODES.includes(code)) { sampleRate = 24000 const data = fs.readFileSync(pcmPath) - fs.unlink(pcmPath, (err) => {}) + fs.unlink(pcmPath, (err) => { + }) return resolve(data) } log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`) @@ -128,3 +130,41 @@ export async function encodeSilk(filePath: string) { return {} } } + +export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') { + const silkArrayBuffer = await fsAsync.readFile(inputFilePath) + const data = (await decode(silkArrayBuffer, 24000)).data + const fileName = path.join(TEMP_DIR, uuidv4()) + const outPCMPath = fileName + '.pcm' + const outFilePath = fileName + '.' + outFormat + await fsAsync.writeFile(outPCMPath, data) + const convert = () => { + return new Promise((resolve, reject) => { + const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg' + const cp = spawn(ffmpegPath, [ + '-y', + '-f', 's16le', // PCM format + '-ar', '24000', // Sample rate + '-ac', '1', // Number of audio channels + '-i', outPCMPath, + outFilePath, + ]) + cp.on('error', (err) => { + log(`FFmpeg处理转换出错: `, err.message) + return reject(err) + }) + cp.on('exit', (code, signal) => { + const EXIT_CODES = [0, 255] + if (code == null || EXIT_CODES.includes(code)) { + fs.unlink(outPCMPath, (err) => { + }) + return resolve(outFilePath) + } + const exitErr = `FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}` + log(exitErr) + reject(Error(`FFmpeg处理转换失败,${exitErr}`)) + }) + }) + } + return convert() +} \ No newline at end of file diff --git a/src/onebot11/action/file/GetRecord.ts b/src/onebot11/action/file/GetRecord.ts index 2e57624..1f12ba3 100644 --- a/src/onebot11/action/file/GetRecord.ts +++ b/src/onebot11/action/file/GetRecord.ts @@ -1,5 +1,6 @@ import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile' import { ActionName } from '../types' +import {decodeSilk} from "@/common/utils/audio"; interface Payload extends GetFilePayload { out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' @@ -8,8 +9,9 @@ interface Payload extends GetFilePayload { export default class GetRecord extends GetFileBase { actionName = ActionName.GetRecord - protected async _handle(payload: Payload): Promise { - let res = super._handle(payload) - return res + protected async _handle(payload: Payload): Promise<{file: string}> { + let res = await super._handle(payload) + res.file = await decodeSilk(res.file, payload.out_format) + return {file: res.file} } } From 781c3311ae8e93724350c246a5c2e3bae0e9b7e2 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 17 Jun 2024 16:20:37 +0800 Subject: [PATCH 2/8] fix: get_file cache not found --- src/common/utils/audio.ts | 2 +- src/onebot11/action/file/GetRecord.ts | 12 ++++++++++-- src/onebot11/constructor.ts | 3 +-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index edd08b2..0510f43 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -134,7 +134,7 @@ export async function encodeSilk(filePath: string) { export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') { const silkArrayBuffer = await fsAsync.readFile(inputFilePath) const data = (await decode(silkArrayBuffer, 24000)).data - const fileName = path.join(TEMP_DIR, uuidv4()) + const fileName = path.join(TEMP_DIR, path.basename(inputFilePath)) const outPCMPath = fileName + '.pcm' const outFilePath = fileName + '.' + outFormat await fsAsync.writeFile(outPCMPath, data) diff --git a/src/onebot11/action/file/GetRecord.ts b/src/onebot11/action/file/GetRecord.ts index 1f12ba3..a73de3c 100644 --- a/src/onebot11/action/file/GetRecord.ts +++ b/src/onebot11/action/file/GetRecord.ts @@ -1,6 +1,9 @@ import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile' import { ActionName } from '../types' import {decodeSilk} from "@/common/utils/audio"; +import { getConfigUtil } from '@/common/config' +import path from 'node:path' +import fs from 'node:fs' interface Payload extends GetFilePayload { out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' @@ -9,9 +12,14 @@ interface Payload extends GetFilePayload { export default class GetRecord extends GetFileBase { actionName = ActionName.GetRecord - protected async _handle(payload: Payload): Promise<{file: string}> { + protected async _handle(payload: Payload): Promise { let res = await super._handle(payload) res.file = await decodeSilk(res.file, payload.out_format) - return {file: res.file} + res.file_name = path.basename(res.file) + res.file_size = fs.statSync(res.file).size.toString() + if (getConfigUtil().getConfig().enableLocalFile2Url){ + res.base64 = fs.readFileSync(res.file, 'base64') + } + return res } } diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index 378f6e5..ac82a99 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -171,7 +171,7 @@ export class OB11Constructor { message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType) // message_data["data"]["file_id"] = element.picElement.fileUuid message_data['data']['file_size'] = element.picElement.fileSize - dbUtil + await dbUtil .addFileCache(fileName, { fileName, elementId: element.elementId, @@ -189,7 +189,6 @@ export class OB11Constructor { ) }, }) - .then() // 不在自动下载图片 } else if (element.videoElement || element.fileElement) { From 958b21e47ea3886c9faaffdd68fb762a131abd9d Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 17 Jun 2024 17:41:23 +0800 Subject: [PATCH 3/8] fix: wait get_file download complete --- src/onebot11/action/file/GetFile.ts | 9 +++++---- src/onebot11/constructor.ts | 5 ++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/onebot11/action/file/GetFile.ts b/src/onebot11/action/file/GetFile.ts index 6d5bf90..a15aa33 100644 --- a/src/onebot11/action/file/GetFile.ts +++ b/src/onebot11/action/file/GetFile.ts @@ -2,7 +2,7 @@ import BaseAction from '../BaseAction' import fs from 'fs/promises' import { dbUtil } from '@/common/db' import { getConfigUtil } from '@/common/config' -import { log, sleep, uri2local } from '@/common/utils' +import { checkFileReceived, log, sleep, uri2local } from '@/common/utils' import { NTQQFileApi } from '@/ntqqapi/api' import { ActionName } from '../types' import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types' @@ -38,20 +38,21 @@ export class GetFileBase extends BaseAction { log('找到了文件 element', element) // 构建下载函数 await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true) - await sleep(1000) // 这里延时是为何? + // 等待文件下载完成 msg = await dbUtil.getMsgByLongId(cache.msgId) log('下载完成后的msg', msg) cache.filePath = this.getElement(msg, cache.elementId).filePath + await checkFileReceived(cache.filePath, 10 * 1000) dbUtil.addFileCache(file, cache).then() } } } protected async _handle(payload: GetFilePayload): Promise { - const cache = await dbUtil.getFileCache(payload.file) - const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig() + let cache = await dbUtil.getFileCache(payload.file) if (!cache) { throw new Error('file not found') } + const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig() if (cache.downloadFunc) { await cache.downloadFunc() } diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index ac82a99..abfaad4 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -171,7 +171,7 @@ export class OB11Constructor { message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType) // message_data["data"]["file_id"] = element.picElement.fileUuid message_data['data']['file_size'] = element.picElement.fileSize - await dbUtil + dbUtil .addFileCache(fileName, { fileName, elementId: element.elementId, @@ -188,8 +188,7 @@ export class OB11Constructor { element.picElement.sourcePath, ) }, - }) - // 不在自动下载图片 + }).then() } else if (element.videoElement || element.fileElement) { const videoOrFileElement = element.videoElement || element.fileElement From 1508dab7fe198646ffcf82249c2d02b3db4f1d79 Mon Sep 17 00:00:00 2001 From: idranme <96647698+idranme@users.noreply.github.com> Date: Tue, 18 Jun 2024 19:15:56 +0000 Subject: [PATCH 4/8] perf: audio --- package.json | 2 +- src/common/utils/audio.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9add729..e25533a 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "file-type": "^19.0.0", "fluent-ffmpeg": "^2.1.2", "level": "^8.0.1", - "silk-wasm": "^3.3.4", + "silk-wasm": "^3.6.0", "utf-8-validate": "^6.0.3", "uuid": "^9.0.1", "ws": "^8.16.0" diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index 3e14343..4eb2458 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -1,5 +1,5 @@ import fs from 'fs' -import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm' +import { encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm' import fsPromise from 'fs/promises' import { log } from './log' import path from 'node:path' @@ -60,10 +60,11 @@ export async function encodeSilk(filePath: string) { // } try { + const file = await fsPromise.readFile(filePath) const pttPath = path.join(TEMP_DIR, uuidv4()) - if (getFileHeader(filePath) !== '02232153494c4b') { + if (!isSilk(file)) { log(`语音文件${filePath}需要转换成silk`) - const _isWav = await isWavFile(filePath) + const _isWav = isWav(file) const pcmPath = pttPath + '.pcm' let sampleRate = 0 const convert = () => { @@ -91,7 +92,7 @@ export async function encodeSilk(filePath: string) { if (!_isWav) { input = await convert() } else { - input = fs.readFileSync(filePath) + input = file const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] const { fmt } = getWavFileInfo(input) // log(`wav文件信息`, fmt) @@ -108,7 +109,7 @@ export async function encodeSilk(filePath: string) { duration: silk.duration / 1000, } } else { - const silk = fs.readFileSync(filePath) + const silk = file let duration = 0 try { duration = getDuration(silk) / 1000 From e01148b86a31f9371ce46fefe8085a32a4dcace1 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Fri, 21 Jun 2024 16:20:26 +0800 Subject: [PATCH 5/8] :bug: fix: ws echo --- src/onebot11/server/ws/WebsocketServer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/onebot11/server/ws/WebsocketServer.ts b/src/onebot11/server/ws/WebsocketServer.ts index 5d77c2b..a1235fa 100644 --- a/src/onebot11/server/ws/WebsocketServer.ts +++ b/src/onebot11/server/ws/WebsocketServer.ts @@ -27,6 +27,7 @@ class OB11WebsocketServer extends WebsocketServerBase { } try { let handleResult = await action.websocketHandle(params, echo) + handleResult.echo = echo wsReply(wsClient, handleResult) } catch (e) { wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo)) From 8871331b7c792341933395570496194984f9125f Mon Sep 17 00:00:00 2001 From: linyuchen Date: Fri, 21 Jun 2024 16:20:26 +0800 Subject: [PATCH 6/8] :bug: fix: ws echo #261 --- src/onebot11/server/ws/WebsocketServer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/onebot11/server/ws/WebsocketServer.ts b/src/onebot11/server/ws/WebsocketServer.ts index 5d77c2b..a1235fa 100644 --- a/src/onebot11/server/ws/WebsocketServer.ts +++ b/src/onebot11/server/ws/WebsocketServer.ts @@ -27,6 +27,7 @@ class OB11WebsocketServer extends WebsocketServerBase { } try { let handleResult = await action.websocketHandle(params, echo) + handleResult.echo = echo wsReply(wsClient, handleResult) } catch (e) { wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo)) From 0d19005dc34a90ad71054b5063006f2ee0d7e7f7 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Fri, 21 Jun 2024 17:28:17 +0800 Subject: [PATCH 7/8] refactor: remove duplicate import --- src/common/utils/audio.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index afb690a..78a7840 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -1,5 +1,4 @@ import fs from 'fs' -import fsAsync from 'fs/promises' import fsPromise from 'fs/promises' import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm' import { log } from './log' @@ -133,12 +132,12 @@ export async function encodeSilk(filePath: string) { } export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') { - const silkArrayBuffer = await fsAsync.readFile(inputFilePath) + const silkArrayBuffer = await fsPromise.readFile(inputFilePath) const data = (await decode(silkArrayBuffer, 24000)).data const fileName = path.join(TEMP_DIR, path.basename(inputFilePath)) const outPCMPath = fileName + '.pcm' const outFilePath = fileName + '.' + outFormat - await fsAsync.writeFile(outPCMPath, data) + await fsPromise.writeFile(outPCMPath, data) const convert = () => { return new Promise((resolve, reject) => { const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg' From 6390620ddd721cfa847dcbd0cc99e820c990d7cd Mon Sep 17 00:00:00 2001 From: linyuchen Date: Fri, 21 Jun 2024 17:33:48 +0800 Subject: [PATCH 8/8] chore: version 3.26.7 --- manifest.json | 4 ++-- src/version.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 7fc0863..2d3c2c6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { "manifest_version": 4, "type": "extension", - "name": "LLOneBot v3.26.6", + "name": "LLOneBot v3.26.7", "slug": "LLOneBot", "description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新", - "version": "3.26.6", + "version": "3.26.7", "icon": "./icon.jpg", "authors": [ { diff --git a/src/version.ts b/src/version.ts index ce3c0b5..678d628 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '3.26.6' +export const version = '3.26.7'