mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-11 23:40:24 +00:00
Compare commits
1 Commits
feat/secur
...
remove-sil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e1e3a6df7 |
@@ -28,7 +28,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
"silk-wasm": "^3.6.1",
|
|
||||||
"ws": "^8.18.3"
|
"ws": "^8.18.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^8.13.0",
|
"ajv": "^8.13.0",
|
||||||
"file-type": "^21.0.0",
|
"file-type": "^21.0.0"
|
||||||
"silk-wasm": "^3.6.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.0.1"
|
"@types/node": "^22.0.1"
|
||||||
|
|||||||
@@ -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<T> (cb: (taskData: T) => Promise<unknown>) {
|
|
||||||
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<EncodeArgs>(async ({ input, sampleRate }) => {
|
|
||||||
return await encode(input, sampleRate);
|
|
||||||
});
|
|
||||||
5
packages/napcat-core/external/appid.json
vendored
5
packages/napcat-core/external/appid.json
vendored
@@ -510,5 +510,10 @@
|
|||||||
"3.2.23-44343": {
|
"3.2.23-44343": {
|
||||||
"appid": 537336639,
|
"appid": 537336639,
|
||||||
"qua": "V1_LNX_NQ_3.2.23_44343_GW_B"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,5 +142,9 @@
|
|||||||
"3.2.23-44343-x64": {
|
"3.2.23-44343-x64": {
|
||||||
"send": "59A27B0",
|
"send": "59A27B0",
|
||||||
"recv": "2FFBE90"
|
"recv": "2FFBE90"
|
||||||
|
},
|
||||||
|
"9.9.26-44498-x64": {
|
||||||
|
"send": "0A1051C",
|
||||||
|
"recv": "1D3BC0D"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
packages/napcat-core/external/packet.json
vendored
4
packages/napcat-core/external/packet.json
vendored
@@ -654,5 +654,9 @@
|
|||||||
"3.2.23-44343-arm64": {
|
"3.2.23-44343-arm64": {
|
||||||
"send": "6926F60",
|
"send": "6926F60",
|
||||||
"recv": "692A910"
|
"recv": "692A910"
|
||||||
|
},
|
||||||
|
"9.9.26-44498-x64": {
|
||||||
|
"send": "2CDAE40",
|
||||||
|
"recv": "2CDE3C0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,8 @@
|
|||||||
import fsPromise from 'fs/promises';
|
import fsPromise from 'fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
|
||||||
import { LogWrapper } from '@/napcat-core/helper/log';
|
import { LogWrapper } from '@/napcat-core/helper/log';
|
||||||
import { EncodeArgs } from 'napcat-common/src/audio-worker';
|
|
||||||
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
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) {
|
async function guessDuration (pttPath: string, logger: LogWrapper) {
|
||||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||||
@@ -22,51 +11,23 @@ async function guessDuration (pttPath: string, logger: LogWrapper) {
|
|||||||
return duration;
|
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) {
|
export async function encodeSilk (filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||||
try {
|
try {
|
||||||
const file = await fsPromise.readFile(filePath);
|
|
||||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||||
if (!isSilk(file)) {
|
if (!(await FFmpegService.isSilk(filePath))) {
|
||||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||||
const pcmPath = `${pttPath}.pcm`;
|
await FFmpegService.convertToNTSilkTct(filePath, pttPath);
|
||||||
// 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 };
|
const duration = await FFmpegService.getDuration(filePath);
|
||||||
let input: Buffer;
|
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', duration);
|
||||||
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<EncodeArgs, EncodeResult>(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);
|
|
||||||
return {
|
return {
|
||||||
converted: true,
|
converted: true,
|
||||||
path: pttPath,
|
path: pttPath,
|
||||||
duration: silk.duration / 1000,
|
duration: duration,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let duration = 0;
|
let duration = 0;
|
||||||
try {
|
try {
|
||||||
duration = getDuration(file) / 1000;
|
duration = await FFmpegService.getDuration(filePath);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, (e as Error).stack);
|
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, (e as Error).stack);
|
||||||
duration = await guessDuration(filePath, logger);
|
duration = await guessDuration(filePath, logger);
|
||||||
|
|||||||
@@ -27,21 +27,27 @@ export interface IFFmpegAdapter {
|
|||||||
readonly name: string;
|
readonly name: string;
|
||||||
|
|
||||||
/** 是否可用 */
|
/** 是否可用 */
|
||||||
isAvailable(): Promise<boolean>;
|
isAvailable (): Promise<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取视频信息(包含缩略图)
|
* 获取视频信息(包含缩略图)
|
||||||
* @param videoPath 视频文件路径
|
* @param videoPath 视频文件路径
|
||||||
* @returns 视频信息
|
* @returns 视频信息
|
||||||
*/
|
*/
|
||||||
getVideoInfo(videoPath: string): Promise<VideoInfoResult>;
|
getVideoInfo (videoPath: string): Promise<VideoInfoResult>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取音视频文件时长
|
* 获取音视频文件时长
|
||||||
* @param filePath 文件路径
|
* @param filePath 文件路径
|
||||||
* @returns 时长(秒)
|
* @returns 时长(秒)
|
||||||
*/
|
*/
|
||||||
getDuration(filePath: string): Promise<number>;
|
getDuration (filePath: string): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为 Silk 格式
|
||||||
|
* @param filePath 文件路径
|
||||||
|
*/
|
||||||
|
isSilk (filePath: string): Promise<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换音频为 PCM 格式
|
* 转换音频为 PCM 格式
|
||||||
@@ -49,7 +55,7 @@ export interface IFFmpegAdapter {
|
|||||||
* @param pcmPath 输出 PCM 文件路径
|
* @param pcmPath 输出 PCM 文件路径
|
||||||
* @returns PCM 数据 Buffer
|
* @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 outputFile 输出文件路径
|
||||||
* @param format 目标格式 ('amr' | 'silk' 等)
|
* @param format 目标格式 ('amr' | 'silk' 等)
|
||||||
*/
|
*/
|
||||||
convertFile(inputFile: string, outputFile: string, format: string): Promise<void>;
|
convertFile (inputFile: string, outputFile: string, format: string): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提取视频缩略图
|
* 提取视频缩略图
|
||||||
* @param videoPath 视频文件路径
|
* @param videoPath 视频文件路径
|
||||||
* @param thumbnailPath 缩略图输出路径
|
* @param thumbnailPath 缩略图输出路径
|
||||||
*/
|
*/
|
||||||
extractThumbnail(videoPath: string, thumbnailPath: string): Promise<void>;
|
extractThumbnail (videoPath: string, thumbnailPath: string): Promise<void>;
|
||||||
|
|
||||||
|
convertToNTSilkTct (inputFile: string, outputFile: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { platform, arch } from 'node:os';
|
import { platform, arch } from 'node:os';
|
||||||
import path from 'node:path';
|
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 { writeFile } from 'node:fs/promises';
|
||||||
import type { FFmpeg } from './ffmpeg-addon';
|
import type { FFmpeg } from './ffmpeg-addon';
|
||||||
import type { IFFmpegAdapter, VideoInfoResult } from './ffmpeg-adapter-interface';
|
import type { IFFmpegAdapter, VideoInfoResult } from './ffmpeg-adapter-interface';
|
||||||
@@ -87,6 +87,22 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
|
|||||||
return addon.getDuration(filePath);
|
return addon.getDuration(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为 Silk 格式
|
||||||
|
*/
|
||||||
|
async isSilk (filePath: string): Promise<boolean> {
|
||||||
|
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
|
* 转换为 PCM
|
||||||
*/
|
*/
|
||||||
@@ -106,6 +122,11 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
|
|||||||
await addon.decodeAudioToFmt(inputFile, outputFile, format);
|
await addon.decodeAudioToFmt(inputFile, outputFile, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async convertToNTSilkTct (inputFile: string, outputFile: string): Promise<void> {
|
||||||
|
const addon = this.ensureAddon();
|
||||||
|
await addon.convertToNTSilkTct(inputFile, outputFile);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提取缩略图
|
* 提取缩略图
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,4 +70,6 @@ export interface FFmpeg {
|
|||||||
*/
|
*/
|
||||||
decodeAudioToPCM (filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number; }>;
|
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; }>;
|
decodeAudioToFmt (filePath: string, pcmPath: string, format: string): Promise<{ channels: number; sampleRate: number; format: string; }>;
|
||||||
|
|
||||||
|
convertToNTSilkTct (inputFile: string, outputFile: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* 使用 execFile 调用 FFmpeg 命令行工具的适配器实现
|
* 使用 execFile 调用 FFmpeg 命令行工具的适配器实现
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync, existsSync, mkdirSync } from 'fs';
|
import { readFileSync, existsSync, mkdirSync, openSync, readSync, closeSync } from 'fs';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { execFile } from 'child_process';
|
import { execFile } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
@@ -154,6 +154,22 @@ export class FFmpegExecAdapter implements IFFmpegAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为 Silk 格式
|
||||||
|
*/
|
||||||
|
async isSilk (filePath: string): Promise<boolean> {
|
||||||
|
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
|
* 转换为 PCM
|
||||||
*/
|
*/
|
||||||
@@ -241,4 +257,8 @@ export class FFmpegExecAdapter implements IFFmpegAdapter {
|
|||||||
throw new Error(`提取缩略图失败: ${(error as Error).message}`);
|
throw new Error(`提取缩略图失败: ${(error as Error).message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async convertToNTSilkTct (inputFile: string, outputFile: string): Promise<void> {
|
||||||
|
throw new Error('convertToNTSilkTct is not implemented in FFmpegExecAdapter');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,10 @@ export class FFmpegService {
|
|||||||
}
|
}
|
||||||
return this.adapter;
|
return this.adapter;
|
||||||
}
|
}
|
||||||
|
public static async convertToNTSilkTct (inputFile: string, outputFile: string): Promise<void> {
|
||||||
|
const adapter = await this.getAdapter();
|
||||||
|
await adapter.convertToNTSilkTct(inputFile, outputFile);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 设置 FFmpeg 路径并更新适配器
|
* 设置 FFmpeg 路径并更新适配器
|
||||||
* @deprecated 建议使用 init() 方法初始化
|
* @deprecated 建议使用 init() 方法初始化
|
||||||
@@ -92,11 +95,27 @@ export class FFmpegService {
|
|||||||
/**
|
/**
|
||||||
* 转换音频文件
|
* 转换音频文件
|
||||||
*/
|
*/
|
||||||
public static async convertFile (inputFile: string, outputFile: string, format: string): Promise<void> {
|
public static async convertAudioFmt (inputFile: string, outputFile: string, format: string): Promise<void> {
|
||||||
const adapter = await this.getAdapter();
|
const adapter = await this.getAdapter();
|
||||||
await adapter.convertFile(inputFile, outputFile, format);
|
await adapter.convertFile(inputFile, outputFile, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取音频时长
|
||||||
|
*/
|
||||||
|
public static async getDuration (filePath: string): Promise<number> {
|
||||||
|
const adapter = await this.getAdapter();
|
||||||
|
return adapter.getDuration(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为 Silk 格式
|
||||||
|
*/
|
||||||
|
public static async isSilk (filePath: string): Promise<boolean> {
|
||||||
|
const adapter = await this.getAdapter();
|
||||||
|
return adapter.isSilk(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换为 PCM 格式
|
* 转换为 PCM 格式
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import react from '@vitejs/plugin-react-swc';
|
|||||||
import napcatVersion from 'napcat-vite/vite-plugin-version.js';
|
import napcatVersion from 'napcat-vite/vite-plugin-version.js';
|
||||||
// 依赖排除
|
// 依赖排除
|
||||||
const external = [
|
const external = [
|
||||||
'silk-wasm',
|
|
||||||
'ws',
|
'ws',
|
||||||
'express',
|
'express',
|
||||||
];
|
];
|
||||||
@@ -60,7 +59,6 @@ const FrameworkBaseConfig = () =>
|
|||||||
lib: {
|
lib: {
|
||||||
entry: {
|
entry: {
|
||||||
napcat: path.resolve(__dirname, 'napcat.ts'),
|
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'),
|
'worker/conoutSocketWorker': path.resolve(__dirname, '../napcat-pty/worker/conoutSocketWorker.ts'),
|
||||||
},
|
},
|
||||||
formats: ['es'],
|
formats: ['es'],
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile';
|
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { decode } from 'silk-wasm';
|
|
||||||
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
||||||
|
|
||||||
const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];
|
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)) {
|
if (!out_format.includes(payload.out_format)) {
|
||||||
throw new Error('转换失败 out_format 字段可能格式不正确');
|
throw new Error('转换失败 out_format 字段可能格式不正确');
|
||||||
}
|
}
|
||||||
const pcmFile = `${inputFile}.pcm`;
|
|
||||||
const outputFile = `${inputFile}.${payload.out_format}`;
|
const outputFile = `${inputFile}.${payload.out_format}`;
|
||||||
try {
|
try {
|
||||||
await fs.access(inputFile);
|
await fs.access(inputFile);
|
||||||
try {
|
try {
|
||||||
await fs.access(outputFile);
|
await fs.access(outputFile);
|
||||||
} catch {
|
} catch {
|
||||||
if (FFmpegService.getAdapterName() === 'FFmpegAddon') {
|
await FFmpegService.convertAudioFmt(inputFile, outputFile, payload.out_format);
|
||||||
await FFmpegService.convertFile(inputFile, outputFile, payload.out_format);
|
|
||||||
} else {
|
|
||||||
await this.decodeFile(inputFile, pcmFile);
|
|
||||||
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
|
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
|
||||||
res.file = outputFile;
|
res.file = outputFile;
|
||||||
@@ -46,15 +39,4 @@ export default class GetRecord extends GetFileBase {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async decodeFile (inputFile: string, outputFile: string): Promise<void> {
|
|
||||||
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; // 重新抛出错误以便调用者可以处理
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Static, Type } from '@sinclair/typebox';
|
|||||||
import { NetworkAdapterConfig } from '@/napcat-onebot/config/config';
|
import { NetworkAdapterConfig } from '@/napcat-onebot/config/config';
|
||||||
import { StreamPacket, StreamStatus } from './StreamBasic';
|
import { StreamPacket, StreamStatus } from './StreamBasic';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { decode } from 'silk-wasm';
|
|
||||||
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
||||||
import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream';
|
import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream';
|
||||||
|
|
||||||
@@ -38,7 +37,6 @@ export class DownloadFileRecordStream extends BaseDownloadStream<Payload, Downlo
|
|||||||
throw new Error('转换失败 out_format 字段可能格式不正确');
|
throw new Error('转换失败 out_format 字段可能格式不正确');
|
||||||
}
|
}
|
||||||
|
|
||||||
const pcmFile = `${downloadPath}.pcm`;
|
|
||||||
const outputFile = `${downloadPath}.${payload.out_format}`;
|
const outputFile = `${downloadPath}.${payload.out_format}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -46,13 +44,8 @@ export class DownloadFileRecordStream extends BaseDownloadStream<Payload, Downlo
|
|||||||
await fs.promises.access(outputFile);
|
await fs.promises.access(outputFile);
|
||||||
streamPath = outputFile;
|
streamPath = outputFile;
|
||||||
} catch {
|
} catch {
|
||||||
// 尝试解码 silk 到 pcm 再用 ffmpeg 转换
|
// 尝试解码 amr 到 out format直接 ffmpeg 转换
|
||||||
if (FFmpegService.getAdapterName() === 'FFmpegAddon') {
|
await FFmpegService.convertAudioFmt(downloadPath, outputFile, payload.out_format);
|
||||||
await FFmpegService.convertFile(downloadPath, outputFile, payload.out_format);
|
|
||||||
} else {
|
|
||||||
await this.decodeFile(downloadPath, pcmFile);
|
|
||||||
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
|
|
||||||
}
|
|
||||||
streamPath = outputFile;
|
streamPath = outputFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,15 +75,4 @@ export class DownloadFileRecordStream extends BaseDownloadStream<Payload, Downlo
|
|||||||
throw new Error(`Download failed: ${(error as Error).message}`);
|
throw new Error(`Download failed: ${(error as Error).message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async decodeFile (inputFile: string, outputFile: string): Promise<void> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1075,7 +1075,8 @@ export class OneBotMsgApi {
|
|||||||
resMsg.sub_type = 'group';
|
resMsg.sub_type = 'group';
|
||||||
const ret = await this.core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid);
|
const ret = await this.core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid);
|
||||||
if (ret.result === 0) {
|
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.group_id = parseInt(ret.tmpChatInfo!.groupCode);
|
||||||
resMsg.sender.nickname = member?.nick ?? member?.cardName ?? '临时会话';
|
resMsg.sender.nickname = member?.nick ?? member?.cardName ?? '临时会话';
|
||||||
resMsg.temp_source = 0;
|
resMsg.temp_source = 0;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import react from '@vitejs/plugin-react-swc';
|
|||||||
|
|
||||||
// 依赖排除
|
// 依赖排除
|
||||||
const external = [
|
const external = [
|
||||||
'silk-wasm',
|
|
||||||
'ws',
|
'ws',
|
||||||
'express',
|
'express',
|
||||||
];
|
];
|
||||||
@@ -56,7 +55,6 @@ const ShellBaseConfig = (source_map: boolean = false) =>
|
|||||||
lib: {
|
lib: {
|
||||||
entry: {
|
entry: {
|
||||||
napcat: path.resolve(__dirname, 'napcat.ts'),
|
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'),
|
'worker/conoutSocketWorker': path.resolve(__dirname, '../napcat-pty/worker/conoutSocketWorker.ts'),
|
||||||
},
|
},
|
||||||
formats: ['es'],
|
formats: ['es'],
|
||||||
|
|||||||
Reference in New Issue
Block a user