UNPKG

tafrigh

Version:

A NodeJS library for transcribing audio/video to text.

2 lines 4.48 kB
import{formatMedia as G,splitFileOnSilences as Y}from"ffmpeg-simplified";import{promises as _}from"node:fs";import B from"node:path";import d from"node:process";import A from"node:process";import E from"pino";import K from"process";var P=E({base:{hostname:void 0,pid:void 0},level:K.env.LOG_LEVEL||"info"}),i=P;var u=A.env.WIT_AI_API_KEYS?A.env.WIT_AI_API_KEYS.split(" "):[],g=0,h=()=>u.length,C=()=>{if(h()===0)throw i.error("At least one Wit.ai API key is required. Please set them in your environment variables."),new Error("Empty wit.ai API keys")},x=()=>{C();let e=u[g];return g=(g+1)%u.length,e},w=e=>{u.length=0,u.push(...e),C()};import j from"p-queue";var S=(e,{end:t,start:r})=>{let o=(e.tokens||[]).filter(n=>n.token).map(n=>({...n.confidence&&{confidence:n.confidence},end:n.end/1e3+r,start:n.start/1e3+r,text:n.token}));return{...e.confidence&&{confidence:e.confidence},end:o.at(-1)?.end??t,start:o[0]?.start??r,text:e.text.trim(),...o.length>0&&{tokens:o}}};import{setTimeout as W}from"node:timers/promises";var $=5,v=1e3,b=async(e,t=$,r=v)=>{for(let o=1;o<=t;o++)try{return await e()}catch(n){if(o<t){let s=r*Math.pow(2,o-1);i.warn(`Attempt ${o} failed. Retrying in ${s}ms...`),await W(s)}else throw i.error(`All ${t} attempts failed.`),n}throw new Error("Exponential backoff failed unexpectedly")};import U from"jsonstream-next";import M from"node:fs";import D from"node:https";var q="FINAL_TRANSCRIPTION",L=(e,t)=>{let r={Authorization:`Bearer ${t.apiKey}`,"Content-Type":"audio/wav"};return(e.endsWith(".mp3")||e.endsWith(".m4a")||e.endsWith(".mp4"))&&(r["Content-Type"]="audio/mpeg3"),r};async function R(e,t){let r=M.createReadStream(e),o={headers:{...L(e,t),Accept:"application/vnd.wit.20200513+json"},hostname:"api.wit.ai",method:"POST",path:"/dictation?v=20240304"};return new Promise((n,s)=>{let a=D.request(o,p=>{if(p.statusCode!==200){s(new Error(`HTTP error! status: ${p.statusCode}`));return}let c={text:"",tokens:[]},m={},l=U.parse("*");p.pipe(l),l.on("data",f=>{f===!0?m={}:f===q?(c.tokens?.push(...m.tokens||[]),c.text+=` ${m.text}`,c.confidence=m.confidence):typeof f=="string"?m.text=f:f&&typeof f=="object"&&Object.assign(m,f)}),l.on("end",()=>{n(c)}),l.on("error",s)});a.on("error",s),r.pipe(a)})}var I=async(e,t,r,o)=>{let n=await b(()=>R(e.filename,{apiKey:x()}),o);return r?.onTranscriptionProgress&&r.onTranscriptionProgress(t),n.text?.trim()?S(n,e.range):null},H=async(e,t,r)=>{let o=[];i.debug(`transcribeAudioChunksInSingleThread for ${e.length}`);for(let[n,s]of e.entries()){let a=await I(s,n,t,r);a?(o.push(a),i.trace(`Transcript received for chunk: ${s.filename}`)):i.warn("Skipping empty transcript")}return t?.onTranscriptionFinished&&await t.onTranscriptionFinished(o),o},F=async(e,t,r,o)=>{i.debug(`transcribeAudioChunksWithConcurrency ${t}`);let n=[],s=new j({concurrency:t}),a=async(p,c)=>{let m=await I(c,p,r,o);m?(n.push(m),i.trace(`Transcript received for chunk: ${c.filename}`)):i.warn("Skipping empty transcript")};return e.forEach((p,c)=>{s.add(()=>a(c,p))}),await s.onIdle(),n.sort((p,c)=>p.start-c.start),r?.onTranscriptionFinished&&await r.onTranscriptionFinished(n),n},O=async(e,{callbacks:t,concurrency:r=1,retries:o}={})=>{let n=h(),s=r&&r<=n?r:n;return t?.onTranscriptionStarted&&await t?.onTranscriptionStarted(e.length),e.length===1||r===1?H(e,t,o):F(e,s,t,o)};var y=300,T=4,k=1;var N=e=>{if(e?.splitOptions?.chunkDuration){let{chunkDuration:t}=e.splitOptions;if(t<4)throw new Error(`chunkDuration=${t} cannot be less than ${4}s`);if(t>300)throw new Error(`chunkDuration=${t} cannot be greater than ${300}s`)}if(e?.concurrency&&e?.concurrency<1)throw new Error(`concurrency=${e?.concurrency} must be a positive integer.`)};var Ie=e=>{w(e.apiKeys)},Oe=async(e,t)=>{i.info(t,`transcribe ${e} (${typeof e})`),N(t);let r=await _.mkdtemp("tafrigh");i.debug(`Using ${r}`);let o=async()=>{t?.preventCleanup||(i.info(`Cleaning up ${r}`),await _.rm(r,{recursive:!0}))},n=async()=>{await o(),d.exit(0)};d.on("SIGINT",n),d.on("SIGTERM",n);try{let s=await G(e,B.format({dir:r,ext:".mp3",name:Date.now().toString()}),t?.preprocessOptions,t?.callbacks),a=await Y(s,r,t?.splitOptions,t?.callbacks),p=a.length?await O(a,{callbacks:t?.callbacks,concurrency:t?.concurrency,retries:t?.retries}):[];return i.debug(a,"Generated chunks"),p}finally{d.off("SIGINT",n),d.off("SIGTERM",n),await o()}};export{y as MAX_CHUNK_DURATION,T as MIN_CHUNK_DURATION,k as MIN_CONCURRENCY,Ie as init,Oe as transcribe}; //# sourceMappingURL=index.js.map