-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathlive-transcription-file.js
More file actions
102 lines (88 loc) · 3.31 KB
/
Copy pathlive-transcription-file.js
File metadata and controls
102 lines (88 loc) · 3.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Stream a local WAV file to the CAMB live transcription WebSocket.
//
// Useful when you cannot capture audio (CI, server, headless) but still
// want to exercise the live transcription pipeline end to end. The file
// is paced at real time so the server sees arrival patterns equivalent
// to a live capture.
//
// Run with:
// export CAMB_API_KEY=...
// node examples/live-transcription-file.js path/to/audio.wav
import fs from "node:fs";
import path from "node:path";
import { CambClient, LiveTranscriptionEncoding, ServerMessageType, bindTranscriptPrinter } from "@camb-ai/sdk";
const apiKey = process.env.CAMB_API_KEY;
if (!apiKey) {
console.error("Missing CAMB_API_KEY environment variable.");
process.exit(1);
}
function readWavHeader(buf) {
let offset = 12;
let sampleRate = 0;
let channels = 0;
let bitsPerSample = 0;
let dataStart = -1;
let dataLen = 0;
while (offset < buf.length - 8) {
const id = buf.toString("ascii", offset, offset + 4);
const size = buf.readUInt32LE(offset + 4);
if (id === "fmt ") {
channels = buf.readUInt16LE(offset + 10);
sampleRate = buf.readUInt32LE(offset + 12);
bitsPerSample = buf.readUInt16LE(offset + 22);
} else if (id === "data") {
dataStart = offset + 8;
dataLen = size;
break;
}
offset += 8 + size;
}
if (dataStart < 0) throw new Error("no data chunk in WAV");
return { sampleRate, channels, bitsPerSample, pcm: buf.subarray(dataStart, dataStart + dataLen) };
}
async function main() {
const file = process.argv[2];
if (!file) {
console.error("usage: node examples/live-transcription-file.js PATH_TO_WAV");
process.exit(2);
}
const { sampleRate, channels, bitsPerSample, pcm } = readWavHeader(fs.readFileSync(file));
console.log(`Streaming ${path.basename(file)} (${sampleRate} Hz, ${channels} ch)`);
const client = new CambClient({ apiKey });
const session = await client.liveTranscription.connect({
model: "boli-v5",
language: "en-us",
encoding: LiveTranscriptionEncoding.Linear16,
sampleRate,
channels,
});
session.on(ServerMessageType.Ready, () => console.log("[Ready]"));
const printer = bindTranscriptPrinter(session);
session.on(ServerMessageType.Error, (err) => {
printer.newline();
console.error(`[error] ${err.code}: ${err.message}`);
});
session.on(ServerMessageType.Closed, (info) => {
printer.newline();
console.log(`Closed: code=${info.code} reason=${info.reason}`);
});
await session.waitUntilReady();
const chunkMs = 100;
const bytesPerSec = sampleRate * channels * (bitsPerSample / 8);
const chunkSize = Math.floor((bytesPerSec * chunkMs) / 1000);
const t0 = Date.now();
let sent = 0;
for (let i = 0; i < pcm.length; i += chunkSize) {
await session.sendAudio(pcm.subarray(i, i + chunkSize));
sent += chunkSize;
const drift = (sent / bytesPerSec) * 1000 - (Date.now() - t0);
if (drift > 0) await new Promise((r) => setTimeout(r, drift));
}
await new Promise((r) => setTimeout(r, 1000));
await session.close();
await session.waitUntilClosed();
}
main().catch((err) => {
console.error(err);
process.exit(1);
});