- New src/utils/transcribeAWS.js: streams audio directly to AWS Transcribe without requiring an S3 bucket - Supports AWS_TRANSCRIBE_MEDICAL=true for Transcribe Medical (better clinical accuracy: drug names, diagnoses, procedures) - AWS_TRANSCRIBE_SPECIALTY configures specialty (default PRIMARYCARE) - transcribe.js auto-selects AWS when AWS_BEDROCK_REGION is set, or can be forced with TRANSCRIBE_PROVIDER=aws|openai - Falls back to OpenAI Whisper when AWS is not configured - Add @aws-sdk/client-transcribe-streaming as optional dependency - Update .env.example with transcription configuration docs
107 lines
4.1 KiB
JavaScript
107 lines
4.1 KiB
JavaScript
// ============================================================
|
|
// TRANSCRIBE-AWS.JS — Amazon Transcribe Streaming
|
|
// No S3 required. Audio streams directly to AWS.
|
|
//
|
|
// Env vars:
|
|
// TRANSCRIBE_PROVIDER=aws — use this instead of Whisper
|
|
// AWS_TRANSCRIBE_MEDICAL=true — use Transcribe Medical (better
|
|
// accuracy for clinical dictation)
|
|
// AWS_TRANSCRIBE_SPECIALTY=PRIMARYCARE — medical specialty
|
|
// Options: PRIMARYCARE, CARDIOLOGY, NEUROLOGY, ONCOLOGY,
|
|
// RADIOLOGY, UROLOGY
|
|
// AWS_BEDROCK_REGION — reused for Transcribe region
|
|
// AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY — reused credentials
|
|
// ============================================================
|
|
|
|
const CHUNK_SIZE = 32768; // 32 KB per audio chunk
|
|
|
|
async function* makeAudioStream(buffer) {
|
|
for (var i = 0; i < buffer.length; i += CHUNK_SIZE) {
|
|
yield { AudioEvent: { AudioChunk: buffer.slice(i, i + CHUNK_SIZE) } };
|
|
// Yield to event loop between chunks to avoid blocking
|
|
await new Promise(function(r) { setTimeout(r, 0); });
|
|
}
|
|
}
|
|
|
|
async function transcribeWithAWS(audioBuffer, mimeType) {
|
|
var TranscribeModule = require('@aws-sdk/client-transcribe-streaming');
|
|
var TranscribeStreamingClient = TranscribeModule.TranscribeStreamingClient;
|
|
|
|
var region = process.env.AWS_BEDROCK_REGION || process.env.AWS_REGION || 'us-east-1';
|
|
var credentials = process.env.AWS_ACCESS_KEY_ID ? {
|
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
} : undefined;
|
|
|
|
var client = new TranscribeStreamingClient({ region: region, credentials: credentials });
|
|
|
|
// Map browser MIME type to AWS MediaEncoding
|
|
// audio/webm;codecs=opus → ogg-opus (same Opus codec, different container)
|
|
// audio/ogg;codecs=opus → ogg-opus (exact match)
|
|
// audio/wav → pcm
|
|
var mediaEncoding = 'ogg-opus';
|
|
if (mimeType && mimeType.indexOf('wav') !== -1) mediaEncoding = 'pcm';
|
|
|
|
var useMedical = process.env.AWS_TRANSCRIBE_MEDICAL === 'true';
|
|
var specialty = process.env.AWS_TRANSCRIBE_SPECIALTY || 'PRIMARYCARE';
|
|
|
|
var transcript = '';
|
|
|
|
if (useMedical) {
|
|
var StartMedicalStreamTranscriptionCommand = TranscribeModule.StartMedicalStreamTranscriptionCommand;
|
|
var medCmd = new StartMedicalStreamTranscriptionCommand({
|
|
LanguageCode: 'en-US',
|
|
MediaSampleRateHertz: 48000,
|
|
MediaEncoding: mediaEncoding,
|
|
Specialty: specialty,
|
|
Type: 'DICTATION',
|
|
AudioStream: makeAudioStream(audioBuffer)
|
|
});
|
|
var medResp = await client.send(medCmd);
|
|
for await (var event of medResp.TranscriptResultStream) {
|
|
if (event.TranscriptEvent && event.TranscriptEvent.Transcript) {
|
|
var results = event.TranscriptEvent.Transcript.Results || [];
|
|
for (var i = 0; i < results.length; i++) {
|
|
var r = results[i];
|
|
if (!r.IsPartial && r.Alternatives && r.Alternatives[0]) {
|
|
transcript += r.Alternatives[0].Transcript + ' ';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var StartStreamTranscriptionCommand = TranscribeModule.StartStreamTranscriptionCommand;
|
|
var cmd = new StartStreamTranscriptionCommand({
|
|
LanguageCode: 'en-US',
|
|
MediaSampleRateHertz: 48000,
|
|
MediaEncoding: mediaEncoding,
|
|
AudioStream: makeAudioStream(audioBuffer)
|
|
});
|
|
var resp = await client.send(cmd);
|
|
for await (var ev of resp.TranscriptResultStream) {
|
|
if (ev.TranscriptEvent && ev.TranscriptEvent.Transcript) {
|
|
var res = ev.TranscriptEvent.Transcript.Results || [];
|
|
for (var j = 0; j < res.length; j++) {
|
|
var item = res[j];
|
|
if (!item.IsPartial && item.Alternatives && item.Alternatives[0]) {
|
|
transcript += item.Alternatives[0].Transcript + ' ';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return transcript.trim();
|
|
}
|
|
|
|
// Check if AWS Transcribe is available and configured
|
|
function isAWSTranscribeConfigured() {
|
|
try {
|
|
require('@aws-sdk/client-transcribe-streaming');
|
|
return !!(process.env.AWS_BEDROCK_REGION || process.env.AWS_REGION);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
module.exports = { transcribeWithAWS, isAWSTranscribeConfigured };
|