@derogab/stt-proxy 0.2.0 → 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.
- package/README.md +25 -4
- package/dist/cjs/index.js +195 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/index.js +192 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +7 -1
- package/.github/workflows/release.yml +0 -131
- package/.github/workflows/tests.yml +0 -42
- package/CLAUDE.md +0 -47
- package/src/index.ts +0 -174
- package/test/index.test.ts +0 -128
- package/test/whisper-cpp.integration.test.ts +0 -119
- package/tsconfig.cjs.json +0 -14
- package/tsconfig.esm.json +0 -14
- package/tsconfig.json +0 -20
- package/tsconfig.types.json +0 -15
- package/vitest.config.ts +0 -13
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# stt-proxy
|
|
2
|
-
A simple and lightweight proxy for seamless integration with multiple STT providers including Whisper.cpp.
|
|
2
|
+
A simple and lightweight proxy for seamless integration with multiple STT providers including Whisper.cpp and Cloudflare AI.
|
|
3
3
|
|
|
4
4
|
## Features
|
|
5
5
|
|
|
@@ -28,6 +28,13 @@ console.log(result.text);
|
|
|
28
28
|
The package automatically detects which STT provider to use based on your environment variables.
|
|
29
29
|
Configure one or more providers:
|
|
30
30
|
|
|
31
|
+
### Provider Selection
|
|
32
|
+
```bash
|
|
33
|
+
PROVIDER=cloudflare # Optional, force a specific provider (whisper.cpp, cloudflare)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
When `PROVIDER` is set, the specified provider will be used and an error is thrown if its credentials are not configured. When not set, providers are selected automatically based on priority.
|
|
37
|
+
|
|
31
38
|
### Whisper.cpp (Local)
|
|
32
39
|
```bash
|
|
33
40
|
WHISPER_CPP_MODEL_PATH=/path/to/ggml-base.bin # Required, path to your GGML model file
|
|
@@ -38,6 +45,14 @@ Download models from [HuggingFace](https://huggingface.co/ggerganov/whisper.cpp/
|
|
|
38
45
|
curl -L -o ggml-base.bin https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin
|
|
39
46
|
```
|
|
40
47
|
|
|
48
|
+
### Cloudflare AI
|
|
49
|
+
```bash
|
|
50
|
+
CLOUDFLARE_ACCOUNT_ID=your-account-id # Required
|
|
51
|
+
CLOUDFLARE_AUTH_KEY=your-api-token # Required
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Uses the `@cf/openai/whisper-large-v3-turbo` model.
|
|
55
|
+
|
|
41
56
|
## API Reference
|
|
42
57
|
|
|
43
58
|
### `transcribe(audio: string | Buffer, options?): Promise<TranscribeOutput>`
|
|
@@ -87,14 +102,17 @@ console.log(result3.text);
|
|
|
87
102
|
|
|
88
103
|
## Provider Priority
|
|
89
104
|
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
When `PROVIDER` environment variable is set, that provider is used directly.
|
|
106
|
+
|
|
107
|
+
Otherwise, the package selects providers in the following order:
|
|
108
|
+
1. **Whisper.cpp** (if `WHISPER_CPP_MODEL_PATH` is set and file exists)
|
|
109
|
+
2. **Cloudflare AI** (if `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_AUTH_KEY` are set)
|
|
92
110
|
|
|
93
111
|
If no providers are configured, the function throws an error.
|
|
94
112
|
|
|
95
113
|
## Requirements
|
|
96
114
|
|
|
97
|
-
- **FFmpeg**: Required for audio conversion.
|
|
115
|
+
- **FFmpeg**: Required for audio conversion (Whisper.cpp only).
|
|
98
116
|
```bash
|
|
99
117
|
# macOS
|
|
100
118
|
brew install ffmpeg
|
|
@@ -114,6 +132,9 @@ npm install
|
|
|
114
132
|
|
|
115
133
|
# Build the package
|
|
116
134
|
npm run build
|
|
135
|
+
|
|
136
|
+
# Run tests
|
|
137
|
+
npm test
|
|
117
138
|
```
|
|
118
139
|
|
|
119
140
|
## Credits
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transcribe = transcribe;
|
|
4
|
+
require("dotenv/config");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Shared utilities
|
|
11
|
+
// ============================================================================
|
|
12
|
+
function cleanTranscription(text) {
|
|
13
|
+
return text.replace(/[\x00-\x1F\x7F]/g, '').trim();
|
|
14
|
+
}
|
|
15
|
+
function generateTempPath(prefix, extension) {
|
|
16
|
+
const randomId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
17
|
+
return path.join(os.tmpdir(), `${prefix}_${randomId}.${extension}`);
|
|
18
|
+
}
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Whisper.cpp provider
|
|
21
|
+
// ============================================================================
|
|
22
|
+
let whisperInstance = null;
|
|
23
|
+
let currentModelPath = null;
|
|
24
|
+
function isWhisperConfigured() {
|
|
25
|
+
const modelPath = process.env['WHISPER_CPP_MODEL_PATH'];
|
|
26
|
+
return !!modelPath && fs.existsSync(modelPath);
|
|
27
|
+
}
|
|
28
|
+
async function getWhisperInstance() {
|
|
29
|
+
const modelPath = process.env['WHISPER_CPP_MODEL_PATH'];
|
|
30
|
+
if (!modelPath) {
|
|
31
|
+
throw new Error('WHISPER_CPP_MODEL_PATH environment variable is not set');
|
|
32
|
+
}
|
|
33
|
+
if (!fs.existsSync(modelPath)) {
|
|
34
|
+
throw new Error(`Whisper model not found at path: ${modelPath}`);
|
|
35
|
+
}
|
|
36
|
+
if (whisperInstance && currentModelPath === modelPath) {
|
|
37
|
+
return whisperInstance;
|
|
38
|
+
}
|
|
39
|
+
if (whisperInstance) {
|
|
40
|
+
await whisperInstance.free();
|
|
41
|
+
whisperInstance = null;
|
|
42
|
+
}
|
|
43
|
+
const { Whisper } = await Promise.resolve().then(() => require('smart-whisper'));
|
|
44
|
+
whisperInstance = new Whisper(modelPath, { gpu: true });
|
|
45
|
+
currentModelPath = modelPath;
|
|
46
|
+
return whisperInstance;
|
|
47
|
+
}
|
|
48
|
+
function audioToPcm(audioPath) {
|
|
49
|
+
const tempPcmPath = generateTempPath('whisper', 'pcm');
|
|
50
|
+
try {
|
|
51
|
+
(0, child_process_1.execSync)(`ffmpeg -y -i "${audioPath}" -ar 16000 -ac 1 -f f32le "${tempPcmPath}"`, { stdio: 'pipe' });
|
|
52
|
+
const pcmBuffer = fs.readFileSync(tempPcmPath);
|
|
53
|
+
return new Float32Array(pcmBuffer.buffer, pcmBuffer.byteOffset, pcmBuffer.length / 4);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
if (fs.existsSync(tempPcmPath)) {
|
|
57
|
+
fs.unlinkSync(tempPcmPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function resultsToText(results) {
|
|
62
|
+
return results.map((r) => r.text).join(' ');
|
|
63
|
+
}
|
|
64
|
+
async function transcribeWithWhisper(audioPath, options = {}) {
|
|
65
|
+
if (!fs.existsSync(audioPath)) {
|
|
66
|
+
throw new Error(`Audio file not found: ${audioPath}`);
|
|
67
|
+
}
|
|
68
|
+
const whisper = await getWhisperInstance();
|
|
69
|
+
const pcmData = audioToPcm(audioPath);
|
|
70
|
+
const transcribeParams = {
|
|
71
|
+
format: 'simple',
|
|
72
|
+
...(options.language !== undefined && { language: options.language }),
|
|
73
|
+
...(options.translate !== undefined && { translate: options.translate }),
|
|
74
|
+
};
|
|
75
|
+
const task = await whisper.transcribe(pcmData, transcribeParams);
|
|
76
|
+
const results = await task.result;
|
|
77
|
+
return { text: cleanTranscription(resultsToText(results)) };
|
|
78
|
+
}
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Cloudflare provider
|
|
81
|
+
// ============================================================================
|
|
82
|
+
function isCloudflareConfigured() {
|
|
83
|
+
return !!(process.env['CLOUDFLARE_ACCOUNT_ID'] && process.env['CLOUDFLARE_AUTH_KEY']);
|
|
84
|
+
}
|
|
85
|
+
async function transcribeWithCloudflare(audioPath, options = {}) {
|
|
86
|
+
if (!fs.existsSync(audioPath)) {
|
|
87
|
+
throw new Error(`Audio file not found: ${audioPath}`);
|
|
88
|
+
}
|
|
89
|
+
const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];
|
|
90
|
+
const authKey = process.env['CLOUDFLARE_AUTH_KEY'];
|
|
91
|
+
if (!accountId || !authKey) {
|
|
92
|
+
throw new Error('Cloudflare credentials not configured. Set CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_AUTH_KEY environment variables.');
|
|
93
|
+
}
|
|
94
|
+
const audioBuffer = fs.readFileSync(audioPath);
|
|
95
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/@cf/openai/whisper-large-v3-turbo`;
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
'Authorization': `Bearer ${authKey}`,
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
audio: audioBuffer.toString('base64'),
|
|
104
|
+
task: options.translate ? 'translate' : 'transcribe',
|
|
105
|
+
vad_filter: true,
|
|
106
|
+
...(options.language && { language: options.language }),
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const errorBody = await response.text();
|
|
111
|
+
throw new Error(`Cloudflare API error: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
112
|
+
}
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
if (!data.success || !data.result?.text) {
|
|
115
|
+
const errorMessage = data.errors?.[0]?.message || 'Unknown Cloudflare API error';
|
|
116
|
+
throw new Error(`Cloudflare transcription failed: ${errorMessage}`);
|
|
117
|
+
}
|
|
118
|
+
return { text: cleanTranscription(data.result.text) };
|
|
119
|
+
}
|
|
120
|
+
function selectProvider() {
|
|
121
|
+
// Check for explicit provider selection
|
|
122
|
+
const explicitProvider = process.env['PROVIDER']?.toLowerCase();
|
|
123
|
+
if (explicitProvider) {
|
|
124
|
+
// Validate explicit provider configuration
|
|
125
|
+
switch (explicitProvider) {
|
|
126
|
+
case 'whisper.cpp':
|
|
127
|
+
if (!isWhisperConfigured()) {
|
|
128
|
+
throw new Error("PROVIDER is set to 'whisper.cpp' but WHISPER_CPP_MODEL_PATH is not configured or model file does not exist.");
|
|
129
|
+
}
|
|
130
|
+
return 'whisper.cpp';
|
|
131
|
+
case 'cloudflare':
|
|
132
|
+
if (!isCloudflareConfigured()) {
|
|
133
|
+
throw new Error("PROVIDER is set to 'cloudflare' but CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_AUTH_KEY are not configured.");
|
|
134
|
+
}
|
|
135
|
+
return 'cloudflare';
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`Unknown provider: ${explicitProvider}. Valid providers are: whisper.cpp, cloudflare`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Auto-detection priority: whisper.cpp > cloudflare
|
|
141
|
+
if (isWhisperConfigured())
|
|
142
|
+
return 'whisper.cpp';
|
|
143
|
+
if (isCloudflareConfigured())
|
|
144
|
+
return 'cloudflare';
|
|
145
|
+
throw new Error('No STT provider configured. Set WHISPER_CPP_MODEL_PATH or CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_AUTH_KEY environment variables.');
|
|
146
|
+
}
|
|
147
|
+
async function transcribeFromPath(audioPath, options, provider) {
|
|
148
|
+
if (provider === 'cloudflare') {
|
|
149
|
+
return transcribeWithCloudflare(audioPath, options);
|
|
150
|
+
}
|
|
151
|
+
return transcribeWithWhisper(audioPath, options);
|
|
152
|
+
}
|
|
153
|
+
async function transcribeFromBuffer(audioBuffer, options, provider) {
|
|
154
|
+
const tempPath = generateTempPath('stt_input', 'audio');
|
|
155
|
+
fs.writeFileSync(tempPath, audioBuffer);
|
|
156
|
+
try {
|
|
157
|
+
return await transcribeFromPath(tempPath, options, provider);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
if (fs.existsSync(tempPath)) {
|
|
161
|
+
fs.unlinkSync(tempPath);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function transcribe(audio, options = {}) {
|
|
166
|
+
const provider = selectProvider();
|
|
167
|
+
return Buffer.isBuffer(audio)
|
|
168
|
+
? transcribeFromBuffer(audio, options, provider)
|
|
169
|
+
: transcribeFromPath(audio, options, provider);
|
|
170
|
+
}
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Cleanup handlers
|
|
173
|
+
// ============================================================================
|
|
174
|
+
async function freeWhisper() {
|
|
175
|
+
if (whisperInstance) {
|
|
176
|
+
await whisperInstance.free();
|
|
177
|
+
whisperInstance = null;
|
|
178
|
+
currentModelPath = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
process.on('exit', () => {
|
|
182
|
+
if (whisperInstance) {
|
|
183
|
+
// Note: Cannot use async operations in 'exit' handler
|
|
184
|
+
// The instance will be cleaned up by the process termination
|
|
185
|
+
whisperInstance = null;
|
|
186
|
+
currentModelPath = null;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
const shutdownHandler = async () => {
|
|
190
|
+
await freeWhisper();
|
|
191
|
+
process.exit(0);
|
|
192
|
+
};
|
|
193
|
+
process.on('SIGINT', shutdownHandler);
|
|
194
|
+
process.on('SIGTERM', shutdownHandler);
|
|
195
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Shared utilities
|
|
8
|
+
// ============================================================================
|
|
9
|
+
function cleanTranscription(text) {
|
|
10
|
+
return text.replace(/[\x00-\x1F\x7F]/g, '').trim();
|
|
11
|
+
}
|
|
12
|
+
function generateTempPath(prefix, extension) {
|
|
13
|
+
const randomId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
14
|
+
return path.join(os.tmpdir(), `${prefix}_${randomId}.${extension}`);
|
|
15
|
+
}
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Whisper.cpp provider
|
|
18
|
+
// ============================================================================
|
|
19
|
+
let whisperInstance = null;
|
|
20
|
+
let currentModelPath = null;
|
|
21
|
+
function isWhisperConfigured() {
|
|
22
|
+
const modelPath = process.env['WHISPER_CPP_MODEL_PATH'];
|
|
23
|
+
return !!modelPath && fs.existsSync(modelPath);
|
|
24
|
+
}
|
|
25
|
+
async function getWhisperInstance() {
|
|
26
|
+
const modelPath = process.env['WHISPER_CPP_MODEL_PATH'];
|
|
27
|
+
if (!modelPath) {
|
|
28
|
+
throw new Error('WHISPER_CPP_MODEL_PATH environment variable is not set');
|
|
29
|
+
}
|
|
30
|
+
if (!fs.existsSync(modelPath)) {
|
|
31
|
+
throw new Error(`Whisper model not found at path: ${modelPath}`);
|
|
32
|
+
}
|
|
33
|
+
if (whisperInstance && currentModelPath === modelPath) {
|
|
34
|
+
return whisperInstance;
|
|
35
|
+
}
|
|
36
|
+
if (whisperInstance) {
|
|
37
|
+
await whisperInstance.free();
|
|
38
|
+
whisperInstance = null;
|
|
39
|
+
}
|
|
40
|
+
const { Whisper } = await import('smart-whisper');
|
|
41
|
+
whisperInstance = new Whisper(modelPath, { gpu: true });
|
|
42
|
+
currentModelPath = modelPath;
|
|
43
|
+
return whisperInstance;
|
|
44
|
+
}
|
|
45
|
+
function audioToPcm(audioPath) {
|
|
46
|
+
const tempPcmPath = generateTempPath('whisper', 'pcm');
|
|
47
|
+
try {
|
|
48
|
+
execSync(`ffmpeg -y -i "${audioPath}" -ar 16000 -ac 1 -f f32le "${tempPcmPath}"`, { stdio: 'pipe' });
|
|
49
|
+
const pcmBuffer = fs.readFileSync(tempPcmPath);
|
|
50
|
+
return new Float32Array(pcmBuffer.buffer, pcmBuffer.byteOffset, pcmBuffer.length / 4);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (fs.existsSync(tempPcmPath)) {
|
|
54
|
+
fs.unlinkSync(tempPcmPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function resultsToText(results) {
|
|
59
|
+
return results.map((r) => r.text).join(' ');
|
|
60
|
+
}
|
|
61
|
+
async function transcribeWithWhisper(audioPath, options = {}) {
|
|
62
|
+
if (!fs.existsSync(audioPath)) {
|
|
63
|
+
throw new Error(`Audio file not found: ${audioPath}`);
|
|
64
|
+
}
|
|
65
|
+
const whisper = await getWhisperInstance();
|
|
66
|
+
const pcmData = audioToPcm(audioPath);
|
|
67
|
+
const transcribeParams = {
|
|
68
|
+
format: 'simple',
|
|
69
|
+
...(options.language !== undefined && { language: options.language }),
|
|
70
|
+
...(options.translate !== undefined && { translate: options.translate }),
|
|
71
|
+
};
|
|
72
|
+
const task = await whisper.transcribe(pcmData, transcribeParams);
|
|
73
|
+
const results = await task.result;
|
|
74
|
+
return { text: cleanTranscription(resultsToText(results)) };
|
|
75
|
+
}
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Cloudflare provider
|
|
78
|
+
// ============================================================================
|
|
79
|
+
function isCloudflareConfigured() {
|
|
80
|
+
return !!(process.env['CLOUDFLARE_ACCOUNT_ID'] && process.env['CLOUDFLARE_AUTH_KEY']);
|
|
81
|
+
}
|
|
82
|
+
async function transcribeWithCloudflare(audioPath, options = {}) {
|
|
83
|
+
if (!fs.existsSync(audioPath)) {
|
|
84
|
+
throw new Error(`Audio file not found: ${audioPath}`);
|
|
85
|
+
}
|
|
86
|
+
const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];
|
|
87
|
+
const authKey = process.env['CLOUDFLARE_AUTH_KEY'];
|
|
88
|
+
if (!accountId || !authKey) {
|
|
89
|
+
throw new Error('Cloudflare credentials not configured. Set CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_AUTH_KEY environment variables.');
|
|
90
|
+
}
|
|
91
|
+
const audioBuffer = fs.readFileSync(audioPath);
|
|
92
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/@cf/openai/whisper-large-v3-turbo`;
|
|
93
|
+
const response = await fetch(url, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Authorization': `Bearer ${authKey}`,
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
audio: audioBuffer.toString('base64'),
|
|
101
|
+
task: options.translate ? 'translate' : 'transcribe',
|
|
102
|
+
vad_filter: true,
|
|
103
|
+
...(options.language && { language: options.language }),
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const errorBody = await response.text();
|
|
108
|
+
throw new Error(`Cloudflare API error: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
109
|
+
}
|
|
110
|
+
const data = await response.json();
|
|
111
|
+
if (!data.success || !data.result?.text) {
|
|
112
|
+
const errorMessage = data.errors?.[0]?.message || 'Unknown Cloudflare API error';
|
|
113
|
+
throw new Error(`Cloudflare transcription failed: ${errorMessage}`);
|
|
114
|
+
}
|
|
115
|
+
return { text: cleanTranscription(data.result.text) };
|
|
116
|
+
}
|
|
117
|
+
function selectProvider() {
|
|
118
|
+
// Check for explicit provider selection
|
|
119
|
+
const explicitProvider = process.env['PROVIDER']?.toLowerCase();
|
|
120
|
+
if (explicitProvider) {
|
|
121
|
+
// Validate explicit provider configuration
|
|
122
|
+
switch (explicitProvider) {
|
|
123
|
+
case 'whisper.cpp':
|
|
124
|
+
if (!isWhisperConfigured()) {
|
|
125
|
+
throw new Error("PROVIDER is set to 'whisper.cpp' but WHISPER_CPP_MODEL_PATH is not configured or model file does not exist.");
|
|
126
|
+
}
|
|
127
|
+
return 'whisper.cpp';
|
|
128
|
+
case 'cloudflare':
|
|
129
|
+
if (!isCloudflareConfigured()) {
|
|
130
|
+
throw new Error("PROVIDER is set to 'cloudflare' but CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_AUTH_KEY are not configured.");
|
|
131
|
+
}
|
|
132
|
+
return 'cloudflare';
|
|
133
|
+
default:
|
|
134
|
+
throw new Error(`Unknown provider: ${explicitProvider}. Valid providers are: whisper.cpp, cloudflare`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Auto-detection priority: whisper.cpp > cloudflare
|
|
138
|
+
if (isWhisperConfigured())
|
|
139
|
+
return 'whisper.cpp';
|
|
140
|
+
if (isCloudflareConfigured())
|
|
141
|
+
return 'cloudflare';
|
|
142
|
+
throw new Error('No STT provider configured. Set WHISPER_CPP_MODEL_PATH or CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_AUTH_KEY environment variables.');
|
|
143
|
+
}
|
|
144
|
+
async function transcribeFromPath(audioPath, options, provider) {
|
|
145
|
+
if (provider === 'cloudflare') {
|
|
146
|
+
return transcribeWithCloudflare(audioPath, options);
|
|
147
|
+
}
|
|
148
|
+
return transcribeWithWhisper(audioPath, options);
|
|
149
|
+
}
|
|
150
|
+
async function transcribeFromBuffer(audioBuffer, options, provider) {
|
|
151
|
+
const tempPath = generateTempPath('stt_input', 'audio');
|
|
152
|
+
fs.writeFileSync(tempPath, audioBuffer);
|
|
153
|
+
try {
|
|
154
|
+
return await transcribeFromPath(tempPath, options, provider);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
if (fs.existsSync(tempPath)) {
|
|
158
|
+
fs.unlinkSync(tempPath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export async function transcribe(audio, options = {}) {
|
|
163
|
+
const provider = selectProvider();
|
|
164
|
+
return Buffer.isBuffer(audio)
|
|
165
|
+
? transcribeFromBuffer(audio, options, provider)
|
|
166
|
+
: transcribeFromPath(audio, options, provider);
|
|
167
|
+
}
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Cleanup handlers
|
|
170
|
+
// ============================================================================
|
|
171
|
+
async function freeWhisper() {
|
|
172
|
+
if (whisperInstance) {
|
|
173
|
+
await whisperInstance.free();
|
|
174
|
+
whisperInstance = null;
|
|
175
|
+
currentModelPath = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
process.on('exit', () => {
|
|
179
|
+
if (whisperInstance) {
|
|
180
|
+
// Note: Cannot use async operations in 'exit' handler
|
|
181
|
+
// The instance will be cleaned up by the process termination
|
|
182
|
+
whisperInstance = null;
|
|
183
|
+
currentModelPath = null;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
const shutdownHandler = async () => {
|
|
187
|
+
await freeWhisper();
|
|
188
|
+
process.exit(0);
|
|
189
|
+
};
|
|
190
|
+
process.on('SIGINT', shutdownHandler);
|
|
191
|
+
process.on('SIGTERM', shutdownHandler);
|
|
192
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
export interface TranscribeOptions {
|
|
3
|
+
language?: string;
|
|
4
|
+
translate?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface TranscribeOutput {
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function transcribe(audio: string | Buffer, options?: TranscribeOptions): Promise<TranscribeOutput>;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAWvB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAiND,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAMnH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@derogab/stt-proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A simple and lightweight proxy for seamless integration with multiple STT (Speech-to-Text) providers including Whisper.cpp",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
21
24
|
"scripts": {
|
|
22
25
|
"build": "npm run build:cjs && npm run build:esm && npm run build:types",
|
|
23
26
|
"build:cjs": "tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
|
|
@@ -26,6 +29,7 @@
|
|
|
26
29
|
"test": "vitest run",
|
|
27
30
|
"test:unit": "vitest run --exclude='**/*.integration.test.*'",
|
|
28
31
|
"test:whisper": "vitest run --testTimeout=300000 --hookTimeout=600000 test/whisper-cpp.integration.test.ts",
|
|
32
|
+
"test:cloudflare": "vitest run --testTimeout=60000 --hookTimeout=60000 test/cloudflare.integration.test.ts",
|
|
29
33
|
"test:watch": "vitest watch",
|
|
30
34
|
"test:coverage": "vitest run --coverage",
|
|
31
35
|
"typecheck": "tsc --noEmit -p tsconfig.esm.json"
|
|
@@ -40,6 +44,8 @@
|
|
|
40
44
|
"transcription",
|
|
41
45
|
"whisper",
|
|
42
46
|
"whisper.cpp",
|
|
47
|
+
"cloudflare",
|
|
48
|
+
"cloudflare-ai",
|
|
43
49
|
"proxy",
|
|
44
50
|
"gateway"
|
|
45
51
|
],
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
name: Release and publish package to NPM
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
# Publish `v1.2.3` tags as releases.
|
|
6
|
-
tags:
|
|
7
|
-
- v*
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
# Release the TAG to GitHub.
|
|
11
|
-
release:
|
|
12
|
-
name: Release pushed tag
|
|
13
|
-
if: startsWith(github.ref, 'refs/tags/')
|
|
14
|
-
permissions:
|
|
15
|
-
contents: write
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
steps:
|
|
18
|
-
- name: Create release
|
|
19
|
-
env:
|
|
20
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
21
|
-
tag: ${{ github.ref_name }}
|
|
22
|
-
run: |
|
|
23
|
-
gh release create "$tag" \
|
|
24
|
-
--repo="$GITHUB_REPOSITORY" \
|
|
25
|
-
--title="v${tag#v}" \
|
|
26
|
-
--generate-notes
|
|
27
|
-
# Publish the package.
|
|
28
|
-
publish-npm:
|
|
29
|
-
name: Publish Package on NPM
|
|
30
|
-
needs: release
|
|
31
|
-
runs-on: ubuntu-latest
|
|
32
|
-
permissions:
|
|
33
|
-
contents: read
|
|
34
|
-
id-token: write
|
|
35
|
-
steps:
|
|
36
|
-
- name: Checkout
|
|
37
|
-
uses: actions/checkout@v6
|
|
38
|
-
- name: Setup Node
|
|
39
|
-
uses: actions/setup-node@v6
|
|
40
|
-
with:
|
|
41
|
-
node-version: '20.x'
|
|
42
|
-
cache: 'npm'
|
|
43
|
-
registry-url: 'https://registry.npmjs.org'
|
|
44
|
-
- name: Install FFmpeg
|
|
45
|
-
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
|
46
|
-
- name: Cache Whisper model
|
|
47
|
-
uses: actions/cache@v4
|
|
48
|
-
with:
|
|
49
|
-
path: test/models
|
|
50
|
-
key: whisper-model-tiny-v1
|
|
51
|
-
- name: Cache test audio
|
|
52
|
-
uses: actions/cache@v4
|
|
53
|
-
with:
|
|
54
|
-
path: test/audio
|
|
55
|
-
key: test-audio-jfk-v1
|
|
56
|
-
- name: Install dependencies (clean)
|
|
57
|
-
run: npm ci
|
|
58
|
-
- name: Type check
|
|
59
|
-
run: npm run typecheck
|
|
60
|
-
- name: Run tests
|
|
61
|
-
run: npm test --if-present
|
|
62
|
-
- name: Build
|
|
63
|
-
run: npm run build
|
|
64
|
-
- name: Verify tag matches package.json version
|
|
65
|
-
run: |
|
|
66
|
-
PKG_VERSION="$(node -p "require('./package.json').version")"
|
|
67
|
-
TAG_VERSION="${GITHUB_REF_NAME#v}" # supports tags like v1.2.3
|
|
68
|
-
echo "package.json: $PKG_VERSION"
|
|
69
|
-
echo "release tag: $TAG_VERSION"
|
|
70
|
-
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
|
|
71
|
-
echo "Release tag ($TAG_VERSION) does not match package.json version ($PKG_VERSION)."
|
|
72
|
-
exit 1
|
|
73
|
-
fi
|
|
74
|
-
- name: Show publish contents (dry run)
|
|
75
|
-
run: npm pack --dry-run
|
|
76
|
-
- name: Publish to npm (with provenance)
|
|
77
|
-
env:
|
|
78
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
79
|
-
run: npm publish --provenance --access public
|
|
80
|
-
publish-github:
|
|
81
|
-
name: Publish Package on GitHub
|
|
82
|
-
needs: release
|
|
83
|
-
runs-on: ubuntu-latest
|
|
84
|
-
permissions:
|
|
85
|
-
contents: read
|
|
86
|
-
id-token: write
|
|
87
|
-
steps:
|
|
88
|
-
- name: Checkout
|
|
89
|
-
uses: actions/checkout@v6
|
|
90
|
-
- name: Setup Node
|
|
91
|
-
uses: actions/setup-node@v6
|
|
92
|
-
with:
|
|
93
|
-
node-version: '20.x'
|
|
94
|
-
cache: 'npm'
|
|
95
|
-
registry-url: 'https://npm.pkg.github.com'
|
|
96
|
-
- name: Install FFmpeg
|
|
97
|
-
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
|
98
|
-
- name: Cache Whisper model
|
|
99
|
-
uses: actions/cache@v4
|
|
100
|
-
with:
|
|
101
|
-
path: test/models
|
|
102
|
-
key: whisper-model-tiny-v1
|
|
103
|
-
- name: Cache test audio
|
|
104
|
-
uses: actions/cache@v4
|
|
105
|
-
with:
|
|
106
|
-
path: test/audio
|
|
107
|
-
key: test-audio-jfk-v1
|
|
108
|
-
- name: Install dependencies (clean)
|
|
109
|
-
run: npm ci
|
|
110
|
-
- name: Type check
|
|
111
|
-
run: npm run typecheck
|
|
112
|
-
- name: Run tests
|
|
113
|
-
run: npm test --if-present
|
|
114
|
-
- name: Build
|
|
115
|
-
run: npm run build
|
|
116
|
-
- name: Verify tag matches package.json version
|
|
117
|
-
run: |
|
|
118
|
-
PKG_VERSION="$(node -p "require('./package.json').version")"
|
|
119
|
-
TAG_VERSION="${GITHUB_REF_NAME#v}" # supports tags like v1.2.3
|
|
120
|
-
echo "package.json: $PKG_VERSION"
|
|
121
|
-
echo "release tag: $TAG_VERSION"
|
|
122
|
-
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
|
|
123
|
-
echo "Release tag ($TAG_VERSION) does not match package.json version ($PKG_VERSION)."
|
|
124
|
-
exit 1
|
|
125
|
-
fi
|
|
126
|
-
- name: Show publish contents (dry run)
|
|
127
|
-
run: npm pack --dry-run
|
|
128
|
-
- name: Publish to GitHub Packages (with provenance)
|
|
129
|
-
env:
|
|
130
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_TOKEN }}
|
|
131
|
-
run: npm publish --provenance --access public
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
name: Tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- master
|
|
7
|
-
pull_request:
|
|
8
|
-
branches:
|
|
9
|
-
- master
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
tests:
|
|
13
|
-
name: Run tests
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- name: Checkout
|
|
17
|
-
uses: actions/checkout@v6
|
|
18
|
-
- name: Setup Node
|
|
19
|
-
uses: actions/setup-node@v6
|
|
20
|
-
with:
|
|
21
|
-
node-version: '20.x'
|
|
22
|
-
cache: 'npm'
|
|
23
|
-
- name: Install FFmpeg
|
|
24
|
-
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
|
25
|
-
- name: Cache Whisper model
|
|
26
|
-
uses: actions/cache@v4
|
|
27
|
-
with:
|
|
28
|
-
path: test/models
|
|
29
|
-
key: whisper-model-tiny-v1
|
|
30
|
-
- name: Cache test audio
|
|
31
|
-
uses: actions/cache@v4
|
|
32
|
-
with:
|
|
33
|
-
path: test/audio
|
|
34
|
-
key: test-audio-jfk-v1
|
|
35
|
-
- name: Install dependencies
|
|
36
|
-
run: npm ci
|
|
37
|
-
- name: Type check
|
|
38
|
-
run: npm run typecheck
|
|
39
|
-
- name: Build project
|
|
40
|
-
run: npm run build
|
|
41
|
-
- name: Run all tests
|
|
42
|
-
run: npm test
|
package/CLAUDE.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Build Commands
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install # Install dependencies
|
|
9
|
-
npm run build # Build all outputs (CJS, ESM, and types)
|
|
10
|
-
npm run build:cjs # Build CommonJS output only
|
|
11
|
-
npm run build:esm # Build ESM output only
|
|
12
|
-
npm run build:types # Build type declarations only
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Test Commands
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm test # Run all tests (unit + integration)
|
|
19
|
-
npm run test:unit # Run unit tests only
|
|
20
|
-
npm run test:whisper # Run Whisper.cpp integration tests only
|
|
21
|
-
npm run test:watch # Run tests in watch mode
|
|
22
|
-
npm run test:coverage # Run tests with coverage report
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Important**: Always run `npm test` after making changes to verify nothing is broken. Tests are located in the `test/` folder.
|
|
26
|
-
|
|
27
|
-
Tests are written using Vitest and cover:
|
|
28
|
-
- Provider selection logic (Whisper.cpp priority)
|
|
29
|
-
- Error handling for all providers
|
|
30
|
-
- Audio transcription functionality
|
|
31
|
-
- API request formatting
|
|
32
|
-
|
|
33
|
-
## Architecture
|
|
34
|
-
|
|
35
|
-
This is a TypeScript npm package (`@derogab/stt-proxy`) that provides a unified interface for multiple STT providers. The entire implementation is in a single file: `src/index.ts`.
|
|
36
|
-
|
|
37
|
-
### Provider Selection
|
|
38
|
-
|
|
39
|
-
The `transcribe()` function automatically selects a provider based on environment variables in this priority order:
|
|
40
|
-
1. **Whisper.cpp** - if `WHISPER_CPP_MODEL_PATH` is set
|
|
41
|
-
|
|
42
|
-
### Build Output
|
|
43
|
-
|
|
44
|
-
The package builds to three output formats:
|
|
45
|
-
- `dist/cjs/` - CommonJS (for `require()`)
|
|
46
|
-
- `dist/esm/` - ES Modules (for `import`)
|
|
47
|
-
- `dist/types/` - TypeScript declarations
|
package/src/index.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import type { Whisper, TranscribeResult } from 'smart-whisper';
|
|
7
|
-
|
|
8
|
-
export interface TranscribeOptions {
|
|
9
|
-
language?: string;
|
|
10
|
-
translate?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface TranscribeOutput {
|
|
14
|
-
text: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let whisperInstance: Whisper | null = null;
|
|
18
|
-
let currentModelPath: string | null = null;
|
|
19
|
-
|
|
20
|
-
function getWhisperModelPath(): string | undefined {
|
|
21
|
-
return process.env['WHISPER_CPP_MODEL_PATH'];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isWhisperConfigured(): boolean {
|
|
25
|
-
const modelPath = getWhisperModelPath();
|
|
26
|
-
return modelPath !== undefined && fs.existsSync(modelPath);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function getWhisperInstance(): Promise<Whisper> {
|
|
30
|
-
const modelPath = getWhisperModelPath();
|
|
31
|
-
|
|
32
|
-
if (!modelPath) {
|
|
33
|
-
throw new Error('WHISPER_CPP_MODEL_PATH environment variable is not set');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!fs.existsSync(modelPath)) {
|
|
37
|
-
throw new Error(`Whisper model not found at path: ${modelPath}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (whisperInstance && currentModelPath === modelPath) {
|
|
41
|
-
return whisperInstance;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (whisperInstance) {
|
|
45
|
-
await whisperInstance.free();
|
|
46
|
-
whisperInstance = null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const { Whisper } = await import('smart-whisper');
|
|
50
|
-
whisperInstance = new Whisper(modelPath, { gpu: true });
|
|
51
|
-
currentModelPath = modelPath;
|
|
52
|
-
|
|
53
|
-
return whisperInstance;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function audioToPcm(audioPath: string): Float32Array {
|
|
57
|
-
const tempDir = os.tmpdir();
|
|
58
|
-
const tempPcmPath = path.join(tempDir, `whisper_${Date.now()}_${Math.random().toString(36).substring(7)}.pcm`);
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
execSync(
|
|
62
|
-
`ffmpeg -y -i "${audioPath}" -ar 16000 -ac 1 -f f32le "${tempPcmPath}"`,
|
|
63
|
-
{ stdio: 'pipe' }
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const pcmBuffer = fs.readFileSync(tempPcmPath);
|
|
67
|
-
return new Float32Array(pcmBuffer.buffer, pcmBuffer.byteOffset, pcmBuffer.length / 4);
|
|
68
|
-
} finally {
|
|
69
|
-
if (fs.existsSync(tempPcmPath)) {
|
|
70
|
-
fs.unlinkSync(tempPcmPath);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function cleanTranscription(text: string): string {
|
|
76
|
-
return text
|
|
77
|
-
.replace(/[\x00-\x1F\x7F]/g, '')
|
|
78
|
-
.trim();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function resultsToText(results: TranscribeResult<'simple'>[]): string {
|
|
82
|
-
return results.map((r) => r.text).join(' ');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function transcribe_whispercpp(audioPath: string, options: TranscribeOptions = {}): Promise<TranscribeOutput> {
|
|
86
|
-
if (!fs.existsSync(audioPath)) {
|
|
87
|
-
throw new Error(`Audio file not found: ${audioPath}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const whisper = await getWhisperInstance();
|
|
91
|
-
const pcmData = audioToPcm(audioPath);
|
|
92
|
-
|
|
93
|
-
const transcribeParams: { language?: string; translate?: boolean; format: 'simple' } = {
|
|
94
|
-
format: 'simple',
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
if (options.language !== undefined) {
|
|
98
|
-
transcribeParams.language = options.language;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (options.translate !== undefined) {
|
|
102
|
-
transcribeParams.translate = options.translate;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const task = await whisper.transcribe(pcmData, transcribeParams);
|
|
106
|
-
const results = await task.result;
|
|
107
|
-
const text = resultsToText(results);
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
text: cleanTranscription(text),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export async function transcribe(audio: string | Buffer, options: TranscribeOptions = {}): Promise<TranscribeOutput> {
|
|
115
|
-
const modelPath = getWhisperModelPath();
|
|
116
|
-
|
|
117
|
-
if (modelPath) {
|
|
118
|
-
if (Buffer.isBuffer(audio)) {
|
|
119
|
-
return transcribeBuffer(audio, options);
|
|
120
|
-
}
|
|
121
|
-
return transcribe_whispercpp(audio, options);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
throw new Error('No STT provider configured. Set WHISPER_CPP_MODEL_PATH environment variable.');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function transcribeBuffer(audioBuffer: Buffer, options: TranscribeOptions = {}): Promise<TranscribeOutput> {
|
|
128
|
-
const modelPath = getWhisperModelPath();
|
|
129
|
-
|
|
130
|
-
if (!modelPath) {
|
|
131
|
-
throw new Error('No STT provider configured. Set WHISPER_CPP_MODEL_PATH environment variable.');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const tempDir = os.tmpdir();
|
|
135
|
-
const tempPath = path.join(tempDir, `whisper_input_${Date.now()}_${Math.random().toString(36).substring(7)}.audio`);
|
|
136
|
-
|
|
137
|
-
fs.writeFileSync(tempPath, audioBuffer);
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const result = await transcribe_whispercpp(tempPath, options);
|
|
141
|
-
return result;
|
|
142
|
-
} finally {
|
|
143
|
-
if (fs.existsSync(tempPath)) {
|
|
144
|
-
fs.unlinkSync(tempPath);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function freeWhisper(): Promise<void> {
|
|
150
|
-
if (whisperInstance) {
|
|
151
|
-
await whisperInstance.free();
|
|
152
|
-
whisperInstance = null;
|
|
153
|
-
currentModelPath = null;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Automatically clean up Whisper instance on process exit
|
|
158
|
-
process.on('exit', () => {
|
|
159
|
-
if (whisperInstance) {
|
|
160
|
-
// Note: Cannot use async operations in 'exit' handler
|
|
161
|
-
// The instance will be cleaned up by the process termination
|
|
162
|
-
whisperInstance = null;
|
|
163
|
-
currentModelPath = null;
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// Handle graceful shutdown signals
|
|
168
|
-
const shutdownHandler = async () => {
|
|
169
|
-
await freeWhisper();
|
|
170
|
-
process.exit(0);
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
process.on('SIGINT', shutdownHandler);
|
|
174
|
-
process.on('SIGTERM', shutdownHandler);
|
package/test/index.test.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
|
|
4
|
-
vi.mock('fs', async () => {
|
|
5
|
-
const actual = await vi.importActual<typeof import('fs')>('fs');
|
|
6
|
-
return {
|
|
7
|
-
...actual,
|
|
8
|
-
existsSync: vi.fn(),
|
|
9
|
-
readFileSync: vi.fn(),
|
|
10
|
-
writeFileSync: vi.fn(),
|
|
11
|
-
unlinkSync: vi.fn(),
|
|
12
|
-
};
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
vi.mock('child_process', () => ({
|
|
16
|
-
execSync: vi.fn(),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
vi.mock('smart-whisper', () => ({
|
|
20
|
-
Whisper: vi.fn().mockImplementation(() => ({
|
|
21
|
-
transcribe: vi.fn().mockResolvedValue({
|
|
22
|
-
result: Promise.resolve([{ text: 'Hello, world!', from: 0, to: 1000 }]),
|
|
23
|
-
}),
|
|
24
|
-
free: vi.fn().mockResolvedValue(undefined),
|
|
25
|
-
})),
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
describe('stt-proxy', () => {
|
|
29
|
-
const originalEnv = process.env;
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
vi.clearAllMocks();
|
|
33
|
-
process.env = { ...originalEnv };
|
|
34
|
-
delete process.env['WHISPER_CPP_MODEL_PATH'];
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
afterEach(() => {
|
|
38
|
-
process.env = originalEnv;
|
|
39
|
-
vi.resetModules();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
describe('transcribe', () => {
|
|
44
|
-
it('should throw error when no provider is configured (string path)', async () => {
|
|
45
|
-
const { transcribe } = await import('../src/index.js');
|
|
46
|
-
await expect(transcribe('/path/to/audio.wav')).rejects.toThrow(
|
|
47
|
-
'No STT provider configured'
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should throw error when no provider is configured (Buffer)', async () => {
|
|
52
|
-
const { transcribe } = await import('../src/index.js');
|
|
53
|
-
const buffer = Buffer.from('test');
|
|
54
|
-
await expect(transcribe(buffer)).rejects.toThrow(
|
|
55
|
-
'No STT provider configured'
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should throw error when audio file does not exist', async () => {
|
|
60
|
-
process.env['WHISPER_CPP_MODEL_PATH'] = '/path/to/model.bin';
|
|
61
|
-
vi.mocked(fs.existsSync).mockImplementation((path) => {
|
|
62
|
-
if (path === '/path/to/model.bin') return true;
|
|
63
|
-
return false;
|
|
64
|
-
});
|
|
65
|
-
const { transcribe } = await import('../src/index.js');
|
|
66
|
-
await expect(transcribe('/path/to/audio.wav')).rejects.toThrow(
|
|
67
|
-
'Audio file not found'
|
|
68
|
-
);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should throw error when model file does not exist', async () => {
|
|
72
|
-
process.env['WHISPER_CPP_MODEL_PATH'] = '/path/to/model.bin';
|
|
73
|
-
vi.mocked(fs.existsSync).mockImplementation((path) => {
|
|
74
|
-
if (path === '/path/to/audio.wav') return true;
|
|
75
|
-
return false;
|
|
76
|
-
});
|
|
77
|
-
const { transcribe } = await import('../src/index.js');
|
|
78
|
-
await expect(transcribe('/path/to/audio.wav')).rejects.toThrow(
|
|
79
|
-
'Whisper model not found at path'
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should successfully transcribe audio file', async () => {
|
|
84
|
-
process.env['WHISPER_CPP_MODEL_PATH'] = '/path/to/model.bin';
|
|
85
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
86
|
-
// Mock readFileSync to return a valid PCM buffer (Float32Array requires 4-byte aligned buffer)
|
|
87
|
-
const pcmData = new Float32Array([0.1, 0.2, 0.3]);
|
|
88
|
-
vi.mocked(fs.readFileSync).mockReturnValue(Buffer.from(pcmData.buffer));
|
|
89
|
-
const { transcribe } = await import('../src/index.js');
|
|
90
|
-
|
|
91
|
-
const result = await transcribe('/path/to/audio.wav');
|
|
92
|
-
|
|
93
|
-
expect(result).toBeDefined();
|
|
94
|
-
expect(result.text).toBe('Hello, world!');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should successfully transcribe audio from buffer', async () => {
|
|
98
|
-
process.env['WHISPER_CPP_MODEL_PATH'] = '/path/to/model.bin';
|
|
99
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
100
|
-
// Mock readFileSync to return a valid PCM buffer (Float32Array requires 4-byte aligned buffer)
|
|
101
|
-
const pcmData = new Float32Array([0.1, 0.2, 0.3]);
|
|
102
|
-
vi.mocked(fs.readFileSync).mockReturnValue(Buffer.from(pcmData.buffer));
|
|
103
|
-
const { transcribe } = await import('../src/index.js');
|
|
104
|
-
|
|
105
|
-
const audioBuffer = Buffer.from('fake audio data');
|
|
106
|
-
const result = await transcribe(audioBuffer);
|
|
107
|
-
|
|
108
|
-
expect(result).toBeDefined();
|
|
109
|
-
expect(result.text).toBe('Hello, world!');
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
describe('API exports', () => {
|
|
115
|
-
it('should export transcribe function', async () => {
|
|
116
|
-
const module = await import('../src/index.js');
|
|
117
|
-
expect(typeof module.transcribe).toBe('function');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should only export transcribe function (no other functions)', async () => {
|
|
121
|
-
const module = await import('../src/index.js');
|
|
122
|
-
const exportedFunctions = Object.keys(module).filter(
|
|
123
|
-
key => typeof module[key as keyof typeof module] === 'function'
|
|
124
|
-
);
|
|
125
|
-
expect(exportedFunctions).toEqual(['transcribe']);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as https from 'https';
|
|
5
|
-
import * as http from 'http';
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
8
|
-
|
|
9
|
-
const TEST_MODEL_DIR = path.join(__dirname, 'models');
|
|
10
|
-
const TEST_AUDIO_DIR = path.join(__dirname, 'audio');
|
|
11
|
-
const MODEL_NAME = 'ggml-tiny.bin';
|
|
12
|
-
const MODEL_PATH = path.join(TEST_MODEL_DIR, MODEL_NAME);
|
|
13
|
-
const AUDIO_FILE = path.join(TEST_AUDIO_DIR, 'jfk.wav');
|
|
14
|
-
|
|
15
|
-
const MODEL_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin';
|
|
16
|
-
const JFK_AUDIO_URL = 'https://github.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav';
|
|
17
|
-
|
|
18
|
-
async function downloadFile(url: string, destPath: string, maxRedirects = 10): Promise<void> {
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
if (maxRedirects <= 0) {
|
|
21
|
-
return reject(new Error('Too many redirects'));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const dir = path.dirname(destPath);
|
|
25
|
-
if (!fs.existsSync(dir)) {
|
|
26
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const protocol = url.startsWith('https') ? https : http;
|
|
30
|
-
|
|
31
|
-
protocol.get(url, (response) => {
|
|
32
|
-
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
33
|
-
let redirectUrl = response.headers.location;
|
|
34
|
-
if (redirectUrl.startsWith('/')) {
|
|
35
|
-
const urlObj = new URL(url);
|
|
36
|
-
redirectUrl = `${urlObj.protocol}//${urlObj.host}${redirectUrl}`;
|
|
37
|
-
}
|
|
38
|
-
downloadFile(redirectUrl, destPath, maxRedirects - 1).then(resolve).catch(reject);
|
|
39
|
-
return;
|
|
40
|
-
} else if (response.statusCode === 200) {
|
|
41
|
-
const file = fs.createWriteStream(destPath);
|
|
42
|
-
response.pipe(file);
|
|
43
|
-
file.on('finish', () => {
|
|
44
|
-
file.close();
|
|
45
|
-
resolve();
|
|
46
|
-
});
|
|
47
|
-
file.on('error', (err) => {
|
|
48
|
-
fs.unlinkSync(destPath);
|
|
49
|
-
reject(err);
|
|
50
|
-
});
|
|
51
|
-
} else {
|
|
52
|
-
reject(new Error(`HTTP ${response.statusCode}`));
|
|
53
|
-
}
|
|
54
|
-
}).on('error', reject);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function normalizeTranscription(text: string): string {
|
|
59
|
-
return text.toLowerCase().replace(/[.,!?]/g, '').trim();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
describe('whisper.cpp integration tests', () => {
|
|
63
|
-
let transcribe: typeof import('../src/index.js').transcribe;
|
|
64
|
-
|
|
65
|
-
beforeAll(async () => {
|
|
66
|
-
// Download model if needed
|
|
67
|
-
if (!fs.existsSync(MODEL_PATH) || fs.statSync(MODEL_PATH).size === 0) {
|
|
68
|
-
if (fs.existsSync(MODEL_PATH)) fs.unlinkSync(MODEL_PATH);
|
|
69
|
-
console.log(`Downloading Whisper tiny model to ${MODEL_PATH}...`);
|
|
70
|
-
console.log('This may take a few minutes on first run.');
|
|
71
|
-
await downloadFile(MODEL_URL, MODEL_PATH);
|
|
72
|
-
console.log('Model downloaded successfully.');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Download audio if needed
|
|
76
|
-
if (!fs.existsSync(AUDIO_FILE) || fs.statSync(AUDIO_FILE).size === 0) {
|
|
77
|
-
if (fs.existsSync(AUDIO_FILE)) fs.unlinkSync(AUDIO_FILE);
|
|
78
|
-
console.log(`Downloading JFK test audio to ${AUDIO_FILE}...`);
|
|
79
|
-
await downloadFile(JFK_AUDIO_URL, AUDIO_FILE);
|
|
80
|
-
console.log('Audio downloaded successfully.');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Set model path
|
|
84
|
-
process.env['WHISPER_CPP_MODEL_PATH'] = MODEL_PATH;
|
|
85
|
-
|
|
86
|
-
// Import module
|
|
87
|
-
const stt = await import('../src/index.js');
|
|
88
|
-
transcribe = stt.transcribe;
|
|
89
|
-
}, 600000); // 10 minute timeout for model download
|
|
90
|
-
|
|
91
|
-
it('should transcribe JFK speech audio file', async () => {
|
|
92
|
-
const result = await transcribe(AUDIO_FILE);
|
|
93
|
-
|
|
94
|
-
expect(result).toBeDefined();
|
|
95
|
-
expect(result.text).toBeDefined();
|
|
96
|
-
expect(typeof result.text).toBe('string');
|
|
97
|
-
expect(result.text.length).toBeGreaterThan(0);
|
|
98
|
-
|
|
99
|
-
const normalizedResult = normalizeTranscription(result.text);
|
|
100
|
-
expect(normalizedResult).toContain('ask not what your country can do for you');
|
|
101
|
-
}, 300000); // 5 minute timeout
|
|
102
|
-
|
|
103
|
-
it('should transcribe audio from buffer', async () => {
|
|
104
|
-
const audioBuffer = fs.readFileSync(AUDIO_FILE);
|
|
105
|
-
const result = await transcribe(audioBuffer);
|
|
106
|
-
|
|
107
|
-
expect(result).toBeDefined();
|
|
108
|
-
expect(result.text).toBeDefined();
|
|
109
|
-
expect(typeof result.text).toBe('string');
|
|
110
|
-
expect(result.text.length).toBeGreaterThan(0);
|
|
111
|
-
|
|
112
|
-
const normalizedResult = normalizeTranscription(result.text);
|
|
113
|
-
expect(normalizedResult).toContain('ask not what your country can do for you');
|
|
114
|
-
}, 300000); // 5 minute timeout
|
|
115
|
-
|
|
116
|
-
it('should throw error for non-existent audio file', async () => {
|
|
117
|
-
await expect(transcribe('/non/existent/audio.wav')).rejects.toThrow('Audio file not found');
|
|
118
|
-
});
|
|
119
|
-
});
|
package/tsconfig.cjs.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"outDir": "./dist/cjs",
|
|
7
|
-
"declaration": false,
|
|
8
|
-
"declarationMap": false,
|
|
9
|
-
"verbatimModuleSyntax": false,
|
|
10
|
-
"types": ["node"]
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*.ts"],
|
|
13
|
-
"exclude": ["**/*.test.ts", "vitest.config.ts"]
|
|
14
|
-
}
|
package/tsconfig.esm.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"module": "nodenext",
|
|
5
|
-
"moduleResolution": "nodenext",
|
|
6
|
-
"outDir": "./dist/esm",
|
|
7
|
-
"declaration": false,
|
|
8
|
-
"declarationMap": false,
|
|
9
|
-
"verbatimModuleSyntax": false,
|
|
10
|
-
"types": ["node"]
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*.ts"],
|
|
13
|
-
"exclude": ["**/*.test.ts", "vitest.config.ts"]
|
|
14
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"rootDir": "./src",
|
|
4
|
-
"outDir": "./dist",
|
|
5
|
-
"module": "nodenext",
|
|
6
|
-
"moduleResolution": "nodenext",
|
|
7
|
-
"target": "esnext",
|
|
8
|
-
"sourceMap": true,
|
|
9
|
-
"declaration": true,
|
|
10
|
-
"declarationMap": true,
|
|
11
|
-
"strict": true,
|
|
12
|
-
"noUncheckedIndexedAccess": true,
|
|
13
|
-
"exactOptionalPropertyTypes": true,
|
|
14
|
-
"verbatimModuleSyntax": true,
|
|
15
|
-
"isolatedModules": true,
|
|
16
|
-
"noUncheckedSideEffectImports": true,
|
|
17
|
-
"moduleDetection": "force",
|
|
18
|
-
"skipLibCheck": true
|
|
19
|
-
}
|
|
20
|
-
}
|
package/tsconfig.types.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"module": "nodenext",
|
|
5
|
-
"moduleResolution": "nodenext",
|
|
6
|
-
"outDir": "./dist/types",
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"declarationMap": true,
|
|
9
|
-
"emitDeclarationOnly": true,
|
|
10
|
-
"verbatimModuleSyntax": false,
|
|
11
|
-
"types": ["node"]
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*.ts"],
|
|
14
|
-
"exclude": ["**/*.test.ts", "vitest.config.ts"]
|
|
15
|
-
}
|
package/vitest.config.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vitest/config';
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
test: {
|
|
5
|
-
environment: 'node',
|
|
6
|
-
include: ['test/**/*.test.ts'],
|
|
7
|
-
coverage: {
|
|
8
|
-
provider: 'v8',
|
|
9
|
-
reporter: ['text', 'html'],
|
|
10
|
-
include: ['src/**/*.ts'],
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
});
|