dsh-pf-stream 0.2.3 → 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 +119 -95
  2. package/README.md +119 -96
  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 +23 -3
  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,179 @@
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 { execFile } from '../stage0/ffmpeg.js';
9
+ const SILENCE_NOISE_DB = -35;
10
+ const SILENCE_MIN_DURATION_S = 0.4;
11
+ export const SILENT_ACTIVE_RATIO_MAX = 0.05;
12
+ export const SILENT_PEAK_DB_MIN = -45;
13
+ const MIN_ISLAND_MS = 200;
14
+ /**
15
+ * 窗口声学扫描(-ss 前置输入寻址;PTS 相对窗口起点换算为绝对 PTS)。
16
+ * 解析失败时返回 null(调用方按无 VAD 信息继续,不短路)。
17
+ */
18
+ export async function scanVad(videoPath, window, options) {
19
+ const [w0, w1] = window;
20
+ const durationS = (w1 - w0) / 1000;
21
+ let stderr;
22
+ try {
23
+ const result = await execFile(options.ffmpegPath, [
24
+ '-nostdin',
25
+ '-ss', (w0 / 1000).toFixed(3),
26
+ '-t', durationS.toFixed(3),
27
+ '-i', videoPath,
28
+ '-vn', '-map', '0:a:0',
29
+ '-af', `silencedetect=noise=${SILENCE_NOISE_DB}dB:d=${SILENCE_MIN_DURATION_S},astats=reset=0`,
30
+ '-f', 'null', '-',
31
+ ], { encoding: 'utf8', timeoutMs: 120_000, signal: options.signal });
32
+ stderr = result.stderr;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ const silences = [];
38
+ let pendingStart = null;
39
+ for (const line of stderr.split(/\r?\n/)) {
40
+ const sm = /silence_start:\s*(-?[\d.]+)/.exec(line);
41
+ if (sm) {
42
+ pendingStart = Math.max(0, Number(sm[1]));
43
+ continue;
44
+ }
45
+ const em = /silence_end:\s*([\d.]+)/.exec(line);
46
+ if (em && pendingStart !== null) {
47
+ silences.push([w0 + pendingStart * 1000, w0 + Number(em[1]) * 1000]);
48
+ pendingStart = null;
49
+ }
50
+ }
51
+ if (pendingStart !== null) {
52
+ silences.push([w0 + pendingStart * 1000, w1]); // 尾部静音延至窗口末
53
+ }
54
+ const rmsMatch = /RMS level dB:\s*(-?[\d.]+|-?inf)/.exec(stderr);
55
+ const peakMatch = /Peak level dB:\s*(-?[\d.]+|-?inf)/.exec(stderr);
56
+ const toDb = (raw) => {
57
+ if (raw === undefined)
58
+ return null;
59
+ if (raw === '-inf')
60
+ return -Infinity;
61
+ const v = Number(raw);
62
+ return Number.isFinite(v) ? v : null;
63
+ };
64
+ const rmsDb = toDb(rmsMatch?.[1]);
65
+ const peakDb = toDb(peakMatch?.[1]);
66
+ const islands = complement(silences, window).filter(([a, b]) => b - a >= MIN_ISLAND_MS);
67
+ const speechMs = islands.reduce((acc, [a, b]) => acc + (b - a), 0);
68
+ return {
69
+ silence: silences,
70
+ islands,
71
+ rmsDb,
72
+ peakDb,
73
+ activeRatio: w1 > w0 ? speechMs / (w1 - w0) : 0,
74
+ };
75
+ }
76
+ /** 整窗无语音判定(静音占比 ≥95% 或峰值 < -45dBFS)。 */
77
+ export function isSilentWindow(vad) {
78
+ if (vad === null)
79
+ return false;
80
+ if (vad.peakDb !== null && vad.peakDb < SILENT_PEAK_DB_MIN)
81
+ return true;
82
+ return vad.activeRatio < SILENT_ACTIVE_RATIO_MAX;
83
+ }
84
+ function complement(silences, window) {
85
+ const out = [];
86
+ let cursor = window[0];
87
+ for (const [a, b] of silences) {
88
+ if (a > cursor)
89
+ out.push([cursor, Math.min(a, window[1])]);
90
+ cursor = Math.max(cursor, b);
91
+ if (cursor >= window[1])
92
+ break;
93
+ }
94
+ if (cursor < window[1])
95
+ out.push([cursor, window[1]]);
96
+ return out;
97
+ }
98
+ export const DEFAULT_CHUNK_PLAN = { minMs: 45_000, maxMs: 75_000, hardMs: 60_000 };
99
+ /**
100
+ * 带时间戳 provider 的切分:谷底优先(取 [min, max] 内最深静音的中点),
101
+ * 否则 hardMs 硬切。切点落在静音中点,句子不被腰斩。
102
+ */
103
+ export function planChunks(window, vad, options = DEFAULT_CHUNK_PLAN) {
104
+ const [w0, w1] = window;
105
+ if (w1 - w0 <= options.maxMs)
106
+ return [[w0, w1]];
107
+ const chunks = [];
108
+ let start = w0;
109
+ while (start < w1) {
110
+ if (w1 - start <= options.maxMs) {
111
+ chunks.push([start, w1]);
112
+ break;
113
+ }
114
+ const lo = start + options.minMs;
115
+ const hi = start + options.maxMs;
116
+ let cut = start + options.hardMs;
117
+ if (vad) {
118
+ let deepest = -1;
119
+ for (const [a, b] of vad.silence) {
120
+ const s = Math.max(a, lo);
121
+ const e = Math.min(b, hi);
122
+ if (e > s && e - s > deepest) {
123
+ deepest = e - s;
124
+ cut = Math.round((s + e) / 2);
125
+ }
126
+ }
127
+ }
128
+ cut = Math.min(cut, hi);
129
+ chunks.push([start, cut]);
130
+ start = cut;
131
+ }
132
+ return chunks;
133
+ }
134
+ /** 无时间戳 provider(openai_basic)的切分:语音岛直切,>30s 的岛硬切。 */
135
+ export function planIslandChunks(window, vad, maxIslandMs = 30_000) {
136
+ const islands = vad ? vad.islands.filter(([a, b]) => b > window[0] && a < window[1]) : [];
137
+ if (islands.length === 0)
138
+ return window[1] > window[0] ? [[window[0], window[1]]] : [];
139
+ const chunks = [];
140
+ for (const [a, b] of islands) {
141
+ const s = Math.max(a, window[0]);
142
+ const e = Math.min(b, window[1]);
143
+ if (e <= s)
144
+ continue;
145
+ if (e - s <= maxIslandMs) {
146
+ chunks.push([s, e]);
147
+ continue;
148
+ }
149
+ for (let t = s; t < e; t += maxIslandMs) {
150
+ chunks.push([t, Math.min(t + maxIslandMs, e)]);
151
+ }
152
+ }
153
+ return chunks;
154
+ }
155
+ /**
156
+ * 切片音频编码(16kHz 单声道;mp3 = libmp3lame -q:a 6,opus = libopus 24k)。
157
+ * 返回 {bytes, mimeType};失败抛错由路由层按块失败处理。
158
+ */
159
+ export async function sliceAudio(videoPath, range, codec, options) {
160
+ const [start, end] = range;
161
+ const codecArgs = codec === 'mp3'
162
+ ? ['-c:a', 'libmp3lame', '-q:a', '6', '-f', 'mp3']
163
+ : ['-c:a', 'libopus', '-b:a', '24k', '-f', 'ogg'];
164
+ const { stdout } = await execFile(options.ffmpegPath, [
165
+ '-nostdin',
166
+ '-ss', (start / 1000).toFixed(3),
167
+ '-t', ((end - start) / 1000).toFixed(3),
168
+ '-i', videoPath,
169
+ '-vn', '-map', '0:a:0',
170
+ '-ac', '1', '-ar', '16000',
171
+ ...codecArgs,
172
+ '-',
173
+ ], { encoding: 'buffer', timeoutMs: 120_000, signal: options.signal });
174
+ return {
175
+ bytes: stdout,
176
+ mimeType: codec === 'mp3' ? 'audio/mpeg' : 'audio/ogg',
177
+ };
178
+ }
179
+ //# sourceMappingURL=acousticVad.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acousticVad.js","sourceRoot":"","sources":["../../src/audio/acousticVad.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Stage A2 —— ASR provider 适配。
3
+ *
4
+ * - openai_verbose:/v1/audio/transcriptions + verbose_json(段级时间戳);
5
+ * 服务端拒绝 verbose_json(sensevoice 等兼容端)时回退 openai_basic 语义;
6
+ * - openai_basic:json 整块文本,时间戳由路由层按切分区间锚定;
7
+ * - local_cli:本地命令行转写器,stdout 支持 JSON / SRT / 纯文本三种形态。
8
+ *
9
+ * 网络/进程失败一律抛 AsrRequestError;路由层按块失败计数,不外抛。
10
+ */
11
+ import type { AsrSettings } from './types.js';
12
+ export declare class AsrRequestError extends Error {
13
+ code: string;
14
+ constructor(message: string, code: string);
15
+ }
16
+ export interface AsrChunkInput {
17
+ /** 绝对 PTS 起点(毫秒)——路由层锚定用。 */
18
+ startMs: number;
19
+ endMs: number;
20
+ bytes: Buffer;
21
+ mimeType: string;
22
+ }
23
+ export interface RawSegment {
24
+ /** 相对块起点的毫秒。 */
25
+ startMs: number;
26
+ endMs: number;
27
+ text: string;
28
+ noSpeechProb?: number | undefined;
29
+ avgLogprob?: number | undefined;
30
+ compressionRatio?: number | undefined;
31
+ }
32
+ export interface AsrTranscribeDeps {
33
+ settings: AsrSettings;
34
+ signal?: AbortSignal | undefined;
35
+ }
36
+ /**
37
+ * 转写端点:接受 origin、origin/v1,以及用户直接粘贴的完整
38
+ * `/v1/audio/transcriptions`,避免出现重复路径段。
39
+ */
40
+ export declare function transcriptionUrl(baseUrl: string): string;
41
+ /** openai_verbose:段级时间戳(相对块起点)。 */
42
+ export declare function transcribeVerbose(input: AsrChunkInput, deps: AsrTranscribeDeps): Promise<RawSegment[]>;
43
+ /** openai_basic:整块文本(时间戳 = 块区间)。 */
44
+ export declare function transcribeBasic(input: AsrChunkInput, deps: AsrTranscribeDeps): Promise<RawSegment[]>;
45
+ /**
46
+ * local_cli:settings.model 为可执行文件;音频写临时文件后作为唯一参数。
47
+ * stdout 依次尝试 JSON → SRT → 纯文本。
48
+ */
49
+ export declare function transcribeLocalCli(input: AsrChunkInput, deps: AsrTranscribeDeps, tempDir?: string): Promise<RawSegment[]>;
50
+ /** provider 分派(锚定到绝对 PTS 由路由层完成)。 */
51
+ export declare function transcribeChunk(input: AsrChunkInput, deps: AsrTranscribeDeps, tempDir?: string): Promise<RawSegment[]>;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Stage A2 —— ASR provider 适配。
3
+ *
4
+ * - openai_verbose:/v1/audio/transcriptions + verbose_json(段级时间戳);
5
+ * 服务端拒绝 verbose_json(sensevoice 等兼容端)时回退 openai_basic 语义;
6
+ * - openai_basic:json 整块文本,时间戳由路由层按切分区间锚定;
7
+ * - local_cli:本地命令行转写器,stdout 支持 JSON / SRT / 纯文本三种形态。
8
+ *
9
+ * 网络/进程失败一律抛 AsrRequestError;路由层按块失败计数,不外抛。
10
+ */
11
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
12
+ import { tmpdir } from 'node:os';
13
+ import path from 'node:path';
14
+ import { spawn } from 'node:child_process';
15
+ import { readFile } from 'node:fs/promises';
16
+ import { parseSrt } from './subtitleExtractor.js';
17
+ export class AsrRequestError extends Error {
18
+ code;
19
+ constructor(message, code) {
20
+ super(message);
21
+ this.code = code;
22
+ }
23
+ }
24
+ function normalizeBaseUrl(baseUrl) {
25
+ return baseUrl.replace(/\/+$/, '');
26
+ }
27
+ /**
28
+ * 转写端点:接受 origin、origin/v1,以及用户直接粘贴的完整
29
+ * `/v1/audio/transcriptions`,避免出现重复路径段。
30
+ */
31
+ export function transcriptionUrl(baseUrl) {
32
+ const base = normalizeBaseUrl(baseUrl);
33
+ if (/\/audio\/transcriptions$/i.test(base))
34
+ return base;
35
+ return /\/v1$/i.test(base) ? `${base}/audio/transcriptions` : `${base}/v1/audio/transcriptions`;
36
+ }
37
+ async function fetchWithTimeout(url, init, timeoutMs, signal) {
38
+ const controller = new AbortController();
39
+ const timer = setTimeout(() => controller.abort(new Error(`ASR 请求超过 ${timeoutMs}ms`)), timeoutMs);
40
+ const onAbort = () => controller.abort(signal?.reason);
41
+ signal?.addEventListener('abort', onAbort, { once: true });
42
+ try {
43
+ return await fetch(url, { ...init, signal: controller.signal });
44
+ }
45
+ finally {
46
+ clearTimeout(timer);
47
+ signal?.removeEventListener('abort', onAbort);
48
+ }
49
+ }
50
+ function buildForm(input, settings, responseFormat) {
51
+ const form = new FormData();
52
+ const ext = input.mimeType === 'audio/ogg' ? 'ogg' : 'mp3';
53
+ form.append('file', new Blob([new Uint8Array(input.bytes)], { type: input.mimeType }), `chunk.${ext}`);
54
+ form.append('model', settings.model ?? 'whisper-1');
55
+ form.append('response_format', responseFormat);
56
+ if (settings.language)
57
+ form.append('language', settings.language);
58
+ return form;
59
+ }
60
+ /** openai_verbose:段级时间戳(相对块起点)。 */
61
+ export async function transcribeVerbose(input, deps) {
62
+ const { settings, signal } = deps;
63
+ if (!settings.baseUrl || !settings.apiKey) {
64
+ throw new AsrRequestError('openai_verbose 需要 baseUrl 与 apiKey', 'ASR_NOT_CONFIGURED');
65
+ }
66
+ let response;
67
+ try {
68
+ response = await fetchWithTimeout(transcriptionUrl(settings.baseUrl), {
69
+ method: 'POST',
70
+ headers: { Authorization: `Bearer ${settings.apiKey}` },
71
+ body: buildForm(input, settings, 'verbose_json'),
72
+ }, settings.timeoutMs, signal);
73
+ }
74
+ catch (error) {
75
+ throw new AsrRequestError(`ASR 网络请求失败:${error.message}`, 'ASR_NETWORK');
76
+ }
77
+ if (response.status === 400) {
78
+ // 兼容端不支持 verbose_json:退 json(调用方按整块文本处理)
79
+ throw new AsrRequestError('服务端拒绝 verbose_json', 'ASR_VERBOSE_REJECTED');
80
+ }
81
+ if (!response.ok) {
82
+ throw new AsrRequestError(`ASR HTTP ${response.status}`, `ASR_HTTP_${response.status}`);
83
+ }
84
+ const doc = (await response.json());
85
+ const segments = (doc.segments ?? [])
86
+ .filter((s) => typeof s.start === 'number' && typeof s.end === 'number' && (s.text ?? '').trim().length > 0)
87
+ .map((s) => ({
88
+ startMs: Math.round(s.start * 1000),
89
+ endMs: Math.round(s.end * 1000),
90
+ text: s.text.trim(),
91
+ noSpeechProb: s.no_speech_prob,
92
+ avgLogprob: s.avg_logprob,
93
+ compressionRatio: s.compression_ratio,
94
+ }));
95
+ if (segments.length === 0 && (doc.text ?? '').trim().length > 0) {
96
+ return [{ startMs: 0, endMs: input.endMs - input.startMs, text: doc.text.trim() }];
97
+ }
98
+ return segments;
99
+ }
100
+ /** openai_basic:整块文本(时间戳 = 块区间)。 */
101
+ export async function transcribeBasic(input, deps) {
102
+ const { settings, signal } = deps;
103
+ if (!settings.baseUrl || !settings.apiKey) {
104
+ throw new AsrRequestError('openai_basic 需要 baseUrl 与 apiKey', 'ASR_NOT_CONFIGURED');
105
+ }
106
+ let response;
107
+ try {
108
+ response = await fetchWithTimeout(transcriptionUrl(settings.baseUrl), {
109
+ method: 'POST',
110
+ headers: { Authorization: `Bearer ${settings.apiKey}` },
111
+ body: buildForm(input, settings, 'json'),
112
+ }, settings.timeoutMs, signal);
113
+ }
114
+ catch (error) {
115
+ throw new AsrRequestError(`ASR 网络请求失败:${error.message}`, 'ASR_NETWORK');
116
+ }
117
+ if (!response.ok) {
118
+ throw new AsrRequestError(`ASR HTTP ${response.status}`, `ASR_HTTP_${response.status}`);
119
+ }
120
+ const doc = (await response.json());
121
+ const text = (doc.text ?? '').trim();
122
+ if (!text)
123
+ return [];
124
+ return [{ startMs: 0, endMs: input.endMs - input.startMs, text }];
125
+ }
126
+ /**
127
+ * local_cli:settings.model 为可执行文件;音频写临时文件后作为唯一参数。
128
+ * stdout 依次尝试 JSON → SRT → 纯文本。
129
+ */
130
+ export async function transcribeLocalCli(input, deps, tempDir) {
131
+ const { settings, signal } = deps;
132
+ const exe = settings.model;
133
+ if (!exe)
134
+ throw new AsrRequestError('local_cli 需要 asr.model 指定可执行文件', 'ASR_NOT_CONFIGURED');
135
+ const dir = await mkdtemp(path.join(tempDir ?? tmpdir(), 'pfstream-asr-'));
136
+ const audioFile = path.join(dir, 'chunk.mp3');
137
+ try {
138
+ await writeFile(audioFile, input.bytes);
139
+ // argv 模板:{audio}=音频路径 {out}=输出文件路径 {dir}=音频所在临时目录
140
+ // {outbase}=输出基名(whisper.cpp 的 -of 用,产出 <base>.srt);
141
+ // 无 {audio} 令牌时音频路径追加在末尾
142
+ const rawArgs = settings.args ?? [];
143
+ const usesAudioToken = rawArgs.some((arg) => arg.includes('{audio}'));
144
+ const outFile = rawArgs.some((arg) => arg.includes('{out}')) ? path.join(dir, 'cli-out.txt') : null;
145
+ const outBase = path.join(dir, 'chunk');
146
+ const usesOutBase = rawArgs.some((arg) => arg.includes('{outbase}'));
147
+ const usesDir = rawArgs.some((arg) => arg.includes('{dir}'));
148
+ const argv = rawArgs.map((arg) => arg
149
+ .split('{audio}').join(audioFile)
150
+ .split('{outbase}').join(outBase)
151
+ .split('{out}').join(outFile ?? path.join(dir, 'cli-out.txt'))
152
+ .split('{dir}').join(dir));
153
+ if (!usesAudioToken)
154
+ argv.push(audioFile);
155
+ const stdout = await new Promise((resolve, reject) => {
156
+ // PYTHONUTF8:Python 系 CLI 在 GBK 控制台输出中文会抛 UnicodeEncodeError
157
+ const child = spawn(exe, argv, { stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, env: { ...process.env, PYTHONUTF8: '1' } });
158
+ let out = '';
159
+ const timer = setTimeout(() => {
160
+ child.kill();
161
+ reject(new AsrRequestError(`local_cli 超过 ${settings.timeoutMs}ms`, 'ASR_TIMEOUT'));
162
+ }, settings.timeoutMs);
163
+ const onAbort = () => {
164
+ child.kill();
165
+ reject(new AsrRequestError('local_cli 被取消', 'ASR_ABORT'));
166
+ };
167
+ signal?.addEventListener('abort', onAbort, { once: true });
168
+ child.stdout.on('data', (c) => {
169
+ out += c.toString('utf8');
170
+ });
171
+ child.on('error', (e) => {
172
+ clearTimeout(timer);
173
+ reject(new AsrRequestError(`无法启动 ${exe}:${e.message}`, 'ASR_SPAWN'));
174
+ });
175
+ child.on('close', (code) => {
176
+ clearTimeout(timer);
177
+ signal?.removeEventListener('abort', onAbort);
178
+ if (code === 0)
179
+ resolve(out);
180
+ else
181
+ reject(new AsrRequestError(`local_cli 退出码 ${code}`, 'ASR_NONZERO'));
182
+ });
183
+ });
184
+ // 契约:args 含 {out}/{outbase}/{dir} 令牌时结果按文件产物优先(CLI 的 stdout 可能只有进度杂讯);
185
+ // 无令牌的零参 CLI 以 stdout 为主,音频同目录产物兜底
186
+ const expectsArtifacts = outFile !== null || usesOutBase || usesDir;
187
+ let content = '';
188
+ const readArtifact = async () => {
189
+ const candidates = [outFile, path.join(dir, 'chunk.srt'), path.join(dir, 'chunk.vtt'), path.join(dir, 'chunk.txt'), path.join(dir, 'chunk.json')];
190
+ for (const c of candidates) {
191
+ if (c === null)
192
+ continue;
193
+ const t = await readFile(c, 'utf8').catch(() => '');
194
+ if (t.trim().length > 0)
195
+ return t;
196
+ }
197
+ return '';
198
+ };
199
+ if (expectsArtifacts) {
200
+ content = (await readArtifact()) || stdout;
201
+ }
202
+ else {
203
+ content = stdout.trim().length > 0 ? stdout : await readArtifact();
204
+ }
205
+ return parseCliOutput(content, input.endMs - input.startMs);
206
+ }
207
+ finally {
208
+ await rm(dir, { recursive: true, force: true }).catch(() => undefined);
209
+ }
210
+ }
211
+ function parseCliOutput(stdout, chunkMs) {
212
+ const trimmed = stdout.trim();
213
+ if (!trimmed)
214
+ return [];
215
+ try {
216
+ const doc = JSON.parse(trimmed);
217
+ if (Array.isArray(doc.segments) && doc.segments.length > 0) {
218
+ return doc.segments
219
+ .filter((s) => (s.text ?? '').trim().length > 0)
220
+ .map((s) => ({
221
+ startMs: Math.round(Number(s.start ?? 0) * 1000),
222
+ endMs: Math.round(Number(s.end ?? chunkMs / 1000) * 1000),
223
+ text: s.text.trim(),
224
+ }));
225
+ }
226
+ if ((doc.text ?? '').trim().length > 0) {
227
+ return [{ startMs: 0, endMs: chunkMs, text: doc.text.trim() }];
228
+ }
229
+ }
230
+ catch {
231
+ // 非 JSON,继续按 SRT / 纯文本解析
232
+ }
233
+ if (trimmed.includes('-->')) {
234
+ return parseSrt(trimmed, 'asr').map((s) => ({ startMs: s.startMs, endMs: s.endMs, text: s.text }));
235
+ }
236
+ return [{ startMs: 0, endMs: chunkMs, text: trimmed }];
237
+ }
238
+ /** provider 分派(锚定到绝对 PTS 由路由层完成)。 */
239
+ export async function transcribeChunk(input, deps, tempDir) {
240
+ switch (deps.settings.provider) {
241
+ case 'openai_verbose':
242
+ return transcribeVerbose(input, deps);
243
+ case 'openai_basic':
244
+ return transcribeBasic(input, deps);
245
+ case 'local_cli':
246
+ return transcribeLocalCli(input, deps, tempDir);
247
+ default:
248
+ throw new AsrRequestError(`未知 provider:${deps.settings.provider}`, 'ASR_PROVIDER');
249
+ }
250
+ }
251
+ //# sourceMappingURL=asrProviders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asrProviders.js","sourceRoot":"","sources":["../../src/audio/asrProviders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ASR 路由:缓存缺口 → 切分 → 切片编码 → provider(verbose 优先,
3
+ * 400 拒绝时降级 basic)→ PTS 锚定 → 幻觉清洗 → 缓存回写。
4
+ *
5
+ * 失败语义:单块失败计入 failedChunks(partial),不中断其余块;
6
+ * verbose 被兼容端拒绝时整窗自动降级 basic(VAD 岛切分 + 整块文本锚定)。
7
+ */
8
+ import type { AsrSettings, TranscriptSegment, VadResult } from './types.js';
9
+ export interface AsrRunOptions {
10
+ ffmpegPath: string;
11
+ settings: AsrSettings;
12
+ cacheDir: string;
13
+ cacheEnabled: boolean;
14
+ sliceCodec: 'mp3' | 'opus';
15
+ tempDir?: string | undefined;
16
+ signal?: AbortSignal | undefined;
17
+ }
18
+ export interface AsrRunResult {
19
+ /** 窗口内全部可用段(缓存命中 + 本次新转写,时间序)。 */
20
+ segments: TranscriptSegment[];
21
+ failedChunks: number;
22
+ requests: number;
23
+ droppedByGuard: number;
24
+ cacheHit: boolean;
25
+ /** verbose → basic 自动降级发生。 */
26
+ degradedToBasic: boolean;
27
+ }
28
+ export declare function transcribeWindow(videoPath: string, window: [number, number], vad: VadResult | null, options: AsrRunOptions): Promise<AsrRunResult>;
29
+ /** basic/无时间戳 provider 的岛切分包装。 */
30
+ export declare function transcribeWindowByIslands(videoPath: string, window: [number, number], vad: VadResult | null, options: AsrRunOptions): Promise<AsrRunResult>;