diff --git a/package.json b/package.json index fad84da5..761edaf7 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,10 @@ }, "dependencies": { "express": "^5.0.0", + "fluent-ffmpeg": "^2.1.2", + "qrcode-terminal": "^0.12.0", "silk-wasm": "^3.6.1", "ws": "^8.18.0", - "qrcode-terminal": "^0.12.0", - "fluent-ffmpeg": "^2.1.2" + "piscina": "^4.7.0" } -} \ No newline at end of file +} diff --git a/src/common/audio-worker.ts b/src/common/audio-worker.ts new file mode 100644 index 00000000..8d84d3b2 --- /dev/null +++ b/src/common/audio-worker.ts @@ -0,0 +1,9 @@ +import { encode } from "silk-wasm"; + +export interface EncodeArgs { + input: ArrayBufferView | ArrayBuffer + sampleRate: number +} +export default async ({ input, sampleRate }: EncodeArgs) => { + return await encode(input, sampleRate); +}; diff --git a/src/common/audio.ts b/src/common/audio.ts index a89a873f..954cfd77 100644 --- a/src/common/audio.ts +++ b/src/common/audio.ts @@ -1,13 +1,19 @@ +import Piscina from 'piscina'; import fsPromise from 'fs/promises'; import path from 'node:path'; import { randomUUID } from 'crypto'; import { spawn } from 'node:child_process'; -import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm'; +import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm'; import { LogWrapper } from './log'; +import { EncodeArgs } from "@/common/audio-worker"; const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000]; const EXIT_CODES = [0, 255]; -const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg'; +const FFMPEG_PATH = process.env.FFMPEG_PATH ?? 'ffmpeg'; + +const piscina = new Piscina({ + filename: new URL('./audio-worker.mjs', import.meta.url).href, +}); async function guessDuration(pttPath: string, logger: LogWrapper) { const pttFileInfo = await fsPromise.stat(pttPath); @@ -41,8 +47,11 @@ async function convert(filePath: string, pcmPath: string, logger: LogWrapper): P } async function handleWavFile( - file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper -): Promise<{input: Buffer, sampleRate: number}> { + file: Buffer, + filePath: string, + pcmPath: string, + logger: LogWrapper +): Promise<{ input: Buffer; sampleRate: number }> { const { fmt } = getWavFileInfo(file); if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) { return { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 }; @@ -60,8 +69,8 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log const { input, sampleRate } = isWav(file) ? (await handleWavFile(file, filePath, pcmPath, logger)) : { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 }; - const silk = await encode(input, sampleRate); - await fsPromise.writeFile(pttPath, silk.data); + const silk = await piscina.run({ input: input, sampleRate: sampleRate }); + await fsPromise.writeFile(pttPath, Buffer.from(silk.data)); logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration); return { converted: true, @@ -86,4 +95,4 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log logger.logError.bind(logger)('convert silk failed', error.stack); return {}; } -} \ No newline at end of file +} diff --git a/vite.config.ts b/vite.config.ts index 81d5d9c7..a0e91db0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,12 +3,14 @@ import { defineConfig, PluginOption, UserConfig } from 'vite'; import { resolve } from 'path'; import nodeResolve from '@rollup/plugin-node-resolve'; import { builtinModules } from 'module'; -//依赖排除 -const external = ['silk-wasm', 'ws', 'express', 'qrcode-terminal', 'fluent-ffmpeg']; + +const external = ['silk-wasm', 'ws', 'express', 'qrcode-terminal', 'fluent-ffmpeg', 'piscina']; const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat(); + function genCpModule(module: string) { return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false }; } + let startScripts: string[] | undefined = undefined; if (process.env.NAPCAT_BUILDSYS == 'linux') { startScripts = []; @@ -17,6 +19,7 @@ if (process.env.NAPCAT_BUILDSYS == 'linux') { } else { startScripts = ['./script/KillQQ.bat']; } + const FrameworkBaseConfigPlugin: PluginOption[] = [ cp({ targets: [ @@ -66,9 +69,12 @@ const ShellBaseConfig = () => defineConfig({ target: 'esnext', minify: false, lib: { - entry: 'src/shell/napcat.ts', + entry: { + 'napcat': 'src/shell/napcat.ts', + 'audio-worker': 'src/common/audio-worker.ts', + }, formats: ['es'], - fileName: () => 'napcat.mjs', + fileName: (_, entryName) => `${entryName}.mjs`, }, rollupOptions: { external: [...nodeModules, ...external], @@ -90,9 +96,12 @@ const FrameworkBaseConfig = () => defineConfig({ target: 'esnext', minify: false, lib: { - entry: 'src/framework/napcat.ts', + entry: { + 'napcat': 'src/framework/napcat.ts', + 'audio-worker': 'src/common/audio-worker.ts', + }, formats: ['es'], - fileName: () => 'napcat.mjs', + fileName: (_, entryName) => `${entryName}.mjs`, }, rollupOptions: { external: [...nodeModules, ...external],