dsh-pf-stream 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.en.md +145 -33
  2. package/README.md +142 -30
  3. package/client.js +1156 -0
  4. package/icon.svg +18 -0
  5. package/lib/asr/catalog.d.ts +74 -0
  6. package/lib/asr/catalog.js +168 -0
  7. package/lib/asr/catalog.js.map +1 -0
  8. package/lib/asr/detect.d.ts +33 -0
  9. package/lib/asr/detect.js +117 -0
  10. package/lib/asr/detect.js.map +1 -0
  11. package/lib/asr/engine.d.ts +43 -0
  12. package/lib/asr/engine.js +143 -0
  13. package/lib/asr/engine.js.map +1 -0
  14. package/lib/asr/install.d.ts +56 -0
  15. package/lib/asr/install.js +317 -0
  16. package/lib/asr/install.js.map +1 -0
  17. package/lib/asr/verify.d.ts +22 -0
  18. package/lib/asr/verify.js +174 -0
  19. package/lib/asr/verify.js.map +1 -0
  20. package/lib/audio/acousticVad.d.ts +45 -0
  21. package/lib/audio/acousticVad.js +179 -0
  22. package/lib/audio/acousticVad.js.map +1 -0
  23. package/lib/audio/asrProviders.d.ts +51 -0
  24. package/lib/audio/asrProviders.js +251 -0
  25. package/lib/audio/asrProviders.js.map +1 -0
  26. package/lib/audio/asrRouter.d.ts +30 -0
  27. package/lib/audio/asrRouter.js +187 -0
  28. package/lib/audio/asrRouter.js.map +1 -0
  29. package/lib/audio/hallucinationGuard.d.ts +18 -0
  30. package/lib/audio/hallucinationGuard.js +97 -0
  31. package/lib/audio/hallucinationGuard.js.map +1 -0
  32. package/lib/audio/sidecar.d.ts +76 -0
  33. package/lib/audio/sidecar.js +362 -0
  34. package/lib/audio/sidecar.js.map +1 -0
  35. package/lib/audio/subtitleExtractor.d.ts +36 -0
  36. package/lib/audio/subtitleExtractor.js +218 -0
  37. package/lib/audio/subtitleExtractor.js.map +1 -0
  38. package/lib/audio/transcriptCache.d.ts +33 -0
  39. package/lib/audio/transcriptCache.js +126 -0
  40. package/lib/audio/transcriptCache.js.map +1 -0
  41. package/lib/audio/types.d.ts +136 -0
  42. package/lib/audio/types.js +10 -0
  43. package/lib/audio/types.js.map +1 -0
  44. package/lib/host/api.d.ts +23 -0
  45. package/lib/host/api.js +286 -0
  46. package/lib/host/api.js.map +1 -0
  47. package/lib/host/runtime.d.ts +167 -0
  48. package/lib/host/runtime.js +335 -0
  49. package/lib/host/runtime.js.map +1 -0
  50. package/lib/index.d.ts +229 -1
  51. package/lib/index.js +103 -2
  52. package/lib/index.js.map +1 -1
  53. package/lib/session/state.js +4 -0
  54. package/lib/session/state.js.map +1 -1
  55. package/lib/stage0/decoder.d.ts +1 -9
  56. package/lib/stage0/decoder.js +15 -45
  57. package/lib/stage0/decoder.js.map +1 -1
  58. package/lib/stage2/stateMachine.d.ts +4 -1
  59. package/lib/stage2/stateMachine.js +7 -1
  60. package/lib/stage2/stateMachine.js.map +1 -1
  61. package/lib/stage3/deterministicRouter.js +3 -3
  62. package/lib/stage3/deterministicRouter.js.map +1 -1
  63. package/lib/stage3/streamSampler.d.ts +9 -2
  64. package/lib/stage3/streamSampler.js +37 -16
  65. package/lib/stage3/streamSampler.js.map +1 -1
  66. package/lib/stage6/history.d.ts +2 -1
  67. package/lib/stage6/history.js +4 -1
  68. package/lib/stage6/history.js.map +1 -1
  69. package/lib/tool/handler.d.ts +9 -0
  70. package/lib/tool/handler.js +33 -3
  71. package/lib/tool/handler.js.map +1 -1
  72. package/lib/tool/schema.js +15 -2
  73. package/lib/tool/schema.js.map +1 -1
  74. package/lib/tool/streamCall.d.ts +11 -0
  75. package/lib/tool/streamCall.js +122 -9
  76. package/lib/tool/streamCall.js.map +1 -1
  77. package/lib/types.d.ts +9 -0
  78. package/locale/en.json +6 -0
  79. package/locale/zh.json +6 -0
  80. package/package.json +17 -2
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 本地转写资源安装器:下载 → 校验 → 解压 → 落位。
3
+ *
4
+ * 约束:
5
+ * - 不引入任何运行时依赖(包只允许 pngjs);用 Node 内置 fetch/stream/child_process。
6
+ * - 下载可取消、可断点续传;进度对设置页可见。
7
+ * - 失败必须给出可行动的原因(网络/磁盘/校验),不吞异常。
8
+ */
9
+ export type InstallTarget = 'model' | 'engine' | 'all';
10
+ export type InstallPhase = 'idle' | 'preparing' | 'downloading' | 'verifying' | 'extracting' | 'done' | 'failed' | 'cancelled';
11
+ export interface InstallJob {
12
+ id: string;
13
+ target: InstallTarget;
14
+ phase: InstallPhase;
15
+ /** 当前资源的人类可读名(引擎包 / 模型文件)。 */
16
+ resource: string;
17
+ received: number;
18
+ total: number | null;
19
+ message: string | null;
20
+ startedAt: number;
21
+ finishedAt: number | null;
22
+ result: {
23
+ exe?: string;
24
+ modelPath?: string;
25
+ integrity?: 'sha256' | 'size';
26
+ } | null;
27
+ }
28
+ export interface InstallerOptions {
29
+ cacheDir: string;
30
+ modelId: string;
31
+ /** 自定义下载基地址(镜像/离线),缺省用官方源。 */
32
+ logger?: (line: string) => void;
33
+ /** 任务结束(成功/失败/取消)时回调,用于让探测缓存失效。 */
34
+ onFinished?: (() => void) | undefined;
35
+ }
36
+ /**
37
+ * 单任务安装器:同一时刻只允许一个下载任务,避免抢占带宽与磁盘。
38
+ * 状态保存在内存,页面轮询;宿主重启后重置(已落盘的文件仍有效)。
39
+ */
40
+ export declare class LocalAsrInstaller {
41
+ private options;
42
+ private job;
43
+ private controller;
44
+ constructor(options: InstallerOptions);
45
+ /** 配置变更后同步模型档位与缓存目录(下一次安装生效,进行中的任务不打断)。 */
46
+ update(patch: Partial<Pick<InstallerOptions, 'cacheDir' | 'modelId'>>): void;
47
+ current(): InstallJob | null;
48
+ private patch;
49
+ private progress;
50
+ cancel(): void;
51
+ /** 启动安装;已有进行中的任务时直接返回该任务。 */
52
+ start(target: InstallTarget): InstallJob;
53
+ private run;
54
+ }
55
+ /** 计算目录占用(用于设置页显示缓存体积)。 */
56
+ export declare function directoryBytes(dir: string): number;
@@ -0,0 +1,317 @@
1
+ /**
2
+ * 本地转写资源安装器:下载 → 校验 → 解压 → 落位。
3
+ *
4
+ * 约束:
5
+ * - 不引入任何运行时依赖(包只允许 pngjs);用 Node 内置 fetch/stream/child_process。
6
+ * - 下载可取消、可断点续传;进度对设置页可见。
7
+ * - 失败必须给出可行动的原因(网络/磁盘/校验),不吞异常。
8
+ */
9
+ import { spawn } from 'node:child_process';
10
+ import { createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, } from 'node:fs';
11
+ import { dirname, join } from 'node:path';
12
+ import { Readable } from 'node:stream';
13
+ import { pipeline } from 'node:stream/promises';
14
+ import { managedPaths, modelDownloadUrls, modelPreset, whisperCppUrls, } from './catalog.js';
15
+ const PART_SUFFIX = '.part';
16
+ function fileSize(path) {
17
+ try {
18
+ return statSync(path).size;
19
+ }
20
+ catch {
21
+ return 0;
22
+ }
23
+ }
24
+ /**
25
+ * 逐 URL 尝试下载到 dest;dest.part 保留已下载字节,支持 Range 续传。
26
+ * 404/网络错误自动换下一个源;用户取消立即抛出 AbortError。
27
+ */
28
+ async function downloadFirst(urls, dest, onProgress, signal) {
29
+ const part = `${dest}${PART_SUFFIX}`;
30
+ const failures = [];
31
+ for (const url of urls) {
32
+ let start = fileSize(part);
33
+ let response;
34
+ try {
35
+ response = await fetch(url, {
36
+ redirect: 'follow',
37
+ headers: start > 0 ? { range: `bytes=${start}-` } : {},
38
+ signal,
39
+ });
40
+ }
41
+ catch (error) {
42
+ if (signal.aborted)
43
+ throw error;
44
+ failures.push(`${url}: ${error.message}`);
45
+ continue;
46
+ }
47
+ if (response.status === 416) {
48
+ // 续传位置已到文件末尾:视为完成。
49
+ return { url, bytes: start };
50
+ }
51
+ if (!response.ok) {
52
+ failures.push(`${url}: HTTP ${response.status}`);
53
+ continue;
54
+ }
55
+ if (start > 0 && response.status !== 206)
56
+ start = 0;
57
+ const lengthHeader = response.headers.get('content-length');
58
+ const incoming = lengthHeader === null ? 0 : Number(lengthHeader) || 0;
59
+ const total = start + incoming > 0 ? start + incoming : null;
60
+ if (response.body === null) {
61
+ failures.push(`${url}: 响应没有内容`);
62
+ continue;
63
+ }
64
+ mkdirSync(join(dest, '..'), { recursive: true });
65
+ const sink = createWriteStream(part, { flags: start > 0 ? 'a' : 'w' });
66
+ let received = start;
67
+ const source = Readable.fromWeb(response.body);
68
+ source.on('data', (chunk) => {
69
+ received += chunk.length;
70
+ onProgress(received, total);
71
+ });
72
+ try {
73
+ await pipeline(source, sink, { signal });
74
+ }
75
+ catch (error) {
76
+ if (signal.aborted)
77
+ throw error;
78
+ failures.push(`${url}: ${error.message}`);
79
+ continue;
80
+ }
81
+ if (total !== null && received < total) {
82
+ failures.push(`${url}: 连接提前结束(${received}/${total} 字节)`);
83
+ continue;
84
+ }
85
+ return { url, bytes: received };
86
+ }
87
+ throw new Error(`下载失败,已尝试 ${urls.length} 个源:\n${failures.join('\n')}`);
88
+ }
89
+ function run(command, args, signal) {
90
+ return new Promise((resolve, reject) => {
91
+ const child = spawn(command, args, { stdio: 'ignore', windowsHide: true, signal });
92
+ child.on('error', reject);
93
+ child.on('exit', (code) => {
94
+ if (code === 0)
95
+ resolve();
96
+ else
97
+ reject(new Error(`${command} 退出码 ${String(code)}`));
98
+ });
99
+ });
100
+ }
101
+ function findExecutable(dir, names, depth = 3) {
102
+ if (depth < 0)
103
+ return null;
104
+ let entries;
105
+ try {
106
+ entries = readdirSync(dir);
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ for (const name of names) {
112
+ const direct = join(dir, name);
113
+ if (existsSync(direct) && statSync(direct).isFile())
114
+ return direct;
115
+ }
116
+ for (const entry of entries) {
117
+ const child = join(dir, entry);
118
+ try {
119
+ if (!statSync(child).isDirectory())
120
+ continue;
121
+ }
122
+ catch {
123
+ continue;
124
+ }
125
+ const found = findExecutable(child, names, depth - 1);
126
+ if (found !== null)
127
+ return found;
128
+ }
129
+ return null;
130
+ }
131
+ async function extractZip(archive, dest, signal) {
132
+ mkdirSync(dest, { recursive: true });
133
+ try {
134
+ await run('tar', ['-xf', archive, '-C', dest], signal);
135
+ return;
136
+ }
137
+ catch (error) {
138
+ if (signal.aborted)
139
+ throw error;
140
+ if (process.platform !== 'win32')
141
+ throw error;
142
+ }
143
+ // Windows 老版本没有 bsdtar:退回 PowerShell。
144
+ await run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `Expand-Archive -LiteralPath '${archive}' -DestinationPath '${dest}' -Force`], signal);
145
+ }
146
+ let jobSeq = 0;
147
+ /**
148
+ * 单任务安装器:同一时刻只允许一个下载任务,避免抢占带宽与磁盘。
149
+ * 状态保存在内存,页面轮询;宿主重启后重置(已落盘的文件仍有效)。
150
+ */
151
+ export class LocalAsrInstaller {
152
+ options;
153
+ job = null;
154
+ controller = null;
155
+ constructor(options) {
156
+ this.options = options;
157
+ }
158
+ /** 配置变更后同步模型档位与缓存目录(下一次安装生效,进行中的任务不打断)。 */
159
+ update(patch) {
160
+ this.options = { ...this.options, ...patch };
161
+ }
162
+ current() {
163
+ return this.job;
164
+ }
165
+ patch(patch) {
166
+ if (this.job === null)
167
+ return;
168
+ this.job = { ...this.job, ...patch };
169
+ }
170
+ progress(received, total) {
171
+ this.patch({ received, total });
172
+ }
173
+ cancel() {
174
+ if (this.job === null)
175
+ return;
176
+ this.patch({ phase: 'cancelled', message: '已取消', finishedAt: Date.now() });
177
+ this.controller?.abort();
178
+ this.controller = null;
179
+ }
180
+ /** 启动安装;已有进行中的任务时直接返回该任务。 */
181
+ start(target) {
182
+ if (this.job !== null && (this.job.phase === 'downloading' || this.job.phase === 'verifying' || this.job.phase === 'extracting' || this.job.phase === 'preparing')) {
183
+ return this.job;
184
+ }
185
+ const job = {
186
+ id: `install-${++jobSeq}`,
187
+ target,
188
+ phase: 'preparing',
189
+ resource: '',
190
+ received: 0,
191
+ total: null,
192
+ message: null,
193
+ startedAt: Date.now(),
194
+ finishedAt: null,
195
+ result: null,
196
+ };
197
+ this.job = job;
198
+ this.controller = new AbortController();
199
+ const signal = this.controller.signal;
200
+ void this.run(job, signal)
201
+ .catch((error) => {
202
+ if (signal.aborted) {
203
+ this.patch({ phase: 'cancelled', finishedAt: Date.now(), message: '已取消' });
204
+ return;
205
+ }
206
+ this.patch({
207
+ phase: 'failed',
208
+ finishedAt: Date.now(),
209
+ message: error instanceof Error ? error.message : String(error),
210
+ });
211
+ })
212
+ .finally(() => {
213
+ this.options.onFinished?.();
214
+ });
215
+ return job;
216
+ }
217
+ async run(job, signal) {
218
+ const { cacheDir } = this.options;
219
+ const preset = modelPreset(this.options.modelId);
220
+ const managed = managedPaths(cacheDir);
221
+ const result = {};
222
+ mkdirSync(managed.modelDir, { recursive: true });
223
+ mkdirSync(managed.binDir, { recursive: true });
224
+ if (job.target === 'engine' || job.target === 'all') {
225
+ if (!existsSync(managed.exe) && process.platform !== 'win32') {
226
+ throw new Error('当前平台暂不支持一键安装本地引擎。请安装 whisper.cpp 后,在配置文件 audio.asr.local.binPath 填写可执行文件路径,或改用云端转写。');
227
+ }
228
+ if (!existsSync(managed.exe)) {
229
+ this.patch({ resource: 'whisper.cpp 引擎', phase: 'downloading', received: 0, total: null });
230
+ const archive = join(managed.binDir, 'whisper-cpp.zip');
231
+ const partialArchive = `${archive}${PART_SUFFIX}`;
232
+ await downloadFirst(whisperCppUrls(), archive, (r, t) => this.progress(r, t), signal);
233
+ // downloadFirst 只写 .part,改名由调用方负责;漏了这步解压会找不到文件。
234
+ if (existsSync(partialArchive))
235
+ renameSync(partialArchive, archive);
236
+ this.patch({ resource: 'whisper.cpp 引擎', phase: 'extracting' });
237
+ await extractZip(archive, managed.binDir, signal);
238
+ rmSync(archive, { force: true });
239
+ const exe = findExecutable(managed.binDir, ['whisper-cli.exe', 'whisper-cli', 'main.exe']);
240
+ if (exe === null) {
241
+ throw new Error('下载的压缩包里没有找到 whisper-cli 可执行文件');
242
+ }
243
+ // 官方 Windows 包把 exe 与 DLL 放在 Release/ 子目录:整体搬到 binDir,
244
+ // 否则只挪 exe 会丢掉 ggml.dll / whisper.dll,一运行就报错。
245
+ const exeDir = dirname(exe);
246
+ if (exeDir !== managed.binDir) {
247
+ for (const name of readdirSync(exeDir)) {
248
+ const from = join(exeDir, name);
249
+ try {
250
+ if (!statSync(from).isFile())
251
+ continue;
252
+ }
253
+ catch {
254
+ continue;
255
+ }
256
+ const to = join(managed.binDir, name);
257
+ rmSync(to, { force: true });
258
+ renameSync(from, to);
259
+ }
260
+ rmSync(exeDir, { recursive: true, force: true });
261
+ }
262
+ if (!existsSync(managed.exe)) {
263
+ const located = findExecutable(managed.binDir, ['whisper-cli.exe', 'whisper-cli', 'main.exe']);
264
+ if (located !== null && located !== managed.exe)
265
+ renameSync(located, managed.exe);
266
+ }
267
+ }
268
+ }
269
+ if (job.target === 'model' || job.target === 'all') {
270
+ const modelPath = join(managed.modelDir, preset.file);
271
+ if (!existsSync(modelPath)) {
272
+ this.patch({ resource: preset.label, phase: 'downloading', received: 0, total: null });
273
+ const partial = `${modelPath}${PART_SUFFIX}`;
274
+ const done = await downloadFirst(modelDownloadUrls(preset), modelPath, (r, t) => this.progress(r, t), signal);
275
+ this.patch({ phase: 'verifying', message: '校验文件…' });
276
+ const bytes = fileSize(partial);
277
+ if (bytes < 1024 * 1024)
278
+ throw new Error(`模型文件不完整(${bytes} 字节)`);
279
+ renameSync(partial, modelPath);
280
+ result.modelPath = modelPath;
281
+ result.integrity = 'size';
282
+ this.options.logger?.(`model downloaded from ${done.url} (${bytes} bytes)`);
283
+ }
284
+ if (!('modelPath' in result)) {
285
+ result.modelPath = modelPath;
286
+ result.integrity = 'size';
287
+ }
288
+ }
289
+ if (existsSync(managed.exe))
290
+ result.exe = managed.exe;
291
+ this.patch({
292
+ phase: 'done',
293
+ finishedAt: Date.now(),
294
+ resource: '',
295
+ message: job.target === 'model' ? '模型已就绪' : '本地转写已就绪',
296
+ result,
297
+ });
298
+ }
299
+ }
300
+ /** 计算目录占用(用于设置页显示缓存体积)。 */
301
+ export function directoryBytes(dir) {
302
+ let total = 0;
303
+ try {
304
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
305
+ const path = join(dir, entry.name);
306
+ if (entry.isDirectory())
307
+ total += directoryBytes(path);
308
+ else if (entry.isFile())
309
+ total += fileSize(path);
310
+ }
311
+ }
312
+ catch {
313
+ return total;
314
+ }
315
+ return total;
316
+ }
317
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/asr/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 「点一下验证」:用一段 1 秒 440Hz 的合成音跑真实转写链路。
3
+ *
4
+ * 为什么不用 ping/模型列表:初学者关心的是「这台机器能不能出字」,
5
+ * 只有真正跑一次引擎才能回答。合成音没有语音内容,但足以证明
6
+ * 进程能启动、模型能加载、网络与密钥可用。
7
+ */
8
+ import type { AsrSettings } from '../audio/types.js';
9
+ /** 16kHz / 16bit / 单声道 WAV,1 秒 440Hz 正弦。 */
10
+ export declare function toneWav(seconds?: number, sampleRate?: number): Buffer;
11
+ export interface VerifyResult {
12
+ ok: boolean;
13
+ kind: 'cloud' | 'local';
14
+ latencyMs: number;
15
+ /** 云端返回的文本(几乎总是空);本地不返回。 */
16
+ text: string | null;
17
+ message: string;
18
+ }
19
+ /** 本地:跑一次 1 秒音频,能产出 SRT(哪怕空)即算通过。 */
20
+ export declare function verifyLocal(settings: AsrSettings, timeoutMs?: number): Promise<VerifyResult>;
21
+ /** 云端:把同一段音频发给端点的 /audio/transcriptions。 */
22
+ export declare function verifyCloud(settings: AsrSettings, timeoutMs?: number): Promise<VerifyResult>;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * 「点一下验证」:用一段 1 秒 440Hz 的合成音跑真实转写链路。
3
+ *
4
+ * 为什么不用 ping/模型列表:初学者关心的是「这台机器能不能出字」,
5
+ * 只有真正跑一次引擎才能回答。合成音没有语音内容,但足以证明
6
+ * 进程能启动、模型能加载、网络与密钥可用。
7
+ */
8
+ import { spawn } from 'node:child_process';
9
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
10
+ import { tmpdir } from 'node:os';
11
+ import path from 'node:path';
12
+ import { transcriptionUrl } from '../audio/asrProviders.js';
13
+ /** 16kHz / 16bit / 单声道 WAV,1 秒 440Hz 正弦。 */
14
+ export function toneWav(seconds = 1, sampleRate = 16_000) {
15
+ const samples = Math.floor(seconds * sampleRate);
16
+ const data = Buffer.alloc(samples * 2);
17
+ for (let i = 0; i < samples; i += 1) {
18
+ const value = Math.round(Math.sin((2 * Math.PI * 440 * i) / sampleRate) * 0.2 * 32767);
19
+ data.writeInt16LE(value, i * 2);
20
+ }
21
+ const header = Buffer.alloc(44);
22
+ header.write('RIFF', 0);
23
+ header.writeUInt32LE(36 + data.length, 4);
24
+ header.write('WAVE', 8);
25
+ header.write('fmt ', 12);
26
+ header.writeUInt32LE(16, 16);
27
+ header.writeUInt16LE(1, 20);
28
+ header.writeUInt16LE(1, 22);
29
+ header.writeUInt32LE(sampleRate, 24);
30
+ header.writeUInt32LE(sampleRate * 2, 28);
31
+ header.writeUInt16LE(2, 32);
32
+ header.writeUInt16LE(16, 34);
33
+ header.write('data', 36);
34
+ header.writeUInt32LE(data.length, 40);
35
+ return Buffer.concat([header, data]);
36
+ }
37
+ function runCaptured(command, args, timeoutMs) {
38
+ return new Promise((resolve) => {
39
+ let stderr = '';
40
+ let settled = false;
41
+ const child = spawn(command, args, { windowsHide: true, stdio: ['ignore', 'ignore', 'pipe'] });
42
+ const timer = setTimeout(() => {
43
+ if (settled)
44
+ return;
45
+ settled = true;
46
+ child.kill();
47
+ resolve({ code: null, stderr: '验证超时', spawnError: null });
48
+ }, timeoutMs);
49
+ child.stderr?.on('data', (chunk) => {
50
+ if (stderr.length < 4000)
51
+ stderr += chunk.toString('utf8');
52
+ });
53
+ child.on('error', (error) => {
54
+ if (settled)
55
+ return;
56
+ settled = true;
57
+ clearTimeout(timer);
58
+ resolve({ code: null, stderr: '', spawnError: error });
59
+ });
60
+ child.on('exit', (code) => {
61
+ if (settled)
62
+ return;
63
+ settled = true;
64
+ clearTimeout(timer);
65
+ resolve({ code, stderr, spawnError: null });
66
+ });
67
+ });
68
+ }
69
+ /** 本地:跑一次 1 秒音频,能产出 SRT(哪怕空)即算通过。 */
70
+ export async function verifyLocal(settings, timeoutMs = 60_000) {
71
+ const started = Date.now();
72
+ if (settings.model === null) {
73
+ return { ok: false, kind: 'local', latencyMs: 0, text: null, message: '没有可执行文件' };
74
+ }
75
+ const dir = await mkdtemp(path.join(tmpdir(), 'pf-verify-'));
76
+ try {
77
+ const audio = path.join(dir, 'probe.wav');
78
+ await writeFile(audio, toneWav());
79
+ const args = (settings.args ?? []).map((token) => token
80
+ .split('{audio}').join(audio)
81
+ .split('{dir}').join(dir)
82
+ .split('{outbase}').join(path.join(dir, 'chunk'))
83
+ .split('{out}').join(path.join(dir, 'cli-out.txt')));
84
+ if (!args.includes(audio))
85
+ args.push(audio);
86
+ const result = await runCaptured(settings.model, args, timeoutMs);
87
+ const latencyMs = Date.now() - started;
88
+ if (result.spawnError !== null) {
89
+ return {
90
+ ok: false,
91
+ kind: 'local',
92
+ latencyMs,
93
+ text: null,
94
+ message: `无法启动:${result.spawnError.message}`,
95
+ };
96
+ }
97
+ if (result.code !== 0) {
98
+ const tail = result.stderr.trim().split(/\r?\n/).slice(-3).join(' / ');
99
+ return {
100
+ ok: false,
101
+ kind: 'local',
102
+ latencyMs,
103
+ text: null,
104
+ message: `引擎退出码 ${String(result.code)}${tail === '' ? '' : `:${tail}`}`,
105
+ };
106
+ }
107
+ return { ok: true, kind: 'local', latencyMs, text: null, message: '本地引擎可用' };
108
+ }
109
+ finally {
110
+ await rm(dir, { recursive: true, force: true }).catch(() => undefined);
111
+ }
112
+ }
113
+ /** 云端:把同一段音频发给端点的 /audio/transcriptions。 */
114
+ export async function verifyCloud(settings, timeoutMs = 20_000) {
115
+ const started = Date.now();
116
+ if (settings.baseUrl === null || settings.apiKey === null) {
117
+ return { ok: false, kind: 'cloud', latencyMs: 0, text: null, message: '缺少端点或密钥' };
118
+ }
119
+ const form = new FormData();
120
+ form.append('file', new Blob([new Uint8Array(toneWav())], { type: 'audio/wav' }), 'probe.wav');
121
+ form.append('model', settings.model ?? 'whisper-1');
122
+ form.append('response_format', 'json');
123
+ const controller = new AbortController();
124
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
125
+ try {
126
+ const response = await fetch(transcriptionUrl(settings.baseUrl), {
127
+ method: 'POST',
128
+ headers: { Authorization: `Bearer ${settings.apiKey}` },
129
+ body: form,
130
+ signal: controller.signal,
131
+ });
132
+ const latencyMs = Date.now() - started;
133
+ if (response.status === 401 || response.status === 403) {
134
+ return { ok: false, kind: 'cloud', latencyMs, text: null, message: '密钥被拒绝(401/403)' };
135
+ }
136
+ if (response.status === 402) {
137
+ return { ok: false, kind: 'cloud', latencyMs, text: null, message: '账户余额不足(402)' };
138
+ }
139
+ if (response.status === 404) {
140
+ return { ok: false, kind: 'cloud', latencyMs, text: null, message: '端点不存在(404),检查服务地址' };
141
+ }
142
+ if (!response.ok) {
143
+ const body = (await response.text().catch(() => '')).slice(0, 200);
144
+ return {
145
+ ok: false,
146
+ kind: 'cloud',
147
+ latencyMs,
148
+ text: null,
149
+ message: `HTTP ${response.status}${body === '' ? '' : `:${body}`}`,
150
+ };
151
+ }
152
+ const doc = (await response.json().catch(() => ({})));
153
+ return {
154
+ ok: true,
155
+ kind: 'cloud',
156
+ latencyMs,
157
+ text: doc.text ?? '',
158
+ message: '密钥与端点可用',
159
+ };
160
+ }
161
+ catch (error) {
162
+ return {
163
+ ok: false,
164
+ kind: 'cloud',
165
+ latencyMs: Date.now() - started,
166
+ text: null,
167
+ message: error instanceof Error ? error.message : String(error),
168
+ };
169
+ }
170
+ finally {
171
+ clearTimeout(timer);
172
+ }
173
+ }
174
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/asr/verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Stage A1 —— 声学 VAD(silencedetect + astats)与切分规划。
3
+ *
4
+ * silencedetect=noise=-35dB:d=0.4 提取静音区间;astats(reset=0) 取整窗
5
+ * RMS/峰值电平。静音占比 ≥95% 或峰值 < -45dBFS 判定整窗无语音(短路)。
6
+ * 长窗口按 VAD 谷底在 [45s, 75s] 弹性切分;无谷底可用时 60s 硬切。
7
+ */
8
+ import type { VadResult } from './types.js';
9
+ export declare const SILENT_ACTIVE_RATIO_MAX = 0.05;
10
+ export declare const SILENT_PEAK_DB_MIN = -45;
11
+ export interface VadScanOptions {
12
+ ffmpegPath: string;
13
+ signal?: AbortSignal | undefined;
14
+ }
15
+ /**
16
+ * 窗口声学扫描(-ss 前置输入寻址;PTS 相对窗口起点换算为绝对 PTS)。
17
+ * 解析失败时返回 null(调用方按无 VAD 信息继续,不短路)。
18
+ */
19
+ export declare function scanVad(videoPath: string, window: [number, number], options: VadScanOptions): Promise<VadResult | null>;
20
+ /** 整窗无语音判定(静音占比 ≥95% 或峰值 < -45dBFS)。 */
21
+ export declare function isSilentWindow(vad: VadResult | null): boolean;
22
+ export interface ChunkPlanOptions {
23
+ /** 切分窗口下限(毫秒)。 */
24
+ minMs: number;
25
+ /** 切分窗口上限(毫秒)。 */
26
+ maxMs: number;
27
+ /** 无谷底时的硬切长度(毫秒)。 */
28
+ hardMs: number;
29
+ }
30
+ export declare const DEFAULT_CHUNK_PLAN: ChunkPlanOptions;
31
+ /**
32
+ * 带时间戳 provider 的切分:谷底优先(取 [min, max] 内最深静音的中点),
33
+ * 否则 hardMs 硬切。切点落在静音中点,句子不被腰斩。
34
+ */
35
+ export declare function planChunks(window: [number, number], vad: VadResult | null, options?: ChunkPlanOptions): Array<[number, number]>;
36
+ /** 无时间戳 provider(openai_basic)的切分:语音岛直切,>30s 的岛硬切。 */
37
+ export declare function planIslandChunks(window: [number, number], vad: VadResult | null, maxIslandMs?: number): Array<[number, number]>;
38
+ /**
39
+ * 切片音频编码(16kHz 单声道;mp3 = libmp3lame -q:a 6,opus = libopus 24k)。
40
+ * 返回 {bytes, mimeType};失败抛错由路由层按块失败处理。
41
+ */
42
+ export declare function sliceAudio(videoPath: string, range: [number, number], codec: 'mp3' | 'opus', options: VadScanOptions): Promise<{
43
+ bytes: Buffer;
44
+ mimeType: string;
45
+ }>;