sera-ai 1.0.22 → 1.0.24
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/dist/index.js +122 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var React3 = require('react');
|
|
4
|
+
var ffmpeg = require('@ffmpeg/ffmpeg');
|
|
4
5
|
var pRetry = require('p-retry');
|
|
5
6
|
var lucideReact = require('lucide-react');
|
|
6
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -78,14 +79,14 @@ var createFFmpegWorker = () => {
|
|
|
78
79
|
|
|
79
80
|
processAudioData: function(audioBuffer, options = {}) {
|
|
80
81
|
try {
|
|
81
|
-
const { quality = 1, bitRate = 128000 } = options;
|
|
82
|
+
const { quality = 1, bitRate = 128000, sampleRate = 44100 } = options;
|
|
82
83
|
const float32Array = new Float32Array(audioBuffer);
|
|
83
|
-
const wavBuffer = this.float32ToWavFile(float32Array);
|
|
84
|
-
|
|
84
|
+
const wavBuffer = this.float32ToWavFile(float32Array, sampleRate);
|
|
85
|
+
|
|
85
86
|
return {
|
|
86
87
|
buffer: wavBuffer,
|
|
87
88
|
size: wavBuffer.byteLength,
|
|
88
|
-
duration: float32Array.length /
|
|
89
|
+
duration: float32Array.length / sampleRate
|
|
89
90
|
};
|
|
90
91
|
} catch (error) {
|
|
91
92
|
throw new Error('Failed to process audio data: ' + error.message);
|
|
@@ -292,14 +293,38 @@ var createFFmpegWorker = () => {
|
|
|
292
293
|
};
|
|
293
294
|
var useFFmpegConverter = () => {
|
|
294
295
|
const [isLoaded, setIsLoaded] = React3.useState(true);
|
|
296
|
+
const [ffmpegLoaded, setFfmpegLoaded] = React3.useState(false);
|
|
295
297
|
const [isConverting, setIsConverting] = React3.useState(false);
|
|
296
298
|
const [progress, setProgress] = React3.useState(0);
|
|
297
299
|
const [error, setError] = React3.useState(null);
|
|
298
300
|
const [statusMessage, setStatusMessage] = React3.useState("");
|
|
301
|
+
const ffmpegRef = React3.useRef(null);
|
|
299
302
|
const loadFFmpeg = React3.useCallback(async () => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
+
if (ffmpegRef.current && ffmpegLoaded) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
setStatusMessage("Loading FFmpeg...");
|
|
308
|
+
const ffmpeg$1 = ffmpeg.createFFmpeg({
|
|
309
|
+
log: false,
|
|
310
|
+
progress: ({ ratio }) => {
|
|
311
|
+
setProgress(Math.round(ratio * 100));
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
await ffmpeg$1.load();
|
|
315
|
+
ffmpegRef.current = ffmpeg$1;
|
|
316
|
+
setFfmpegLoaded(true);
|
|
317
|
+
setIsLoaded(true);
|
|
318
|
+
setStatusMessage("");
|
|
319
|
+
console.log("FFmpeg WASM loaded successfully");
|
|
320
|
+
return true;
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.error("Failed to load FFmpeg:", err);
|
|
323
|
+
setError("Failed to load FFmpeg");
|
|
324
|
+
setStatusMessage("");
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}, [ffmpegLoaded]);
|
|
303
328
|
const convertToWav = React3.useCallback(
|
|
304
329
|
async (audioData, sampleRate = AUDIO_SAMPLE_RATE, fileName = "recording.wav") => {
|
|
305
330
|
setIsConverting(true);
|
|
@@ -344,8 +369,7 @@ var useFFmpegConverter = () => {
|
|
|
344
369
|
worker.postMessage({
|
|
345
370
|
type: "convertWav",
|
|
346
371
|
audioBuffer: audioData.buffer,
|
|
347
|
-
sampleRate
|
|
348
|
-
options: { quality: 1, bitRate: 128e3 }
|
|
372
|
+
options: { quality: 1, bitRate: 128e3, sampleRate }
|
|
349
373
|
});
|
|
350
374
|
});
|
|
351
375
|
} catch (err) {
|
|
@@ -357,6 +381,69 @@ var useFFmpegConverter = () => {
|
|
|
357
381
|
},
|
|
358
382
|
[]
|
|
359
383
|
);
|
|
384
|
+
const convertToFlac = React3.useCallback(
|
|
385
|
+
async (wavFile) => {
|
|
386
|
+
if (!ffmpegRef.current || !ffmpegLoaded) {
|
|
387
|
+
const loaded = await loadFFmpeg();
|
|
388
|
+
if (!loaded) {
|
|
389
|
+
console.error("Failed to load FFmpeg for FLAC conversion");
|
|
390
|
+
return wavFile;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const ffmpeg$1 = ffmpegRef.current;
|
|
394
|
+
if (!ffmpeg$1) {
|
|
395
|
+
console.error("FFmpeg not available");
|
|
396
|
+
return wavFile;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
setIsConverting(true);
|
|
400
|
+
setProgress(0);
|
|
401
|
+
setError(null);
|
|
402
|
+
setStatusMessage("Converting to FLAC...");
|
|
403
|
+
const inputFileName = "input.wav";
|
|
404
|
+
const outputFileName = "output.flac";
|
|
405
|
+
const wavData = await ffmpeg.fetchFile(wavFile);
|
|
406
|
+
ffmpeg$1.FS("writeFile", inputFileName, wavData);
|
|
407
|
+
setProgress(30);
|
|
408
|
+
setStatusMessage("Encoding FLAC...");
|
|
409
|
+
await ffmpeg$1.run(
|
|
410
|
+
"-i",
|
|
411
|
+
inputFileName,
|
|
412
|
+
"-c:a",
|
|
413
|
+
"flac",
|
|
414
|
+
"-compression_level",
|
|
415
|
+
"5",
|
|
416
|
+
// 0-12, higher = smaller file but slower
|
|
417
|
+
outputFileName
|
|
418
|
+
);
|
|
419
|
+
setProgress(80);
|
|
420
|
+
setStatusMessage("Finalizing...");
|
|
421
|
+
const flacData = ffmpeg$1.FS("readFile", outputFileName);
|
|
422
|
+
ffmpeg$1.FS("unlink", inputFileName);
|
|
423
|
+
ffmpeg$1.FS("unlink", outputFileName);
|
|
424
|
+
const flacBlob = new Blob([new Uint8Array(flacData)], { type: "audio/flac" });
|
|
425
|
+
const flacFileName = wavFile.name.replace(/\.wav$/i, ".flac");
|
|
426
|
+
const flacFile = new File([flacBlob], flacFileName, { type: "audio/flac" });
|
|
427
|
+
setProgress(100);
|
|
428
|
+
setStatusMessage("FLAC conversion complete");
|
|
429
|
+
console.log(`[FLAC] Converted ${wavFile.size} bytes WAV to ${flacFile.size} bytes FLAC (${Math.round((1 - flacFile.size / wavFile.size) * 100)}% reduction)`);
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
setIsConverting(false);
|
|
432
|
+
setProgress(0);
|
|
433
|
+
setStatusMessage("");
|
|
434
|
+
}, 500);
|
|
435
|
+
return flacFile;
|
|
436
|
+
} catch (err) {
|
|
437
|
+
console.error("FLAC conversion failed:", err);
|
|
438
|
+
setError("FLAC conversion failed");
|
|
439
|
+
setIsConverting(false);
|
|
440
|
+
setProgress(0);
|
|
441
|
+
setStatusMessage("");
|
|
442
|
+
return wavFile;
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
[ffmpegLoaded, loadFFmpeg]
|
|
446
|
+
);
|
|
360
447
|
const removeSilence = React3.useCallback(async (file) => {
|
|
361
448
|
if (!file) {
|
|
362
449
|
setError("No file provided for processing");
|
|
@@ -529,14 +616,16 @@ var useFFmpegConverter = () => {
|
|
|
529
616
|
setStatusMessage("");
|
|
530
617
|
}, []);
|
|
531
618
|
return {
|
|
532
|
-
ffmpeg:
|
|
619
|
+
ffmpeg: ffmpegRef.current,
|
|
533
620
|
isLoaded,
|
|
621
|
+
ffmpegLoaded,
|
|
534
622
|
isConverting,
|
|
535
623
|
progress,
|
|
536
624
|
error,
|
|
537
625
|
statusMessage,
|
|
538
626
|
loadFFmpeg,
|
|
539
627
|
convertToWav,
|
|
628
|
+
convertToFlac,
|
|
540
629
|
removeSilence,
|
|
541
630
|
reset
|
|
542
631
|
};
|
|
@@ -2960,7 +3049,8 @@ var useAudioRecorder = ({
|
|
|
2960
3049
|
loadFFmpeg,
|
|
2961
3050
|
progress,
|
|
2962
3051
|
statusMessage,
|
|
2963
|
-
convertToWav
|
|
3052
|
+
convertToWav,
|
|
3053
|
+
convertToFlac
|
|
2964
3054
|
} = useFFmpegConverter_default();
|
|
2965
3055
|
const isLoadedRef = React3__namespace.default.useRef(isLoaded);
|
|
2966
3056
|
React3__namespace.default.useEffect(() => {
|
|
@@ -3168,12 +3258,27 @@ var useAudioRecorder = ({
|
|
|
3168
3258
|
} else {
|
|
3169
3259
|
console.log("[SILENCE] Silence removal disabled or not loaded");
|
|
3170
3260
|
}
|
|
3261
|
+
let audioFile = wavFile;
|
|
3262
|
+
try {
|
|
3263
|
+
console.log("[FLAC] Converting WAV to FLAC...");
|
|
3264
|
+
const flacFile = await convertToFlac(wavFile);
|
|
3265
|
+
if (flacFile && flacFile.type === "audio/flac") {
|
|
3266
|
+
audioFile = flacFile;
|
|
3267
|
+
console.log(
|
|
3268
|
+
`[FLAC] Conversion successful: ${wavFile.size} bytes WAV \u2192 ${flacFile.size} bytes FLAC`
|
|
3269
|
+
);
|
|
3270
|
+
} else {
|
|
3271
|
+
console.warn("[FLAC] Conversion failed, using WAV file");
|
|
3272
|
+
}
|
|
3273
|
+
} catch (flacError) {
|
|
3274
|
+
console.warn("[FLAC] Conversion error, using WAV file:", flacError);
|
|
3275
|
+
}
|
|
3171
3276
|
console.log(
|
|
3172
|
-
`[OUT] Final file for transcription: ${
|
|
3277
|
+
`[OUT] Final file for transcription: ${audioFile.size} bytes, ${audioFile.name}`
|
|
3173
3278
|
);
|
|
3174
|
-
if (
|
|
3279
|
+
if (audioFile.size < 500) {
|
|
3175
3280
|
console.warn(
|
|
3176
|
-
`[WARN] Small audio file (${
|
|
3281
|
+
`[WARN] Small audio file (${audioFile.size} bytes) - may contain minimal audio data, sending to server anyway`
|
|
3177
3282
|
);
|
|
3178
3283
|
}
|
|
3179
3284
|
const requestData = {
|
|
@@ -3194,13 +3299,13 @@ var useAudioRecorder = ({
|
|
|
3194
3299
|
let contentType;
|
|
3195
3300
|
switch (selectedFormatRef.current) {
|
|
3196
3301
|
case "hl7":
|
|
3197
|
-
formData = createHL7TranscriptionRequest(
|
|
3302
|
+
formData = createHL7TranscriptionRequest(audioFile, requestData);
|
|
3198
3303
|
contentType = "multipart/form-data; hl7-request=true";
|
|
3199
3304
|
console.log("Created HL7-formatted request");
|
|
3200
3305
|
console.log("HL7 FormData entries:", Array.from(formData.entries()));
|
|
3201
3306
|
break;
|
|
3202
3307
|
case "fhir":
|
|
3203
|
-
formData = createFHIRTranscriptionRequest(
|
|
3308
|
+
formData = createFHIRTranscriptionRequest(audioFile, requestData);
|
|
3204
3309
|
contentType = "multipart/form-data; fhir-request=true";
|
|
3205
3310
|
console.log("Created FHIR-formatted request");
|
|
3206
3311
|
console.log("FHIR FormData entries:", Array.from(formData.entries()));
|
|
@@ -3213,7 +3318,7 @@ var useAudioRecorder = ({
|
|
|
3213
3318
|
} else if (sessionIdRef.current) {
|
|
3214
3319
|
formData.append("sessionId", sessionIdRef.current);
|
|
3215
3320
|
}
|
|
3216
|
-
formData.append("audio",
|
|
3321
|
+
formData.append("audio", audioFile);
|
|
3217
3322
|
formData.append("model", selectedModelRef.current);
|
|
3218
3323
|
formData.append("doctorName", doctorName);
|
|
3219
3324
|
formData.append("patientName", patientName || "");
|