diff --git a/package.json b/package.json index cf91a7ad..f959a9fd 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ }, "dependencies": { "express": "^5.0.0", - "silk-wasm": "^3.6.1", "ws": "^8.18.3" } } \ No newline at end of file diff --git a/packages/napcat-common/package.json b/packages/napcat-common/package.json index a3f6018c..44b76115 100644 --- a/packages/napcat-common/package.json +++ b/packages/napcat-common/package.json @@ -17,8 +17,7 @@ }, "dependencies": { "ajv": "^8.13.0", - "file-type": "^21.0.0", - "silk-wasm": "^3.6.1" + "file-type": "^21.0.0" }, "devDependencies": { "@types/node": "^22.0.1" diff --git a/packages/napcat-common/src/audio-worker.ts b/packages/napcat-common/src/audio-worker.ts deleted file mode 100644 index 1a20e185..00000000 --- a/packages/napcat-common/src/audio-worker.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { encode } from 'silk-wasm'; -import { parentPort } from 'worker_threads'; - -export interface EncodeArgs { - input: ArrayBufferView | ArrayBuffer - sampleRate: number -} -export function recvTask (cb: (taskData: T) => Promise) { - parentPort?.on('message', async (taskData: T) => { - try { - const ret = await cb(taskData); - parentPort?.postMessage(ret); - } catch (error: unknown) { - parentPort?.postMessage({ error: (error as Error).message }); - } - }); -} -recvTask(async ({ input, sampleRate }) => { - return await encode(input, sampleRate); -}); diff --git a/packages/napcat-core/external/appid.json b/packages/napcat-core/external/appid.json index 2e069750..bf0e1d60 100644 --- a/packages/napcat-core/external/appid.json +++ b/packages/napcat-core/external/appid.json @@ -510,5 +510,10 @@ "3.2.23-44343": { "appid": 537336639, "qua": "V1_LNX_NQ_3.2.23_44343_GW_B" + }, + "9.9.26-44498": { + "appid": 537337416, + "offset": "0x1809C2810", + "qua": "V1_WIN_NQ_9.9.26_44498_GW_B" } } \ No newline at end of file diff --git a/packages/napcat-core/external/napi2native.json b/packages/napcat-core/external/napi2native.json index bd9bc895..9fe3a696 100644 --- a/packages/napcat-core/external/napi2native.json +++ b/packages/napcat-core/external/napi2native.json @@ -142,5 +142,9 @@ "3.2.23-44343-x64": { "send": "59A27B0", "recv": "2FFBE90" + }, + "9.9.26-44498-x64": { + "send": "0A1051C", + "recv": "1D3BC0D" } } \ No newline at end of file diff --git a/packages/napcat-core/external/packet.json b/packages/napcat-core/external/packet.json index ca9e21a5..d5c618f9 100644 --- a/packages/napcat-core/external/packet.json +++ b/packages/napcat-core/external/packet.json @@ -654,5 +654,9 @@ "3.2.23-44343-arm64": { "send": "6926F60", "recv": "692A910" + }, + "9.9.26-44498-x64": { + "send": "2CDAE40", + "recv": "2CDE3C0" } } \ No newline at end of file diff --git a/packages/napcat-core/helper/audio.ts b/packages/napcat-core/helper/audio.ts index 82439de9..a4736d0e 100644 --- a/packages/napcat-core/helper/audio.ts +++ b/packages/napcat-core/helper/audio.ts @@ -1,19 +1,8 @@ import fsPromise from 'fs/promises'; import path from 'node:path'; import { randomUUID } from 'crypto'; -import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm'; import { LogWrapper } from '@/napcat-core/helper/log'; -import { EncodeArgs } from 'napcat-common/src/audio-worker'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg'; -import { runTask } from 'napcat-common/src/worker'; -import { fileURLToPath } from 'node:url'; - -const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000]; - -function getWorkerPath () { - // return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href; - return path.join(path.dirname(fileURLToPath(import.meta.url)), 'audio-worker.mjs'); -} async function guessDuration (pttPath: string, logger: LogWrapper) { const pttFileInfo = await fsPromise.stat(pttPath); @@ -22,51 +11,23 @@ async function guessDuration (pttPath: string, logger: LogWrapper) { return duration; } -async function handleWavFile ( - file: Buffer, - filePath: string, - pcmPath: string -): Promise<{ input: Buffer; sampleRate: number }> { - const { fmt } = getWavFileInfo(file); - if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) { - const result = await FFmpegService.convert(filePath, pcmPath); - return { input: await fsPromise.readFile(pcmPath), sampleRate: result.sampleRate }; - } - return { input: file, sampleRate: fmt.sampleRate }; -} - export async function encodeSilk (filePath: string, TEMP_DIR: string, logger: LogWrapper) { try { - const file = await fsPromise.readFile(filePath); const pttPath = path.join(TEMP_DIR, randomUUID()); - if (!isSilk(file)) { + if (!(await FFmpegService.isSilk(filePath))) { logger.log(`语音文件${filePath}需要转换成silk`); - const pcmPath = `${pttPath}.pcm`; - // const { input, sampleRate } = isWav(file) ? await handleWavFile(file, filePath, pcmPath): { input: await FFmpegService.convert(filePath, pcmPath) ? await fsPromise.readFile(pcmPath) : Buffer.alloc(0), sampleRate: 24000 }; - let input: Buffer; - let sampleRate: number; - if (isWav(file)) { - const result = await handleWavFile(file, filePath, pcmPath); - input = result.input; - sampleRate = result.sampleRate; - } else { - const result = await FFmpegService.convert(filePath, pcmPath); - input = await fsPromise.readFile(pcmPath); - sampleRate = result.sampleRate; - } - const silk = await runTask(getWorkerPath(), { input, sampleRate }); - fsPromise.unlink(pcmPath).catch((e) => logger.logError('删除临时文件失败', pcmPath, e)); - await fsPromise.writeFile(pttPath, Buffer.from(silk.data)); - logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration); + await FFmpegService.convertToNTSilkTct(filePath, pttPath); + const duration = await FFmpegService.getDuration(filePath); + logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', duration); return { converted: true, path: pttPath, - duration: silk.duration / 1000, + duration: duration, }; } else { let duration = 0; try { - duration = getDuration(file) / 1000; + duration = await FFmpegService.getDuration(filePath); } catch (e: unknown) { logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, (e as Error).stack); duration = await guessDuration(filePath, logger); diff --git a/packages/napcat-core/helper/ffmpeg/ffmpeg-adapter-interface.ts b/packages/napcat-core/helper/ffmpeg/ffmpeg-adapter-interface.ts index 9b795bf7..79e6dfb9 100644 --- a/packages/napcat-core/helper/ffmpeg/ffmpeg-adapter-interface.ts +++ b/packages/napcat-core/helper/ffmpeg/ffmpeg-adapter-interface.ts @@ -27,21 +27,27 @@ export interface IFFmpegAdapter { readonly name: string; /** 是否可用 */ - isAvailable(): Promise; + isAvailable (): Promise; /** * 获取视频信息(包含缩略图) * @param videoPath 视频文件路径 * @returns 视频信息 */ - getVideoInfo(videoPath: string): Promise; + getVideoInfo (videoPath: string): Promise; /** * 获取音视频文件时长 * @param filePath 文件路径 * @returns 时长(秒) */ - getDuration(filePath: string): Promise; + getDuration (filePath: string): Promise; + + /** + * 判断是否为 Silk 格式 + * @param filePath 文件路径 + */ + isSilk (filePath: string): Promise; /** * 转换音频为 PCM 格式 @@ -49,7 +55,7 @@ export interface IFFmpegAdapter { * @param pcmPath 输出 PCM 文件路径 * @returns PCM 数据 Buffer */ - convertToPCM(filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }>; + convertToPCM (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number; }>; /** * 转换音频文件 @@ -57,12 +63,14 @@ export interface IFFmpegAdapter { * @param outputFile 输出文件路径 * @param format 目标格式 ('amr' | 'silk' 等) */ - convertFile(inputFile: string, outputFile: string, format: string): Promise; + convertFile (inputFile: string, outputFile: string, format: string): Promise; /** * 提取视频缩略图 * @param videoPath 视频文件路径 * @param thumbnailPath 缩略图输出路径 */ - extractThumbnail(videoPath: string, thumbnailPath: string): Promise; + extractThumbnail (videoPath: string, thumbnailPath: string): Promise; + + convertToNTSilkTct (inputFile: string, outputFile: string): Promise; } diff --git a/packages/napcat-core/helper/ffmpeg/ffmpeg-addon-adapter.ts b/packages/napcat-core/helper/ffmpeg/ffmpeg-addon-adapter.ts index 3f131511..dd4b24e4 100644 --- a/packages/napcat-core/helper/ffmpeg/ffmpeg-addon-adapter.ts +++ b/packages/napcat-core/helper/ffmpeg/ffmpeg-addon-adapter.ts @@ -5,7 +5,7 @@ import { platform, arch } from 'node:os'; import path from 'node:path'; -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync, openSync, readSync, closeSync } from 'node:fs'; import { writeFile } from 'node:fs/promises'; import type { FFmpeg } from './ffmpeg-addon'; import type { IFFmpegAdapter, VideoInfoResult } from './ffmpeg-adapter-interface'; @@ -87,6 +87,22 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter { return addon.getDuration(filePath); } + /** + * 判断是否为 Silk 格式 + */ + async isSilk (filePath: string): Promise { + try { + const fd = openSync(filePath, 'r'); + const buffer = Buffer.alloc(10); + readSync(fd, buffer, 0, 10, 0); + closeSync(fd); + const header = buffer.toString(); + return header.includes('#!SILK') || header.includes('\x02#!SILK'); + } catch { + return false; + } + } + /** * 转换为 PCM */ @@ -106,6 +122,11 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter { await addon.decodeAudioToFmt(inputFile, outputFile, format); } + async convertToNTSilkTct (inputFile: string, outputFile: string): Promise { + const addon = this.ensureAddon(); + await addon.convertToNTSilkTct(inputFile, outputFile); + } + /** * 提取缩略图 */ diff --git a/packages/napcat-core/helper/ffmpeg/ffmpeg-addon.ts b/packages/napcat-core/helper/ffmpeg/ffmpeg-addon.ts index 3ad82bd3..dace1022 100644 --- a/packages/napcat-core/helper/ffmpeg/ffmpeg-addon.ts +++ b/packages/napcat-core/helper/ffmpeg/ffmpeg-addon.ts @@ -70,4 +70,6 @@ export interface FFmpeg { */ decodeAudioToPCM (filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number; }>; decodeAudioToFmt (filePath: string, pcmPath: string, format: string): Promise<{ channels: number; sampleRate: number; format: string; }>; + + convertToNTSilkTct (inputFile: string, outputFile: string): Promise; } diff --git a/packages/napcat-core/helper/ffmpeg/ffmpeg-exec-adapter.ts b/packages/napcat-core/helper/ffmpeg/ffmpeg-exec-adapter.ts index a1f21915..80f7cbab 100644 --- a/packages/napcat-core/helper/ffmpeg/ffmpeg-exec-adapter.ts +++ b/packages/napcat-core/helper/ffmpeg/ffmpeg-exec-adapter.ts @@ -3,7 +3,7 @@ * 使用 execFile 调用 FFmpeg 命令行工具的适配器实现 */ -import { readFileSync, existsSync, mkdirSync } from 'fs'; +import { readFileSync, existsSync, mkdirSync, openSync, readSync, closeSync } from 'fs'; import { dirname, join } from 'path'; import { execFile } from 'child_process'; import { promisify } from 'util'; @@ -154,6 +154,22 @@ export class FFmpegExecAdapter implements IFFmpegAdapter { } } + /** + * 判断是否为 Silk 格式 + */ + async isSilk (filePath: string): Promise { + try { + const fd = openSync(filePath, 'r'); + const buffer = Buffer.alloc(10); + readSync(fd, buffer, 0, 10, 0); + closeSync(fd); + const header = buffer.toString(); + return header.includes('#!SILK') || header.includes('\x02#!SILK'); + } catch { + return false; + } + } + /** * 转换为 PCM */ @@ -241,4 +257,8 @@ export class FFmpegExecAdapter implements IFFmpegAdapter { throw new Error(`提取缩略图失败: ${(error as Error).message}`); } } + + async convertToNTSilkTct (inputFile: string, outputFile: string): Promise { + throw new Error('convertToNTSilkTct is not implemented in FFmpegExecAdapter'); + } } diff --git a/packages/napcat-core/helper/ffmpeg/ffmpeg.ts b/packages/napcat-core/helper/ffmpeg/ffmpeg.ts index f8cbee44..fdfe4365 100644 --- a/packages/napcat-core/helper/ffmpeg/ffmpeg.ts +++ b/packages/napcat-core/helper/ffmpeg/ffmpeg.ts @@ -64,7 +64,10 @@ export class FFmpegService { } return this.adapter; } - + public static async convertToNTSilkTct (inputFile: string, outputFile: string): Promise { + const adapter = await this.getAdapter(); + await adapter.convertToNTSilkTct(inputFile, outputFile); + } /** * 设置 FFmpeg 路径并更新适配器 * @deprecated 建议使用 init() 方法初始化 @@ -92,11 +95,27 @@ export class FFmpegService { /** * 转换音频文件 */ - public static async convertFile (inputFile: string, outputFile: string, format: string): Promise { + public static async convertAudioFmt (inputFile: string, outputFile: string, format: string): Promise { const adapter = await this.getAdapter(); await adapter.convertFile(inputFile, outputFile, format); } + /** + * 获取音频时长 + */ + public static async getDuration (filePath: string): Promise { + const adapter = await this.getAdapter(); + return adapter.getDuration(filePath); + } + + /** + * 判断是否为 Silk 格式 + */ + public static async isSilk (filePath: string): Promise { + const adapter = await this.getAdapter(); + return adapter.isSilk(filePath); + } + /** * 转换为 PCM 格式 */ diff --git a/packages/napcat-framework/vite.config.ts b/packages/napcat-framework/vite.config.ts index 9e4121a1..3e15c1e3 100644 --- a/packages/napcat-framework/vite.config.ts +++ b/packages/napcat-framework/vite.config.ts @@ -8,7 +8,6 @@ import react from '@vitejs/plugin-react-swc'; import napcatVersion from 'napcat-vite/vite-plugin-version.js'; // 依赖排除 const external = [ - 'silk-wasm', 'ws', 'express', ]; @@ -60,7 +59,6 @@ const FrameworkBaseConfig = () => lib: { entry: { napcat: path.resolve(__dirname, 'napcat.ts'), - 'audio-worker': path.resolve(__dirname, '../napcat-common/src/audio-worker.ts'), 'worker/conoutSocketWorker': path.resolve(__dirname, '../napcat-pty/worker/conoutSocketWorker.ts'), }, formats: ['es'], diff --git a/packages/napcat-onebot/action/file/GetRecord.ts b/packages/napcat-onebot/action/file/GetRecord.ts index 19d6eed1..b444f44f 100644 --- a/packages/napcat-onebot/action/file/GetRecord.ts +++ b/packages/napcat-onebot/action/file/GetRecord.ts @@ -1,7 +1,6 @@ import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'; import { ActionName } from '@/napcat-onebot/action/router'; import { promises as fs } from 'fs'; -import { decode } from 'silk-wasm'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg'; const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac']; @@ -21,19 +20,13 @@ export default class GetRecord extends GetFileBase { if (!out_format.includes(payload.out_format)) { throw new Error('转换失败 out_format 字段可能格式不正确'); } - const pcmFile = `${inputFile}.pcm`; const outputFile = `${inputFile}.${payload.out_format}`; try { await fs.access(inputFile); try { await fs.access(outputFile); } catch { - if (FFmpegService.getAdapterName() === 'FFmpegAddon') { - await FFmpegService.convertFile(inputFile, outputFile, payload.out_format); - } else { - await this.decodeFile(inputFile, pcmFile); - await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format); - } + await FFmpegService.convertAudioFmt(inputFile, outputFile, payload.out_format); } const base64Data = await fs.readFile(outputFile, { encoding: 'base64' }); res.file = outputFile; @@ -46,15 +39,4 @@ export default class GetRecord extends GetFileBase { } return res; } - - private async decodeFile (inputFile: string, outputFile: string): Promise { - try { - const inputData = await fs.readFile(inputFile); - const decodedData = await decode(inputData, 24000); - await fs.writeFile(outputFile, Buffer.from(decodedData.data)); - } catch (error) { - console.error('Error decoding file:', error); - throw error; // 重新抛出错误以便调用者可以处理 - } - } } diff --git a/packages/napcat-onebot/action/stream/DownloadFileRecordStream.ts b/packages/napcat-onebot/action/stream/DownloadFileRecordStream.ts index 8f16d09d..e5482eab 100644 --- a/packages/napcat-onebot/action/stream/DownloadFileRecordStream.ts +++ b/packages/napcat-onebot/action/stream/DownloadFileRecordStream.ts @@ -4,7 +4,6 @@ import { Static, Type } from '@sinclair/typebox'; import { NetworkAdapterConfig } from '@/napcat-onebot/config/config'; import { StreamPacket, StreamStatus } from './StreamBasic'; import fs from 'fs'; -import { decode } from 'silk-wasm'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg'; import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream'; @@ -38,7 +37,6 @@ export class DownloadFileRecordStream extends BaseDownloadStream { - try { - const inputData = await fs.promises.readFile(inputFile); - const decodedData = await decode(inputData, 24000); - await fs.promises.writeFile(outputFile, Buffer.from(decodedData.data)); - } catch (error) { - console.error('Error decoding file:', error); - throw error; - } - } } diff --git a/packages/napcat-onebot/api/msg.ts b/packages/napcat-onebot/api/msg.ts index 417f8d38..94f948aa 100644 --- a/packages/napcat-onebot/api/msg.ts +++ b/packages/napcat-onebot/api/msg.ts @@ -1075,7 +1075,8 @@ export class OneBotMsgApi { resMsg.sub_type = 'group'; const ret = await this.core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid); if (ret.result === 0) { - const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin); + // 避免uin:'' uid非空,uid一般不空 + const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, await this.core.apis.UserApi.getUinByUidV2(msg.senderUid)); resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode); resMsg.sender.nickname = member?.nick ?? member?.cardName ?? '临时会话'; resMsg.temp_source = 0; diff --git a/packages/napcat-shell/vite.config.ts b/packages/napcat-shell/vite.config.ts index f07fa251..e97bc597 100644 --- a/packages/napcat-shell/vite.config.ts +++ b/packages/napcat-shell/vite.config.ts @@ -9,7 +9,6 @@ import react from '@vitejs/plugin-react-swc'; // 依赖排除 const external = [ - 'silk-wasm', 'ws', 'express', ]; @@ -56,7 +55,6 @@ const ShellBaseConfig = (source_map: boolean = false) => lib: { entry: { napcat: path.resolve(__dirname, 'napcat.ts'), - 'audio-worker': path.resolve(__dirname, '../napcat-common/src/audio-worker.ts'), 'worker/conoutSocketWorker': path.resolve(__dirname, '../napcat-pty/worker/conoutSocketWorker.ts'), }, formats: ['es'],