@stacksjs/audio 0.75.285 → 0.75.287
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/speech-input.js +1 -1
- package/package.json +1 -1
package/dist/speech-input.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export async function prepareSpeechAudio(input,options={}){const maxBytes=options.maxBytes??12582912,maxDuration=options.maxDurationSeconds??300,timeout=options.timeoutMs??30000;if(![maxBytes,maxDuration,timeout].every((value)=>Number.isSafeInteger(value)&&value>0)||maxDuration>3600)throw TypeError("Invalid speech audio limits");if(!input.length||input.length>maxBytes)throw Error("Audio size exceeds the upload limit or is empty");options.signal?.throwIfAborted();const executable=options.decoderPath||Bun.which("ffmpeg");if(!executable)throw Error("Speech audio preparation needs FFmpeg installed on the server");const child=Bun.spawn([executable,"-nostdin","-hide_banner","-v","error","-threads","1","-protocol_whitelist","pipe","-i","pipe:0","-map","0:a:0","-vn","-t",String(maxDuration+1),"-ac","1","-ar","16000","-c:a","pcm_s16le","-f","wav","pipe:1"],{stdin:new Blob([new Uint8Array(input)]),stdout:"pipe",stderr:"pipe"});let timedOut=!1;const stop=()=>{if(child.exitCode===null)child.kill("SIGKILL")},timer=setTimeout(()=>{timedOut=!0;stop()},timeout);options.signal?.addEventListener("abort",stop,{once:!0});async function bounded(stream,limit){const reader=stream.getReader(),chunks=[];let size=0;try{while(!0){const item=await reader.read();if(item.done)break;size+=item.value.length;if(size>limit){stop();throw Error("Audio decoder exceeded its output limit")}chunks.push(item.value)}}finally{reader.releaseLock()}const bytes=new Uint8Array(size);let offset=0;for(const chunk of chunks){bytes.set(chunk,offset);offset+=chunk.length}return bytes}try{const[audio,,code]=await Promise.all([bounded(child.stdout,(maxDuration+1)*32000+4096),bounded(child.stderr,16384),child.exited]);options.signal?.throwIfAborted();if(timedOut)throw Error("Audio decoder timed out");if(code!==0||audio.length<44)throw Error("The uploaded audio could not be decoded");const view=new DataView(audio.buffer,audio.byteOffset,audio.byteLength);for(let offset=12;offset+8<=audio.length;){const size=view.getUint32(offset+4,!0);if(String.fromCharCode(...audio.subarray(offset,offset+4))==="data"){const durationSeconds=(audio.length-offset-8)/32000;if(durationSeconds<=0||durationSeconds>maxDuration)throw Error("Audio duration exceeds the recording limit or is empty");return{audio,mimeType:"audio/wav",durationSeconds}}offset+=8+size+size%2}throw Error("Audio decoder returned an invalid WAV container")}finally{clearTimeout(timer);options.signal?.removeEventListener("abort",stop);stop()}}
|
|
1
|
+
export async function prepareSpeechAudio(input,options={}){const maxBytes=options.maxBytes??12582912,maxDuration=options.maxDurationSeconds??300,timeout=options.timeoutMs??30000;if(![maxBytes,maxDuration,timeout].every((value)=>Number.isSafeInteger(value)&&value>0)||maxDuration>3600)throw TypeError("Invalid speech audio limits");if(!input.length||input.length>maxBytes)throw Error("Audio size exceeds the upload limit or is empty");options.signal?.throwIfAborted();const executable=options.decoderPath||Bun.which("ffmpeg");if(!executable)throw Error("Speech audio preparation needs FFmpeg installed on the server");const child=Bun.spawn([executable,"-nostdin","-hide_banner","-v","error","-threads","1","-protocol_whitelist","cache,pipe","-read_ahead_limit",String(Math.min(maxBytes,2147483647)),"-i","cache:pipe:0","-map","0:a:0","-vn","-t",String(maxDuration+1),"-ac","1","-ar","16000","-c:a","pcm_s16le","-f","wav","pipe:1"],{stdin:new Blob([new Uint8Array(input)]),stdout:"pipe",stderr:"pipe"});let timedOut=!1;const stop=()=>{if(child.exitCode===null)child.kill("SIGKILL")},timer=setTimeout(()=>{timedOut=!0;stop()},timeout);options.signal?.addEventListener("abort",stop,{once:!0});async function bounded(stream,limit){const reader=stream.getReader(),chunks=[];let size=0;try{while(!0){const item=await reader.read();if(item.done)break;size+=item.value.length;if(size>limit){stop();throw Error("Audio decoder exceeded its output limit")}chunks.push(item.value)}}finally{reader.releaseLock()}const bytes=new Uint8Array(size);let offset=0;for(const chunk of chunks){bytes.set(chunk,offset);offset+=chunk.length}return bytes}try{const[audio,,code]=await Promise.all([bounded(child.stdout,(maxDuration+1)*32000+4096),bounded(child.stderr,16384),child.exited]);options.signal?.throwIfAborted();if(timedOut)throw Error("Audio decoder timed out");if(code!==0||audio.length<44)throw Error("The uploaded audio could not be decoded");const view=new DataView(audio.buffer,audio.byteOffset,audio.byteLength);for(let offset=12;offset+8<=audio.length;){const size=view.getUint32(offset+4,!0);if(String.fromCharCode(...audio.subarray(offset,offset+4))==="data"){const durationSeconds=(audio.length-offset-8)/32000;if(durationSeconds<=0||durationSeconds>maxDuration)throw Error("Audio duration exceeds the recording limit or is empty");return{audio,mimeType:"audio/wav",durationSeconds}}offset+=8+size+size%2}throw Error("Audio decoder returned an invalid WAV container")}finally{clearTimeout(timer);options.signal?.removeEventListener("abort",stop);stop()}}
|