mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-13 11:40:35 +00:00
Add convertToNTSilkTct to FFmpeg adapters and update usage (#1517)
Introduces the convertToNTSilkTct method to FFmpeg adapter interfaces and implementations, updating audio conversion logic to use this new method for Silk format conversion. Refactors FFmpegService to rename convertFile to convertAudioFmt and updates related usages. Removes 'audio-worker' entry from vite configs in napcat-framework and napcat-shell. Also fixes a typo in appid.json. Remove silk-wasm dependency and refactor audio handling Eliminated the silk-wasm package and related code, including audio-worker and direct Silk encoding/decoding logic. Audio format conversion and Silk detection are now handled via FFmpeg adapters. Updated related OneBot actions and configuration files to remove all references to silk-wasm and streamline audio processing.
This commit is contained in:
parent
c5de5e00fc
commit
17d5110069
@ -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'],
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user