@lssm-tech/lib.voice 1.7.29 → 3.0.2

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.
Files changed (178) hide show
  1. package/README.md +8 -8
  2. package/package.json +234 -321
  3. package/dist/audio/audio-concatenator.d.ts +0 -15
  4. package/dist/audio/audio-concatenator.js +0 -2
  5. package/dist/audio/duration-estimator.d.ts +0 -31
  6. package/dist/audio/duration-estimator.js +0 -2
  7. package/dist/audio/format-converter.d.ts +0 -17
  8. package/dist/audio/format-converter.js +0 -2
  9. package/dist/audio/index.d.ts +0 -4
  10. package/dist/audio/index.js +0 -2
  11. package/dist/audio/silence-generator.d.ts +0 -16
  12. package/dist/audio/silence-generator.js +0 -2
  13. package/dist/browser/audio/audio-concatenator.js +0 -1
  14. package/dist/browser/audio/duration-estimator.js +0 -1
  15. package/dist/browser/audio/format-converter.js +0 -1
  16. package/dist/browser/audio/index.js +0 -1
  17. package/dist/browser/audio/silence-generator.js +0 -1
  18. package/dist/browser/conversational/index.js +0 -2
  19. package/dist/browser/conversational/response-orchestrator.js +0 -1
  20. package/dist/browser/conversational/transcript-builder.js +0 -2
  21. package/dist/browser/conversational/turn-detector.js +0 -1
  22. package/dist/browser/conversational/types.js +0 -0
  23. package/dist/browser/conversational/voice-session-manager.js +0 -2
  24. package/dist/browser/docs/conversational.docblock.js +0 -1
  25. package/dist/browser/docs/stt.docblock.js +0 -1
  26. package/dist/browser/docs/sync.docblock.js +0 -1
  27. package/dist/browser/docs/tts.docblock.js +0 -1
  28. package/dist/browser/docs/voice.docblock.js +0 -1
  29. package/dist/browser/i18n/catalogs/en.js +0 -3
  30. package/dist/browser/i18n/catalogs/es.js +0 -3
  31. package/dist/browser/i18n/catalogs/fr.js +0 -3
  32. package/dist/browser/i18n/catalogs/index.js +0 -7
  33. package/dist/browser/i18n/index.js +0 -7
  34. package/dist/browser/i18n/keys.js +0 -1
  35. package/dist/browser/i18n/locale.js +0 -1
  36. package/dist/browser/i18n/messages.js +0 -7
  37. package/dist/browser/index.js +0 -13
  38. package/dist/browser/stt/diarization-mapper.js +0 -1
  39. package/dist/browser/stt/index.js +0 -11
  40. package/dist/browser/stt/segment-splitter.js +0 -1
  41. package/dist/browser/stt/subtitle-formatter.js +0 -11
  42. package/dist/browser/stt/transcriber.js +0 -11
  43. package/dist/browser/stt/types.js +0 -0
  44. package/dist/browser/sync/duration-negotiator.js +0 -1
  45. package/dist/browser/sync/index.js +0 -1
  46. package/dist/browser/sync/scene-adapter.js +0 -1
  47. package/dist/browser/sync/timing-calculator.js +0 -1
  48. package/dist/browser/tts/audio-assembler.js +0 -1
  49. package/dist/browser/tts/emphasis-planner.js +0 -2
  50. package/dist/browser/tts/index.js +0 -2
  51. package/dist/browser/tts/pace-analyzer.js +0 -1
  52. package/dist/browser/tts/segment-synthesizer.js +0 -1
  53. package/dist/browser/tts/types.js +0 -0
  54. package/dist/browser/tts/voice-synthesizer.js +0 -2
  55. package/dist/browser/types.js +0 -0
  56. package/dist/browser/voice.feature.js +0 -1
  57. package/dist/conversational/index.d.ts +0 -5
  58. package/dist/conversational/index.js +0 -3
  59. package/dist/conversational/response-orchestrator.d.ts +0 -37
  60. package/dist/conversational/response-orchestrator.js +0 -2
  61. package/dist/conversational/transcript-builder.d.ts +0 -25
  62. package/dist/conversational/transcript-builder.js +0 -3
  63. package/dist/conversational/turn-detector.d.ts +0 -31
  64. package/dist/conversational/turn-detector.js +0 -2
  65. package/dist/conversational/types.d.ts +0 -60
  66. package/dist/conversational/types.js +0 -1
  67. package/dist/conversational/voice-session-manager.d.ts +0 -17
  68. package/dist/conversational/voice-session-manager.js +0 -3
  69. package/dist/docs/conversational.docblock.d.ts +0 -14
  70. package/dist/docs/conversational.docblock.js +0 -2
  71. package/dist/docs/stt.docblock.d.ts +0 -12
  72. package/dist/docs/stt.docblock.js +0 -2
  73. package/dist/docs/sync.docblock.d.ts +0 -12
  74. package/dist/docs/sync.docblock.js +0 -2
  75. package/dist/docs/tts.docblock.d.ts +0 -12
  76. package/dist/docs/tts.docblock.js +0 -2
  77. package/dist/docs/voice.docblock.d.ts +0 -22
  78. package/dist/docs/voice.docblock.js +0 -2
  79. package/dist/i18n/catalogs/en.d.ts +0 -6
  80. package/dist/i18n/catalogs/en.js +0 -4
  81. package/dist/i18n/catalogs/es.d.ts +0 -4
  82. package/dist/i18n/catalogs/es.js +0 -4
  83. package/dist/i18n/catalogs/fr.d.ts +0 -4
  84. package/dist/i18n/catalogs/fr.js +0 -4
  85. package/dist/i18n/catalogs/index.d.ts +0 -3
  86. package/dist/i18n/catalogs/index.js +0 -8
  87. package/dist/i18n/index.d.ts +0 -20
  88. package/dist/i18n/index.js +0 -8
  89. package/dist/i18n/keys.d.ts +0 -50
  90. package/dist/i18n/keys.js +0 -2
  91. package/dist/i18n/locale.d.ts +0 -6
  92. package/dist/i18n/locale.js +0 -2
  93. package/dist/i18n/messages.d.ts +0 -13
  94. package/dist/i18n/messages.js +0 -8
  95. package/dist/index.d.ts +0 -7
  96. package/dist/index.js +0 -14
  97. package/dist/node/audio/audio-concatenator.js +0 -1
  98. package/dist/node/audio/duration-estimator.js +0 -1
  99. package/dist/node/audio/format-converter.js +0 -1
  100. package/dist/node/audio/index.js +0 -1
  101. package/dist/node/audio/silence-generator.js +0 -1
  102. package/dist/node/conversational/index.js +0 -2
  103. package/dist/node/conversational/response-orchestrator.js +0 -1
  104. package/dist/node/conversational/transcript-builder.js +0 -2
  105. package/dist/node/conversational/turn-detector.js +0 -1
  106. package/dist/node/conversational/types.js +0 -0
  107. package/dist/node/conversational/voice-session-manager.js +0 -2
  108. package/dist/node/docs/conversational.docblock.js +0 -1
  109. package/dist/node/docs/stt.docblock.js +0 -1
  110. package/dist/node/docs/sync.docblock.js +0 -1
  111. package/dist/node/docs/tts.docblock.js +0 -1
  112. package/dist/node/docs/voice.docblock.js +0 -1
  113. package/dist/node/i18n/catalogs/en.js +0 -3
  114. package/dist/node/i18n/catalogs/es.js +0 -3
  115. package/dist/node/i18n/catalogs/fr.js +0 -3
  116. package/dist/node/i18n/catalogs/index.js +0 -7
  117. package/dist/node/i18n/index.js +0 -7
  118. package/dist/node/i18n/keys.js +0 -1
  119. package/dist/node/i18n/locale.js +0 -1
  120. package/dist/node/i18n/messages.js +0 -7
  121. package/dist/node/index.js +0 -13
  122. package/dist/node/stt/diarization-mapper.js +0 -1
  123. package/dist/node/stt/index.js +0 -11
  124. package/dist/node/stt/segment-splitter.js +0 -1
  125. package/dist/node/stt/subtitle-formatter.js +0 -11
  126. package/dist/node/stt/transcriber.js +0 -11
  127. package/dist/node/stt/types.js +0 -0
  128. package/dist/node/sync/duration-negotiator.js +0 -1
  129. package/dist/node/sync/index.js +0 -1
  130. package/dist/node/sync/scene-adapter.js +0 -1
  131. package/dist/node/sync/timing-calculator.js +0 -1
  132. package/dist/node/tts/audio-assembler.js +0 -1
  133. package/dist/node/tts/emphasis-planner.js +0 -2
  134. package/dist/node/tts/index.js +0 -2
  135. package/dist/node/tts/pace-analyzer.js +0 -1
  136. package/dist/node/tts/segment-synthesizer.js +0 -1
  137. package/dist/node/tts/types.js +0 -0
  138. package/dist/node/tts/voice-synthesizer.js +0 -2
  139. package/dist/node/types.js +0 -0
  140. package/dist/node/voice.feature.js +0 -1
  141. package/dist/stt/diarization-mapper.d.ts +0 -19
  142. package/dist/stt/diarization-mapper.js +0 -2
  143. package/dist/stt/index.d.ts +0 -5
  144. package/dist/stt/index.js +0 -12
  145. package/dist/stt/segment-splitter.d.ts +0 -19
  146. package/dist/stt/segment-splitter.js +0 -2
  147. package/dist/stt/subtitle-formatter.d.ts +0 -19
  148. package/dist/stt/subtitle-formatter.js +0 -12
  149. package/dist/stt/transcriber.d.ts +0 -21
  150. package/dist/stt/transcriber.js +0 -12
  151. package/dist/stt/types.d.ts +0 -44
  152. package/dist/stt/types.js +0 -1
  153. package/dist/sync/duration-negotiator.d.ts +0 -37
  154. package/dist/sync/duration-negotiator.js +0 -2
  155. package/dist/sync/index.d.ts +0 -3
  156. package/dist/sync/index.js +0 -2
  157. package/dist/sync/scene-adapter.d.ts +0 -29
  158. package/dist/sync/scene-adapter.js +0 -2
  159. package/dist/sync/timing-calculator.d.ts +0 -21
  160. package/dist/sync/timing-calculator.js +0 -2
  161. package/dist/tts/audio-assembler.d.ts +0 -19
  162. package/dist/tts/audio-assembler.js +0 -2
  163. package/dist/tts/emphasis-planner.d.ts +0 -31
  164. package/dist/tts/emphasis-planner.js +0 -3
  165. package/dist/tts/index.d.ts +0 -6
  166. package/dist/tts/index.js +0 -3
  167. package/dist/tts/pace-analyzer.d.ts +0 -30
  168. package/dist/tts/pace-analyzer.js +0 -2
  169. package/dist/tts/segment-synthesizer.d.ts +0 -21
  170. package/dist/tts/segment-synthesizer.js +0 -2
  171. package/dist/tts/types.d.ts +0 -76
  172. package/dist/tts/types.js +0 -1
  173. package/dist/tts/voice-synthesizer.d.ts +0 -28
  174. package/dist/tts/voice-synthesizer.js +0 -3
  175. package/dist/types.d.ts +0 -18
  176. package/dist/types.js +0 -1
  177. package/dist/voice.feature.d.ts +0 -1
  178. package/dist/voice.feature.js +0 -2
package/dist/index.js DELETED
@@ -1,14 +0,0 @@
1
- // @bun
2
- class A{concatenate(q){if(q.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[J]=q;if(!J)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};if(q.length===1)return{...J};let{format:U,sampleRateHz:K}=J,X=J.channels??1;for(let V of q){if(V.format!==U)throw Error(`Format mismatch: expected ${U}, got ${V.format}`);if(V.sampleRateHz!==K)throw Error(`Sample rate mismatch: expected ${K}, got ${V.sampleRateHz}`)}let Q=q.reduce((V,L)=>V+L.data.length,0),Z=new Uint8Array(Q),$=0;for(let V of q)Z.set(V.data,$),$+=V.data.length;let Y=q.reduce((V,L)=>V+(L.durationMs??0),0);return{data:Z,format:U,sampleRateHz:K,durationMs:Y,channels:X}}}class G{static DEFAULT_WPM=150;estimateSeconds(q,J){let U=J??G.DEFAULT_WPM,K=q.split(/\s+/).filter(Boolean).length;return Math.ceil(K/U*60)}estimateMs(q,J){let U=J??G.DEFAULT_WPM,K=q.split(/\s+/).filter(Boolean).length;return Math.ceil(K/U*60*1000)}estimateWordCount(q,J){let U=J??G.DEFAULT_WPM;return Math.round(q/60*U)}}class P{convert(q,J){if(q.format===J)return q;return{...q,format:J}}isSupported(q,J){if(q===J)return!0;return{wav:["mp3","ogg","pcm","opus"],mp3:["wav"],ogg:["wav"],pcm:["wav"],opus:["wav"]}[q]?.includes(J)??!1}}class E{generate(q,J="wav",U=44100,K=1){let X=Math.ceil(U*q/1000),Q=2,Z=X*2*K;return{data:new Uint8Array(Z),format:J,sampleRateHz:U,durationMs:q,channels:K}}}class O{stt;llm;tts;modelSelector;selectionContext;conversationHistory=[];constructor(q,J,U){if("stt"in q)this.stt=q.stt,this.llm=q.llm,this.tts=q.tts,this.modelSelector=q.modelSelector,this.selectionContext=q.selectionContext;else{if(!J||!U)throw Error("ResponseOrchestrator requires llm and tts when constructed with positional arguments");this.stt=q,this.llm=J,this.tts=U}}async*processUserTurn(q,J){let K=(await this.stt.transcribe({audio:q,language:J.language,wordTimestamps:!1})).text;yield{type:"user_speech_ended",transcript:K},yield{type:"transcript",role:"user",text:K,timestamp:Date.now()},this.conversationHistory.push({role:"user",content:K});let X=await this.resolveModel(J.llmModel),Z=(await this.llm.chat([{role:"system",content:[{type:"text",text:J.systemPrompt}]},...this.conversationHistory.map((V)=>({role:V.role,content:[{type:"text",text:V.content}]}))],{model:X})).message.content.find((V)=>V.type==="text"),$=Z&&Z.type==="text"?Z.text:"I apologize, I could not generate a response.";this.conversationHistory.push({role:"assistant",content:$}),yield{type:"agent_speech_started",text:$},yield{type:"agent_audio",audio:(await this.tts.synthesize({text:$,voiceId:J.voiceId,language:J.language,format:J.outputFormat})).audio.data},yield{type:"agent_speech_ended"},yield{type:"transcript",role:"agent",text:$,timestamp:Date.now()}}async resolveModel(q){if(q)return q;if(this.modelSelector){let J=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(J)).modelId}return}reset(){this.conversationHistory.length=0}}class I{turns=[];currentTurn=null;sessionStartMs=Date.now();getTranscript(){return[...this.turns]}toText(){return this.turns.map((q)=>`[${q.role}] ${q.text}`).join(`
3
- `)}getTurnCount(){return this.turns.length}processEvent(q){switch(q.type){case"session_started":this.sessionStartMs=Date.now();break;case"user_speech_started":this.currentTurn={role:"user",startMs:Date.now()-this.sessionStartMs};break;case"user_speech_ended":if(this.currentTurn&&this.currentTurn.role==="user")this.currentTurn.text=q.transcript,this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"agent_speech_started":this.currentTurn={role:"agent",text:q.text,startMs:Date.now()-this.sessionStartMs};break;case"agent_speech_ended":if(this.currentTurn&&this.currentTurn.role==="agent")this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"transcript":break;default:break}}reset(){this.turns.length=0,this.currentTurn=null,this.sessionStartMs=Date.now()}}class k{silenceThresholdMs;energyThreshold;silenceStartMs=null;constructor(q=800,J=0.01){this.silenceThresholdMs=q,this.energyThreshold=J}processChunk(q,J){if(this.calculateEnergy(q)>this.energyThreshold)return this.silenceStartMs=null,!1;if(this.silenceStartMs===null)this.silenceStartMs=J;return J-this.silenceStartMs>=this.silenceThresholdMs}reset(){this.silenceStartMs=null}calculateEnergy(q){if(q.length<2)return 0;let J=0,U=Math.floor(q.length/2);for(let K=0;K<q.length-1;K+=2){let X=q[K]??0,Q=q[K+1]??0,$=((X|Q<<8)<<16>>16)/32768;J+=$*$}return Math.sqrt(J/U)}}class D{provider;constructor(q){this.provider=q.conversational}async startSession(q){let J=new I,U=await this.provider.startSession({voiceId:q.voiceId,language:q.language,systemPrompt:q.systemPrompt,llmModel:q.llmModel,inputFormat:q.inputFormat,outputFormat:q.outputFormat,turnDetection:q.turnDetection,silenceThresholdMs:q.silenceThresholdMs,maxDurationSeconds:q.maxDurationSeconds}),K={sessionId:"",status:"connecting",currentTurn:"idle",turnCount:0,durationMs:0,transcript:[]},X=this.wrapEvents(U.events,K,J);return{state:K,sendAudio:(Q)=>U.sendAudio(Q),sendText:(Q)=>U.sendText(Q),interrupt:()=>U.interrupt(),close:async()=>{let Q=await U.close();return K.status="ended",Q},events:X}}async*wrapEvents(q,J,U){for await(let K of q){switch(U.processEvent(K),K.type){case"session_started":J.sessionId=K.sessionId,J.status="active";break;case"user_speech_started":J.currentTurn="user";break;case"user_speech_ended":J.currentTurn="idle",J.turnCount+=1;break;case"agent_speech_started":J.currentTurn="agent";break;case"agent_speech_ended":J.currentTurn="idle",J.turnCount+=1;break;case"session_ended":J.status="ended",J.durationMs=K.durationMs;break}J.transcript=U.getTranscript(),yield K}}}class T{map(q,J="Speaker"){let U=[],K=new Map;for(let Z of q)if(Z.speakerId&&!U.includes(Z.speakerId))U.push(Z.speakerId),K.set(Z.speakerId,{segmentCount:0,totalSpeakingMs:0});let X=q.map((Z)=>{if(!Z.speakerId)return Z;let $=U.indexOf(Z.speakerId),Y=`${J} ${$+1}`,V=K.get(Z.speakerId);if(!V)return{...Z,speakerLabel:Y};return V.segmentCount+=1,V.totalSpeakingMs+=Z.endMs-Z.startMs,{...Z,speakerLabel:Y}}),Q=U.map((Z,$)=>{let Y=K.get(Z);return{id:Z,label:`${J} ${$+1}`,segmentCount:Y?.segmentCount??0,totalSpeakingMs:Y?.totalSpeakingMs??0}});return{segments:X,speakers:Q}}}class C{static DEFAULT_MAX_CHUNK_MS=300000;split(q,J=C.DEFAULT_MAX_CHUNK_MS){let U=q.durationMs??this.estimateDurationMs(q);if(U<=J)return[q];let K=[],X=q.data.length/Math.max(U,1),Q=0;while(Q<U){let Z=Math.min(J,U-Q),$=Math.floor(Q*X),Y=Math.floor((Q+Z)*X);K.push({data:q.data.slice($,Y),format:q.format,sampleRateHz:q.sampleRateHz,durationMs:Z,channels:q.channels}),Q+=Z}return K}estimateDurationMs(q){let U=q.channels??1,K=q.data.length/(2*U);return Math.ceil(K/q.sampleRateHz*1000)}}class N{toSRT(q){return q.map((J,U)=>{let K=this.formatTimeSRT(J.startMs),X=this.formatTimeSRT(J.endMs),Q=J.speakerLabel?`[${J.speakerLabel}] `:"";return`${U+1}
4
- ${K} --> ${X}
5
- ${Q}${J.text}`}).join(`
6
-
7
- `)}toVTT(q){return`WEBVTT
8
-
9
- `+q.map((K,X)=>{let Q=this.formatTimeVTT(K.startMs),Z=this.formatTimeVTT(K.endMs),$=K.speakerLabel?`<v ${K.speakerLabel}>`:"";return`${X+1}
10
- ${Q} --> ${Z}
11
- ${$}${K.text}`}).join(`
12
-
13
- `)}formatTimeSRT(q){let J=Math.floor(q/3600000),U=Math.floor(q%3600000/60000),K=Math.floor(q%60000/1000),X=q%1000;return`${this.pad(J,2)}:${this.pad(U,2)}:${this.pad(K,2)},${this.pad(X,3)}`}formatTimeVTT(q){let J=Math.floor(q/3600000),U=Math.floor(q%3600000/60000),K=Math.floor(q%60000/1000),X=q%1000;return`${this.pad(J,2)}:${this.pad(U,2)}:${this.pad(K,2)}.${this.pad(X,3)}`}pad(q,J){return q.toString().padStart(J,"0")}}class x{stt;segmentSplitter=new C;diarizationMapper=new T;subtitleFormatter=new N;constructor(q){this.stt=q.stt}async transcribe(q){let J=b(),U=this.segmentSplitter.split(q.audio),K=[],X="",Q=0,Z=0;for(let W of U){let R=await this.stt.transcribe({audio:W,language:q.language,diarize:q.diarize,speakerCount:q.speakerCount,wordTimestamps:!0,vocabularyHints:q.vocabularyHints}),h=R.segments.map((H)=>({text:H.text,startMs:H.startMs+Z,endMs:H.endMs+Z,speakerId:H.speakerId,speakerName:H.speakerName,confidence:H.confidence}));K.push(...h),X+=(X?" ":"")+R.text,Q+=R.durationMs,Z+=W.durationMs??0}let $=K,Y;if(q.diarize){let W=this.diarizationMapper.map(K);$=W.segments,Y=W.speakers}let V={text:X,segments:$,language:q.language??"en",durationMs:Q},L,_=q.subtitleFormat??"none";if(_==="srt")L=this.subtitleFormatter.toSRT($);else if(_==="vtt")L=this.subtitleFormatter.toVTT($);return{id:J,transcript:V,subtitles:L,speakers:Y}}async*transcribeStream(q,J){if(!this.stt.transcribeStream)throw Error("Streaming transcription not supported by the current STT provider");let U=this.stt.transcribeStream(q,{language:J?.language,diarize:J?.diarize,speakerCount:J?.speakerCount,wordTimestamps:!0,vocabularyHints:J?.vocabularyHints});for await(let K of U)yield{text:K.text,startMs:K.startMs,endMs:K.endMs,speakerId:K.speakerId,speakerLabel:K.speakerName,confidence:K.confidence}}}function b(){let q=Date.now().toString(36),J=Math.random().toString(36).slice(2,8);return`stt_${q}_${J}`}class B{static UPPER_THRESHOLD=1.1;static LOWER_THRESHOLD=0.7;static MAX_RATE=1.3;static MIN_RATE=0.8;negotiate(q,J){let U=[],K=q.segments.map((X)=>{let Q=J.get(X.sceneId);if(Q===void 0)return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:X.recommendedSceneDurationInFrames,voiceDurationInFrames:X.durationInFrames,action:"no_change",finalSceneDurationInFrames:X.recommendedSceneDurationInFrames}),X;let Z=X.durationInFrames/Q;if(Z>B.UPPER_THRESHOLD){let $=Math.min(Z,B.MAX_RATE);return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:Q,voiceDurationInFrames:X.durationInFrames,action:Z>B.MAX_RATE?"extend_scene":"suggest_rate_change",suggestedRate:$,finalSceneDurationInFrames:X.recommendedSceneDurationInFrames}),X}if(Z<B.LOWER_THRESHOLD){let $=Math.max(Z,B.MIN_RATE);return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:Q,voiceDurationInFrames:X.durationInFrames,action:"pad_silence",suggestedRate:$,finalSceneDurationInFrames:Q}),{...X,recommendedSceneDurationInFrames:Q}}return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:Q,voiceDurationInFrames:X.durationInFrames,action:"no_change",finalSceneDurationInFrames:X.recommendedSceneDurationInFrames}),X});return{timingMap:{...q,segments:K},adjustments:U}}}class S{durationEstimator=new G;adapt(q){let J=q.scenes.filter((Q)=>Q.narrationText&&Q.narrationText.trim().length>0),U=J.map((Q,Z)=>{let $=Q.narrationText??"";return{sceneId:Q.id,text:$,estimatedDurationSeconds:this.durationEstimator.estimateSeconds($),contentType:this.inferContentType(Z,J.length)}}),K=U.map((Q)=>Q.text).join(" "),X=U.reduce((Q,Z)=>Q+Z.estimatedDurationSeconds,0);return{fullText:K,segments:U,estimatedDurationSeconds:X}}inferContentType(q,J){if(q===0)return"intro";if(q===J-1)return"cta";if(q===1&&J>3)return"problem";if(q===J-2&&J>3)return"metric";return"solution"}}class w{calculate(q,J,U=1.15){let K=q.map((Q)=>{let Z=Math.ceil(Q.durationMs/1000*J),$=Math.ceil(Z*U),Y=Q.wordTimings?.map((V)=>({word:V.word,startMs:V.startMs,endMs:V.endMs}));return{sceneId:Q.sceneId,durationMs:Q.durationMs,durationInFrames:Z,recommendedSceneDurationInFrames:$,wordTimings:Y}});return{totalDurationMs:q.reduce((Q,Z)=>Q+Z.durationMs,0),segments:K,fps:J}}recalculateForFps(q,J){let U=q.segments.map((K)=>{let X=Math.ceil(K.durationMs/1000*J),Q=K.recommendedSceneDurationInFrames/Math.max(K.durationInFrames,1);return{...K,durationInFrames:X,recommendedSceneDurationInFrames:Math.ceil(X*Q)}});return{...q,segments:U,fps:J}}}class z{concatenator=new A;silenceGenerator=new E;assemble(q,J,U=500){if(q.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[K]=q;if(!K)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let X=new Map(J.map(($)=>[$.sceneId,$])),Q=K.audio,Z=[];for(let $=0;$<q.length;$++){let Y=q[$];if(!Y)continue;let V=X.get(Y.sceneId),L=V?.leadingSilenceMs??0;if(L>0)Z.push(this.silenceGenerator.generate(L,Q.format,Q.sampleRateHz,Q.channels??1));Z.push(Y.audio);let _=V?.trailingSilenceMs??($<q.length-1?U:0);if(_>0)Z.push(this.silenceGenerator.generate(_,Q.format,Q.sampleRateHz,Q.channels??1))}return this.concatenator.concatenate(Z)}}var v={intro:{rate:0.95,emphasis:"normal",tone:"authoritative",leadingSilenceMs:0,trailingSilenceMs:500},problem:{rate:0.9,emphasis:"strong",tone:"urgent",leadingSilenceMs:300,trailingSilenceMs:500},solution:{rate:1,emphasis:"normal",tone:"calm",leadingSilenceMs:300,trailingSilenceMs:500},metric:{rate:0.85,emphasis:"strong",tone:"excited",leadingSilenceMs:300,trailingSilenceMs:600},cta:{rate:0.9,emphasis:"strong",tone:"authoritative",leadingSilenceMs:400,trailingSilenceMs:0},transition:{rate:1.1,emphasis:"reduced",tone:"neutral",leadingSilenceMs:200,trailingSilenceMs:300}};class j{analyze(q,J=1){return q.map((U)=>{let K=v[U.contentType];return{sceneId:U.sceneId,rate:K.rate*J,emphasis:K.emphasis,tone:K.tone,leadingSilenceMs:K.leadingSilenceMs,trailingSilenceMs:K.trailingSilenceMs}})}getDefaults(q){return{...v[q]}}}class F{llm;model;modelSelector;selectionContext;paceAnalyzer;constructor(q){this.llm=q?.llm,this.model=q?.model,this.modelSelector=q?.modelSelector,this.selectionContext=q?.selectionContext,this.paceAnalyzer=new j}async plan(q,J=1){if(!this.llm)return this.paceAnalyzer.analyze(q,J);try{return await this.planWithLlm(q,J)}catch{return this.paceAnalyzer.analyze(q,J)}}async resolveModel(){if(this.model)return this.model;if(this.modelSelector){let q=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(q)).modelId}return}async planWithLlm(q,J){if(!this.llm)return this.paceAnalyzer.analyze(q,J);let U=await this.resolveModel(),X=(await this.llm.chat([{role:"system",content:[{type:"text",text:["You are a voice director planning emphasis and pacing for TTS narration.","For each segment, return a JSON array of directives.","Each directive has: sceneId, rate (0.7-1.3), emphasis (reduced|normal|strong),","tone (neutral|urgent|excited|calm|authoritative), leadingSilenceMs, trailingSilenceMs.","Return ONLY a JSON array, no other text."].join(`
14
- `)}]},{role:"user",content:[{type:"text",text:JSON.stringify(q.map((Z)=>({sceneId:Z.sceneId,text:Z.text,contentType:Z.contentType})))}]}],{model:U,temperature:0.3,responseFormat:"json"})).message.content.find((Z)=>Z.type==="text");if(!X||X.type!=="text")return this.paceAnalyzer.analyze(q,J);return JSON.parse(X.text).map((Z)=>({...Z,rate:Z.rate*J}))}}class y{tts;constructor(q){this.tts=q}async synthesizeAll(q,J,U){let K=new Map(U.map((Q)=>[Q.sceneId,Q]));return await Promise.all(q.map((Q)=>this.synthesizeOne(Q,J,K.get(Q.sceneId))))}async synthesizeOne(q,J,U){let K=await this.tts.synthesize({text:q.text,voiceId:J.voiceId,language:J.language,style:J.style,stability:J.stability,rate:U?.rate,emphasis:U?.emphasis});return{sceneId:q.sceneId,audio:K.audio,durationMs:K.audio.durationMs??0,wordTimings:K.wordTimings?.map((X)=>({word:X.word,startMs:X.startMs,endMs:X.endMs}))}}}class M{segmentSynthesizer;emphasisPlanner;audioAssembler=new z;durationEstimator=new G;paceAnalyzer=new j;options;constructor(q){this.options=q,this.segmentSynthesizer=new y(q.tts),this.emphasisPlanner=new F({llm:q.llm,model:q.model})}async synthesize(q){let J=this.buildScript(q);return this.executePipeline(J,q.voice,q.pacing)}async synthesizeForVideo(q){let J=this.buildScriptFromScenePlan(q);return this.executePipeline(J,q.voice,q.pacing,q.fps)}async executePipeline(q,J,U,K){let X=f(),Q=U?.baseRate??1,Z=await this.emphasisPlanner.plan(q.segments,Q),$=await this.segmentSynthesizer.synthesizeAll(q.segments,J,Z),Y=U?.segmentPauseMs??500,V=this.audioAssembler.assemble($,Z,Y),L=K??this.options.fps??30,_=U?.breathingRoomFactor??1.15,W=this.buildTimingMap($,L,_);return{id:X,script:q,pacingDirectives:Z,segments:$,assembledAudio:V,timingMap:W}}buildScript(q){let J=[],U=`${q.content.title}. ${q.content.summary}`;if(J.push({sceneId:"intro",text:U,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(U),contentType:"intro"}),q.content.problems.length>0){let Q=q.content.problems.join(". ");J.push({sceneId:"problems",text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"problem"})}if(q.content.solutions.length>0){let Q=q.content.solutions.join(". ");J.push({sceneId:"solutions",text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"solution"})}if(q.content.metrics&&q.content.metrics.length>0){let Q=q.content.metrics.join(". ");J.push({sceneId:"metrics",text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"metric"})}if(q.content.callToAction)J.push({sceneId:"cta",text:q.content.callToAction,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(q.content.callToAction),contentType:"cta"});let K=J.map((Q)=>Q.text).join(" "),X=J.reduce((Q,Z)=>Q+Z.estimatedDurationSeconds,0);return{fullText:K,segments:J,estimatedDurationSeconds:X}}buildScriptFromScenePlan(q){let J=q.scenePlan.scenes.filter((X)=>X.narrationText).map((X)=>{let Q=X.narrationText??"";return{sceneId:X.id,text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"intro"}}),U=J.map((X)=>X.text).join(" "),K=J.reduce((X,Q)=>X+Q.estimatedDurationSeconds,0);return{fullText:U,segments:J,estimatedDurationSeconds:K}}buildTimingMap(q,J,U){let K=q.map((Q)=>{let Z=Math.ceil(Q.durationMs/1000*J);return{sceneId:Q.sceneId,durationMs:Q.durationMs,durationInFrames:Z,recommendedSceneDurationInFrames:Math.ceil(Z*U),wordTimings:Q.wordTimings?.map(($)=>({word:$.word,startMs:$.startMs,endMs:$.endMs}))}});return{totalDurationMs:q.reduce((Q,Z)=>Q+Z.durationMs,0),segments:K,fps:J}}}function f(){let q=Date.now().toString(36),J=Math.random().toString(36).slice(2,8);return`tts_${q}_${J}`}import{defineFeature as p}from"@contractspec/lib.contracts-spec/features";var gq=p({meta:{key:"libs.voice",version:"1.0.0",title:"Voice",description:"Voice capabilities: TTS, STT, and conversational AI",domain:"voice",owners:["@contractspec-core"],tags:["package","libs","voice"],stability:"experimental"}});export{M as VoiceSynthesizer,D as VoiceSessionManager,gq as VoiceFeature,k as TurnDetector,I as TranscriptBuilder,x as Transcriber,w as TimingCalculator,N as SubtitleFormatter,E as SilenceGenerator,y as SegmentSynthesizer,C as SegmentSplitter,S as SceneAdapter,O as ResponseOrchestrator,j as PaceAnalyzer,P as FormatConverter,F as EmphasisPlanner,B as DurationNegotiator,G as DurationEstimator,T as DiarizationMapper,A as AudioConcatenator,z as AudioAssembler};
@@ -1 +0,0 @@
1
- class L{concatenate(k){if(k.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[q]=k;if(!q)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};if(k.length===1)return{...q};let{format:x,sampleRateHz:z}=q,H=q.channels??1;for(let j of k){if(j.format!==x)throw Error(`Format mismatch: expected ${x}, got ${j.format}`);if(j.sampleRateHz!==z)throw Error(`Sample rate mismatch: expected ${z}, got ${j.sampleRateHz}`)}let J=k.reduce((j,A)=>j+A.data.length,0),E=new Uint8Array(J),G=0;for(let j of k)E.set(j.data,G),G+=j.data.length;let K=k.reduce((j,A)=>j+(A.durationMs??0),0);return{data:E,format:x,sampleRateHz:z,durationMs:K,channels:H}}}export{L as AudioConcatenator};
@@ -1 +0,0 @@
1
- class q{static DEFAULT_WPM=150;estimateSeconds(b,h){let j=h??q.DEFAULT_WPM,v=b.split(/\s+/).filter(Boolean).length;return Math.ceil(v/j*60)}estimateMs(b,h){let j=h??q.DEFAULT_WPM,v=b.split(/\s+/).filter(Boolean).length;return Math.ceil(v/j*60*1000)}estimateWordCount(b,h){let j=h??q.DEFAULT_WPM;return Math.round(b/60*j)}}export{q as DurationEstimator};
@@ -1 +0,0 @@
1
- class k{convert(c,j){if(c.format===j)return c;return{...c,format:j}}isSupported(c,j){if(c===j)return!0;return{wav:["mp3","ogg","pcm","opus"],mp3:["wav"],ogg:["wav"],pcm:["wav"],opus:["wav"]}[c]?.includes(j)??!1}}export{k as FormatConverter};
@@ -1 +0,0 @@
1
- class Q{concatenate(j){if(j.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[q]=j;if(!q)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};if(j.length===1)return{...q};let{format:v,sampleRateHz:A}=q,K=q.channels??1;for(let x of j){if(x.format!==v)throw Error(`Format mismatch: expected ${v}, got ${x.format}`);if(x.sampleRateHz!==A)throw Error(`Sample rate mismatch: expected ${A}, got ${x.sampleRateHz}`)}let O=j.reduce((x,N)=>x+N.data.length,0),J=new Uint8Array(O),L=0;for(let x of j)J.set(x.data,L),L+=x.data.length;let X=j.reduce((x,N)=>x+(N.durationMs??0),0);return{data:J,format:v,sampleRateHz:A,durationMs:X,channels:K}}}class G{static DEFAULT_WPM=150;estimateSeconds(j,q){let v=q??G.DEFAULT_WPM,A=j.split(/\s+/).filter(Boolean).length;return Math.ceil(A/v*60)}estimateMs(j,q){let v=q??G.DEFAULT_WPM,A=j.split(/\s+/).filter(Boolean).length;return Math.ceil(A/v*60*1000)}estimateWordCount(j,q){let v=q??G.DEFAULT_WPM;return Math.round(j/60*v)}}class T{convert(j,q){if(j.format===q)return j;return{...j,format:q}}isSupported(j,q){if(j===q)return!0;return{wav:["mp3","ogg","pcm","opus"],mp3:["wav"],ogg:["wav"],pcm:["wav"],opus:["wav"]}[j]?.includes(q)??!1}}class U{generate(j,q="wav",v=44100,A=1){let K=Math.ceil(v*j/1000),O=2,J=K*2*A;return{data:new Uint8Array(J),format:q,sampleRateHz:v,durationMs:j,channels:A}}}export{U as SilenceGenerator,T as FormatConverter,G as DurationEstimator,Q as AudioConcatenator};
@@ -1 +0,0 @@
1
- class x{generate(b,k="wav",g=44100,j=1){let q=Math.ceil(g*b/1000),y=2,v=q*2*j;return{data:new Uint8Array(v),format:k,sampleRateHz:g,durationMs:b,channels:j}}}export{x as SilenceGenerator};
@@ -1,2 +0,0 @@
1
- class I{stt;llm;tts;modelSelector;selectionContext;conversationHistory=[];constructor(C,S,V){if("stt"in C)this.stt=C.stt,this.llm=C.llm,this.tts=C.tts,this.modelSelector=C.modelSelector,this.selectionContext=C.selectionContext;else{if(!S||!V)throw Error("ResponseOrchestrator requires llm and tts when constructed with positional arguments");this.stt=C,this.llm=S,this.tts=V}}async*processUserTurn(C,S){let D=(await this.stt.transcribe({audio:C,language:S.language,wordTimestamps:!1})).text;yield{type:"user_speech_ended",transcript:D},yield{type:"transcript",role:"user",text:D,timestamp:Date.now()},this.conversationHistory.push({role:"user",content:D});let E=await this.resolveModel(S.llmModel),F=(await this.llm.chat([{role:"system",content:[{type:"text",text:S.systemPrompt}]},...this.conversationHistory.map((G)=>({role:G.role,content:[{type:"text",text:G.content}]}))],{model:E})).message.content.find((G)=>G.type==="text"),q=F&&F.type==="text"?F.text:"I apologize, I could not generate a response.";this.conversationHistory.push({role:"assistant",content:q}),yield{type:"agent_speech_started",text:q},yield{type:"agent_audio",audio:(await this.tts.synthesize({text:q,voiceId:S.voiceId,language:S.language,format:S.outputFormat})).audio.data},yield{type:"agent_speech_ended"},yield{type:"transcript",role:"agent",text:q,timestamp:Date.now()}}async resolveModel(C){if(C)return C;if(this.modelSelector){let S=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(S)).modelId}return}reset(){this.conversationHistory.length=0}}class H{turns=[];currentTurn=null;sessionStartMs=Date.now();getTranscript(){return[...this.turns]}toText(){return this.turns.map((C)=>`[${C.role}] ${C.text}`).join(`
2
- `)}getTurnCount(){return this.turns.length}processEvent(C){switch(C.type){case"session_started":this.sessionStartMs=Date.now();break;case"user_speech_started":this.currentTurn={role:"user",startMs:Date.now()-this.sessionStartMs};break;case"user_speech_ended":if(this.currentTurn&&this.currentTurn.role==="user")this.currentTurn.text=C.transcript,this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"agent_speech_started":this.currentTurn={role:"agent",text:C.text,startMs:Date.now()-this.sessionStartMs};break;case"agent_speech_ended":if(this.currentTurn&&this.currentTurn.role==="agent")this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"transcript":break;default:break}}reset(){this.turns.length=0,this.currentTurn=null,this.sessionStartMs=Date.now()}}class J{silenceThresholdMs;energyThreshold;silenceStartMs=null;constructor(C=800,S=0.01){this.silenceThresholdMs=C,this.energyThreshold=S}processChunk(C,S){if(this.calculateEnergy(C)>this.energyThreshold)return this.silenceStartMs=null,!1;if(this.silenceStartMs===null)this.silenceStartMs=S;return S-this.silenceStartMs>=this.silenceThresholdMs}reset(){this.silenceStartMs=null}calculateEnergy(C){if(C.length<2)return 0;let S=0,V=Math.floor(C.length/2);for(let D=0;D<C.length-1;D+=2){let E=C[D]??0,j=C[D+1]??0,q=((E|j<<8)<<16>>16)/32768;S+=q*q}return Math.sqrt(S/V)}}class K{provider;constructor(C){this.provider=C.conversational}async startSession(C){let S=new H,V=await this.provider.startSession({voiceId:C.voiceId,language:C.language,systemPrompt:C.systemPrompt,llmModel:C.llmModel,inputFormat:C.inputFormat,outputFormat:C.outputFormat,turnDetection:C.turnDetection,silenceThresholdMs:C.silenceThresholdMs,maxDurationSeconds:C.maxDurationSeconds}),D={sessionId:"",status:"connecting",currentTurn:"idle",turnCount:0,durationMs:0,transcript:[]},E=this.wrapEvents(V.events,D,S);return{state:D,sendAudio:(j)=>V.sendAudio(j),sendText:(j)=>V.sendText(j),interrupt:()=>V.interrupt(),close:async()=>{let j=await V.close();return D.status="ended",j},events:E}}async*wrapEvents(C,S,V){for await(let D of C){switch(V.processEvent(D),D.type){case"session_started":S.sessionId=D.sessionId,S.status="active";break;case"user_speech_started":S.currentTurn="user";break;case"user_speech_ended":S.currentTurn="idle",S.turnCount+=1;break;case"agent_speech_started":S.currentTurn="agent";break;case"agent_speech_ended":S.currentTurn="idle",S.turnCount+=1;break;case"session_ended":S.status="ended",S.durationMs=D.durationMs;break}S.transcript=V.getTranscript(),yield D}}}export{K as VoiceSessionManager,J as TurnDetector,H as TranscriptBuilder,I as ResponseOrchestrator};
@@ -1 +0,0 @@
1
- class D{stt;llm;tts;modelSelector;selectionContext;conversationHistory=[];constructor(b,j,k){if("stt"in b)this.stt=b.stt,this.llm=b.llm,this.tts=b.tts,this.modelSelector=b.modelSelector,this.selectionContext=b.selectionContext;else{if(!j||!k)throw Error("ResponseOrchestrator requires llm and tts when constructed with positional arguments");this.stt=b,this.llm=j,this.tts=k}}async*processUserTurn(b,j){let z=(await this.stt.transcribe({audio:b,language:j.language,wordTimestamps:!1})).text;yield{type:"user_speech_ended",transcript:z},yield{type:"transcript",role:"user",text:z,timestamp:Date.now()},this.conversationHistory.push({role:"user",content:z});let C=await this.resolveModel(j.llmModel),B=(await this.llm.chat([{role:"system",content:[{type:"text",text:j.systemPrompt}]},...this.conversationHistory.map((w)=>({role:w.role,content:[{type:"text",text:w.content}]}))],{model:C})).message.content.find((w)=>w.type==="text"),q=B&&B.type==="text"?B.text:"I apologize, I could not generate a response.";this.conversationHistory.push({role:"assistant",content:q}),yield{type:"agent_speech_started",text:q},yield{type:"agent_audio",audio:(await this.tts.synthesize({text:q,voiceId:j.voiceId,language:j.language,format:j.outputFormat})).audio.data},yield{type:"agent_speech_ended"},yield{type:"transcript",role:"agent",text:q,timestamp:Date.now()}}async resolveModel(b){if(b)return b;if(this.modelSelector){let j=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(j)).modelId}return}reset(){this.conversationHistory.length=0}}export{D as ResponseOrchestrator};
@@ -1,2 +0,0 @@
1
- class d{turns=[];currentTurn=null;sessionStartMs=Date.now();getTranscript(){return[...this.turns]}toText(){return this.turns.map((b)=>`[${b.role}] ${b.text}`).join(`
2
- `)}getTurnCount(){return this.turns.length}processEvent(b){switch(b.type){case"session_started":this.sessionStartMs=Date.now();break;case"user_speech_started":this.currentTurn={role:"user",startMs:Date.now()-this.sessionStartMs};break;case"user_speech_ended":if(this.currentTurn&&this.currentTurn.role==="user")this.currentTurn.text=b.transcript,this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"agent_speech_started":this.currentTurn={role:"agent",text:b.text,startMs:Date.now()-this.sessionStartMs};break;case"agent_speech_ended":if(this.currentTurn&&this.currentTurn.role==="agent")this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"transcript":break;default:break}}reset(){this.turns.length=0,this.currentTurn=null,this.sessionStartMs=Date.now()}}export{d as TranscriptBuilder};
@@ -1 +0,0 @@
1
- class E{silenceThresholdMs;energyThreshold;silenceStartMs=null;constructor(f=800,j=0.01){this.silenceThresholdMs=f,this.energyThreshold=j}processChunk(f,j){if(this.calculateEnergy(f)>this.energyThreshold)return this.silenceStartMs=null,!1;if(this.silenceStartMs===null)this.silenceStartMs=j;return j-this.silenceStartMs>=this.silenceThresholdMs}reset(){this.silenceStartMs=null}calculateEnergy(f){if(f.length<2)return 0;let j=0,x=Math.floor(f.length/2);for(let q=0;q<f.length-1;q+=2){let A=f[q]??0,D=f[q+1]??0,B=((A|D<<8)<<16>>16)/32768;j+=B*B}return Math.sqrt(j/x)}}export{E as TurnDetector};
File without changes
@@ -1,2 +0,0 @@
1
- class D{turns=[];currentTurn=null;sessionStartMs=Date.now();getTranscript(){return[...this.turns]}toText(){return this.turns.map((j)=>`[${j.role}] ${j.text}`).join(`
2
- `)}getTurnCount(){return this.turns.length}processEvent(j){switch(j.type){case"session_started":this.sessionStartMs=Date.now();break;case"user_speech_started":this.currentTurn={role:"user",startMs:Date.now()-this.sessionStartMs};break;case"user_speech_ended":if(this.currentTurn&&this.currentTurn.role==="user")this.currentTurn.text=j.transcript,this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"agent_speech_started":this.currentTurn={role:"agent",text:j.text,startMs:Date.now()-this.sessionStartMs};break;case"agent_speech_ended":if(this.currentTurn&&this.currentTurn.role==="agent")this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"transcript":break;default:break}}reset(){this.turns.length=0,this.currentTurn=null,this.sessionStartMs=Date.now()}}class G{provider;constructor(j){this.provider=j.conversational}async startSession(j){let q=new D,A=await this.provider.startSession({voiceId:j.voiceId,language:j.language,systemPrompt:j.systemPrompt,llmModel:j.llmModel,inputFormat:j.inputFormat,outputFormat:j.outputFormat,turnDetection:j.turnDetection,silenceThresholdMs:j.silenceThresholdMs,maxDurationSeconds:j.maxDurationSeconds}),z={sessionId:"",status:"connecting",currentTurn:"idle",turnCount:0,durationMs:0,transcript:[]},F=this.wrapEvents(A.events,z,q);return{state:z,sendAudio:(C)=>A.sendAudio(C),sendText:(C)=>A.sendText(C),interrupt:()=>A.interrupt(),close:async()=>{let C=await A.close();return z.status="ended",C},events:F}}async*wrapEvents(j,q,A){for await(let z of j){switch(A.processEvent(z),z.type){case"session_started":q.sessionId=z.sessionId,q.status="active";break;case"user_speech_started":q.currentTurn="user";break;case"user_speech_ended":q.currentTurn="idle",q.turnCount+=1;break;case"agent_speech_started":q.currentTurn="agent";break;case"agent_speech_ended":q.currentTurn="idle",q.turnCount+=1;break;case"session_ended":q.status="ended",q.durationMs=z.durationMs;break}q.transcript=A.getTranscript(),yield z}}}export{G as VoiceSessionManager};
@@ -1 +0,0 @@
1
- var e=!0;export{e as conversationalDocblock};
@@ -1 +0,0 @@
1
- var t=!0;export{t as sttDocblock};
@@ -1 +0,0 @@
1
- var e=!0;export{e as syncDocblock};
@@ -1 +0,0 @@
1
- var e=!0;export{e as ttsDocblock};
@@ -1 +0,0 @@
1
- var i=!0;export{i as voiceDocblock};
@@ -1,3 +0,0 @@
1
- import{defineTranslation as g}from"@contractspec/lib.contracts-spec/translations";var j=g({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"All user-facing, LLM-facing, and developer-facing strings for the voice package",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"prompt.tts.system":{value:`You are a voice narration script writer.
2
- Analyze the content and produce a narration script with pacing directives.
3
- Return JSON with segments, each having sceneId, text, and contentType.`,description:"TTS system prompt for LLM-enhanced script generation"},"prompt.pace.sceneMatched":{value:"Match voice pacing to scene durations. Adjust rate and emphasis per segment to fit the video timeline.",description:"Prompt for scene-matched pacing strategy"},"prompt.emphasis.system":{value:"You are a voice director. For each segment, determine the optimal emphasis, tone, and speaking rate.",description:"Emphasis planner LLM system prompt"},"pace.intro.description":{value:"Authoritative opening at a measured pace",description:"Description for intro pacing"},"pace.problem.description":{value:"Urgent emphasis on the challenge",description:"Description for problem pacing"},"pace.solution.description":{value:"Calm, clear delivery of the solution",description:"Description for solution pacing"},"pace.metric.description":{value:"Excited emphasis on key results",description:"Description for metric pacing"},"pace.cta.description":{value:"Authoritative call to action",description:"Description for CTA pacing"},"pace.transition.description":{value:"Quick neutral transition",description:"Description for transition pacing"},"stt.transcribing":{value:"Transcribing audio...",description:"Status message during transcription"},"stt.diarization.speaker":{value:"Speaker {index}",description:"Default speaker label",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Subtitle timestamp format",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Voice session started",description:"Session start notification"},"conv.turn.user":{value:"User is speaking",description:"User turn indicator"},"conv.turn.agent":{value:"Agent is responding",description:"Agent turn indicator"},"conv.session.ended":{value:"Voice session ended. Duration: {durationMs}ms",description:"Session end notification",placeholders:[{name:"durationMs",type:"number"}]}}});export{j as enMessages};
@@ -1,3 +0,0 @@
1
- import{defineTranslation as g}from"@contractspec/lib.contracts-spec/translations";var j=g({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"Spanish translations for the voice package",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"prompt.tts.system":{value:`Eres un escritor de guiones de narracion vocal.
2
- Analiza el contenido y produce un guion de narracion con directivas de ritmo.
3
- Devuelve JSON con segmentos, cada uno con sceneId, text y contentType.`,description:"Prompt del sistema TTS para generacion de guiones mejorada con LLM"},"prompt.pace.sceneMatched":{value:"Ajusta el ritmo vocal a la duracion de las escenas. Modifica la velocidad y el enfasis por segmento para coincidir con la linea de tiempo del video.",description:"Prompt para la estrategia de ritmo sincronizado con escenas"},"prompt.emphasis.system":{value:"Eres un director vocal. Para cada segmento, determina el enfasis, tono y velocidad optimos.",description:"Prompt del sistema LLM para el planificador de enfasis"},"pace.intro.description":{value:"Apertura autoritaria a un ritmo medido",description:"Descripcion del ritmo de introduccion"},"pace.problem.description":{value:"Enfasis urgente en el desafio",description:"Descripcion del ritmo de problema"},"pace.solution.description":{value:"Entrega calmada y clara de la solucion",description:"Descripcion del ritmo de solucion"},"pace.metric.description":{value:"Enfasis entusiasta en los resultados clave",description:"Descripcion del ritmo de metrica"},"pace.cta.description":{value:"Llamada a la accion autoritaria",description:"Descripcion del ritmo CTA"},"pace.transition.description":{value:"Transicion rapida y neutral",description:"Descripcion del ritmo de transicion"},"stt.transcribing":{value:"Transcribiendo audio...",description:"Mensaje de estado durante la transcripcion"},"stt.diarization.speaker":{value:"Hablante {index}",description:"Etiqueta de hablante predeterminada",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Formato de marca de tiempo para subtitulos",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Sesion de voz iniciada",description:"Notificacion de inicio de sesion"},"conv.turn.user":{value:"El usuario esta hablando",description:"Indicador de turno del usuario"},"conv.turn.agent":{value:"El agente esta respondiendo",description:"Indicador de turno del agente"},"conv.session.ended":{value:"Sesion de voz finalizada. Duracion: {durationMs}ms",description:"Notificacion de fin de sesion",placeholders:[{name:"durationMs",type:"number"}]}}});export{j as esMessages};
@@ -1,3 +0,0 @@
1
- import{defineTranslation as g}from"@contractspec/lib.contracts-spec/translations";var j=g({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"French translations for the voice package",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"prompt.tts.system":{value:`Vous etes un redacteur de scripts de narration vocale.
2
- Analysez le contenu et produisez un script de narration avec des directives de rythme.
3
- Retournez du JSON avec des segments, chacun ayant sceneId, text et contentType.`,description:"Prompt systeme TTS pour la generation de scripts amelioree par LLM"},"prompt.pace.sceneMatched":{value:"Adaptez le rythme vocal aux durees des scenes. Ajustez le debit et l'emphase par segment pour correspondre a la timeline video.",description:"Prompt pour la strategie de rythme alignee sur les scenes"},"prompt.emphasis.system":{value:"Vous etes un directeur vocal. Pour chaque segment, determinez l'emphase, le ton et le debit optimaux.",description:"Prompt systeme LLM pour le planificateur d'emphase"},"pace.intro.description":{value:"Ouverture autoritaire a un rythme mesure",description:"Description du rythme d'introduction"},"pace.problem.description":{value:"Emphase urgente sur le defi",description:"Description du rythme de probleme"},"pace.solution.description":{value:"Livraison calme et claire de la solution",description:"Description du rythme de solution"},"pace.metric.description":{value:"Emphase enthousiaste sur les resultats cles",description:"Description du rythme de metrique"},"pace.cta.description":{value:"Appel a l'action autoritaire",description:"Description du rythme CTA"},"pace.transition.description":{value:"Transition rapide et neutre",description:"Description du rythme de transition"},"stt.transcribing":{value:"Transcription en cours...",description:"Message de statut pendant la transcription"},"stt.diarization.speaker":{value:"Locuteur {index}",description:"Label de locuteur par defaut",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Format de timestamp pour les sous-titres",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Session vocale demarree",description:"Notification de debut de session"},"conv.turn.user":{value:"L'utilisateur parle",description:"Indicateur de tour de l'utilisateur"},"conv.turn.agent":{value:"L'agent repond",description:"Indicateur de tour de l'agent"},"conv.session.ended":{value:"Session vocale terminee. Duree : {durationMs}ms",description:"Notification de fin de session",placeholders:[{name:"durationMs",type:"number"}]}}});export{j as frMessages};
@@ -1,7 +0,0 @@
1
- import{defineTranslation as g}from"@contractspec/lib.contracts-spec/translations";var m=g({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"All user-facing, LLM-facing, and developer-facing strings for the voice package",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"prompt.tts.system":{value:`You are a voice narration script writer.
2
- Analyze the content and produce a narration script with pacing directives.
3
- Return JSON with segments, each having sceneId, text, and contentType.`,description:"TTS system prompt for LLM-enhanced script generation"},"prompt.pace.sceneMatched":{value:"Match voice pacing to scene durations. Adjust rate and emphasis per segment to fit the video timeline.",description:"Prompt for scene-matched pacing strategy"},"prompt.emphasis.system":{value:"You are a voice director. For each segment, determine the optimal emphasis, tone, and speaking rate.",description:"Emphasis planner LLM system prompt"},"pace.intro.description":{value:"Authoritative opening at a measured pace",description:"Description for intro pacing"},"pace.problem.description":{value:"Urgent emphasis on the challenge",description:"Description for problem pacing"},"pace.solution.description":{value:"Calm, clear delivery of the solution",description:"Description for solution pacing"},"pace.metric.description":{value:"Excited emphasis on key results",description:"Description for metric pacing"},"pace.cta.description":{value:"Authoritative call to action",description:"Description for CTA pacing"},"pace.transition.description":{value:"Quick neutral transition",description:"Description for transition pacing"},"stt.transcribing":{value:"Transcribing audio...",description:"Status message during transcription"},"stt.diarization.speaker":{value:"Speaker {index}",description:"Default speaker label",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Subtitle timestamp format",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Voice session started",description:"Session start notification"},"conv.turn.user":{value:"User is speaking",description:"User turn indicator"},"conv.turn.agent":{value:"Agent is responding",description:"Agent turn indicator"},"conv.session.ended":{value:"Voice session ended. Duration: {durationMs}ms",description:"Session end notification",placeholders:[{name:"durationMs",type:"number"}]}}});import{defineTranslation as x}from"@contractspec/lib.contracts-spec/translations";var M=x({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"Spanish translations for the voice package",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"prompt.tts.system":{value:`Eres un escritor de guiones de narracion vocal.
4
- Analiza el contenido y produce un guion de narracion con directivas de ritmo.
5
- Devuelve JSON con segmentos, cada uno con sceneId, text y contentType.`,description:"Prompt del sistema TTS para generacion de guiones mejorada con LLM"},"prompt.pace.sceneMatched":{value:"Ajusta el ritmo vocal a la duracion de las escenas. Modifica la velocidad y el enfasis por segmento para coincidir con la linea de tiempo del video.",description:"Prompt para la estrategia de ritmo sincronizado con escenas"},"prompt.emphasis.system":{value:"Eres un director vocal. Para cada segmento, determina el enfasis, tono y velocidad optimos.",description:"Prompt del sistema LLM para el planificador de enfasis"},"pace.intro.description":{value:"Apertura autoritaria a un ritmo medido",description:"Descripcion del ritmo de introduccion"},"pace.problem.description":{value:"Enfasis urgente en el desafio",description:"Descripcion del ritmo de problema"},"pace.solution.description":{value:"Entrega calmada y clara de la solucion",description:"Descripcion del ritmo de solucion"},"pace.metric.description":{value:"Enfasis entusiasta en los resultados clave",description:"Descripcion del ritmo de metrica"},"pace.cta.description":{value:"Llamada a la accion autoritaria",description:"Descripcion del ritmo CTA"},"pace.transition.description":{value:"Transicion rapida y neutral",description:"Descripcion del ritmo de transicion"},"stt.transcribing":{value:"Transcribiendo audio...",description:"Mensaje de estado durante la transcripcion"},"stt.diarization.speaker":{value:"Hablante {index}",description:"Etiqueta de hablante predeterminada",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Formato de marca de tiempo para subtitulos",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Sesion de voz iniciada",description:"Notificacion de inicio de sesion"},"conv.turn.user":{value:"El usuario esta hablando",description:"Indicador de turno del usuario"},"conv.turn.agent":{value:"El agente esta respondiendo",description:"Indicador de turno del agente"},"conv.session.ended":{value:"Sesion de voz finalizada. Duracion: {durationMs}ms",description:"Notificacion de fin de sesion",placeholders:[{name:"durationMs",type:"number"}]}}});import{defineTranslation as f}from"@contractspec/lib.contracts-spec/translations";var h=f({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"French translations for the voice package",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"prompt.tts.system":{value:`Vous etes un redacteur de scripts de narration vocale.
6
- Analysez le contenu et produisez un script de narration avec des directives de rythme.
7
- Retournez du JSON avec des segments, chacun ayant sceneId, text et contentType.`,description:"Prompt systeme TTS pour la generation de scripts amelioree par LLM"},"prompt.pace.sceneMatched":{value:"Adaptez le rythme vocal aux durees des scenes. Ajustez le debit et l'emphase par segment pour correspondre a la timeline video.",description:"Prompt pour la strategie de rythme alignee sur les scenes"},"prompt.emphasis.system":{value:"Vous etes un directeur vocal. Pour chaque segment, determinez l'emphase, le ton et le debit optimaux.",description:"Prompt systeme LLM pour le planificateur d'emphase"},"pace.intro.description":{value:"Ouverture autoritaire a un rythme mesure",description:"Description du rythme d'introduction"},"pace.problem.description":{value:"Emphase urgente sur le defi",description:"Description du rythme de probleme"},"pace.solution.description":{value:"Livraison calme et claire de la solution",description:"Description du rythme de solution"},"pace.metric.description":{value:"Emphase enthousiaste sur les resultats cles",description:"Description du rythme de metrique"},"pace.cta.description":{value:"Appel a l'action autoritaire",description:"Description du rythme CTA"},"pace.transition.description":{value:"Transition rapide et neutre",description:"Description du rythme de transition"},"stt.transcribing":{value:"Transcription en cours...",description:"Message de statut pendant la transcription"},"stt.diarization.speaker":{value:"Locuteur {index}",description:"Label de locuteur par defaut",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Format de timestamp pour les sous-titres",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Session vocale demarree",description:"Notification de debut de session"},"conv.turn.user":{value:"L'utilisateur parle",description:"Indicateur de tour de l'utilisateur"},"conv.turn.agent":{value:"L'agent repond",description:"Indicateur de tour de l'agent"},"conv.session.ended":{value:"Session vocale terminee. Duree : {durationMs}ms",description:"Notification de fin de session",placeholders:[{name:"durationMs",type:"number"}]}}});export{h as frMessages,M as esMessages,m as enMessages};
@@ -1,7 +0,0 @@
1
- import{defineTranslation as S}from"@contractspec/lib.contracts-spec/translations";var e=S({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"All user-facing, LLM-facing, and developer-facing strings for the voice package",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"prompt.tts.system":{value:`You are a voice narration script writer.
2
- Analyze the content and produce a narration script with pacing directives.
3
- Return JSON with segments, each having sceneId, text, and contentType.`,description:"TTS system prompt for LLM-enhanced script generation"},"prompt.pace.sceneMatched":{value:"Match voice pacing to scene durations. Adjust rate and emphasis per segment to fit the video timeline.",description:"Prompt for scene-matched pacing strategy"},"prompt.emphasis.system":{value:"You are a voice director. For each segment, determine the optimal emphasis, tone, and speaking rate.",description:"Emphasis planner LLM system prompt"},"pace.intro.description":{value:"Authoritative opening at a measured pace",description:"Description for intro pacing"},"pace.problem.description":{value:"Urgent emphasis on the challenge",description:"Description for problem pacing"},"pace.solution.description":{value:"Calm, clear delivery of the solution",description:"Description for solution pacing"},"pace.metric.description":{value:"Excited emphasis on key results",description:"Description for metric pacing"},"pace.cta.description":{value:"Authoritative call to action",description:"Description for CTA pacing"},"pace.transition.description":{value:"Quick neutral transition",description:"Description for transition pacing"},"stt.transcribing":{value:"Transcribing audio...",description:"Status message during transcription"},"stt.diarization.speaker":{value:"Speaker {index}",description:"Default speaker label",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Subtitle timestamp format",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Voice session started",description:"Session start notification"},"conv.turn.user":{value:"User is speaking",description:"User turn indicator"},"conv.turn.agent":{value:"Agent is responding",description:"Agent turn indicator"},"conv.session.ended":{value:"Voice session ended. Duration: {durationMs}ms",description:"Session end notification",placeholders:[{name:"durationMs",type:"number"}]}}});import{defineTranslation as f}from"@contractspec/lib.contracts-spec/translations";var o=f({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"Spanish translations for the voice package",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"prompt.tts.system":{value:`Eres un escritor de guiones de narracion vocal.
4
- Analiza el contenido y produce un guion de narracion con directivas de ritmo.
5
- Devuelve JSON con segmentos, cada uno con sceneId, text y contentType.`,description:"Prompt del sistema TTS para generacion de guiones mejorada con LLM"},"prompt.pace.sceneMatched":{value:"Ajusta el ritmo vocal a la duracion de las escenas. Modifica la velocidad y el enfasis por segmento para coincidir con la linea de tiempo del video.",description:"Prompt para la estrategia de ritmo sincronizado con escenas"},"prompt.emphasis.system":{value:"Eres un director vocal. Para cada segmento, determina el enfasis, tono y velocidad optimos.",description:"Prompt del sistema LLM para el planificador de enfasis"},"pace.intro.description":{value:"Apertura autoritaria a un ritmo medido",description:"Descripcion del ritmo de introduccion"},"pace.problem.description":{value:"Enfasis urgente en el desafio",description:"Descripcion del ritmo de problema"},"pace.solution.description":{value:"Entrega calmada y clara de la solucion",description:"Descripcion del ritmo de solucion"},"pace.metric.description":{value:"Enfasis entusiasta en los resultados clave",description:"Descripcion del ritmo de metrica"},"pace.cta.description":{value:"Llamada a la accion autoritaria",description:"Descripcion del ritmo CTA"},"pace.transition.description":{value:"Transicion rapida y neutral",description:"Descripcion del ritmo de transicion"},"stt.transcribing":{value:"Transcribiendo audio...",description:"Mensaje de estado durante la transcripcion"},"stt.diarization.speaker":{value:"Hablante {index}",description:"Etiqueta de hablante predeterminada",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Formato de marca de tiempo para subtitulos",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Sesion de voz iniciada",description:"Notificacion de inicio de sesion"},"conv.turn.user":{value:"El usuario esta hablando",description:"Indicador de turno del usuario"},"conv.turn.agent":{value:"El agente esta respondiendo",description:"Indicador de turno del agente"},"conv.session.ended":{value:"Sesion de voz finalizada. Duracion: {durationMs}ms",description:"Notificacion de fin de sesion",placeholders:[{name:"durationMs",type:"number"}]}}});import{defineTranslation as E}from"@contractspec/lib.contracts-spec/translations";var m=E({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"French translations for the voice package",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"prompt.tts.system":{value:`Vous etes un redacteur de scripts de narration vocale.
6
- Analysez le contenu et produisez un script de narration avec des directives de rythme.
7
- Retournez du JSON avec des segments, chacun ayant sceneId, text et contentType.`,description:"Prompt systeme TTS pour la generation de scripts amelioree par LLM"},"prompt.pace.sceneMatched":{value:"Adaptez le rythme vocal aux durees des scenes. Ajustez le debit et l'emphase par segment pour correspondre a la timeline video.",description:"Prompt pour la strategie de rythme alignee sur les scenes"},"prompt.emphasis.system":{value:"Vous etes un directeur vocal. Pour chaque segment, determinez l'emphase, le ton et le debit optimaux.",description:"Prompt systeme LLM pour le planificateur d'emphase"},"pace.intro.description":{value:"Ouverture autoritaire a un rythme mesure",description:"Description du rythme d'introduction"},"pace.problem.description":{value:"Emphase urgente sur le defi",description:"Description du rythme de probleme"},"pace.solution.description":{value:"Livraison calme et claire de la solution",description:"Description du rythme de solution"},"pace.metric.description":{value:"Emphase enthousiaste sur les resultats cles",description:"Description du rythme de metrique"},"pace.cta.description":{value:"Appel a l'action autoritaire",description:"Description du rythme CTA"},"pace.transition.description":{value:"Transition rapide et neutre",description:"Description du rythme de transition"},"stt.transcribing":{value:"Transcription en cours...",description:"Message de statut pendant la transcription"},"stt.diarization.speaker":{value:"Locuteur {index}",description:"Label de locuteur par defaut",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Format de timestamp pour les sous-titres",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Session vocale demarree",description:"Notification de debut de session"},"conv.turn.user":{value:"L'utilisateur parle",description:"Indicateur de tour de l'utilisateur"},"conv.turn.agent":{value:"L'agent repond",description:"Indicateur de tour de l'agent"},"conv.session.ended":{value:"Session vocale terminee. Duree : {durationMs}ms",description:"Notification de fin de session",placeholders:[{name:"durationMs",type:"number"}]}}});var x={"prompt.tts.system":"prompt.tts.system","prompt.pace.sceneMatched":"prompt.pace.sceneMatched","prompt.emphasis.system":"prompt.emphasis.system"},p={"pace.intro.description":"pace.intro.description","pace.problem.description":"pace.problem.description","pace.solution.description":"pace.solution.description","pace.metric.description":"pace.metric.description","pace.cta.description":"pace.cta.description","pace.transition.description":"pace.transition.description"},L={"stt.transcribing":"stt.transcribing","stt.diarization.speaker":"stt.diarization.speaker","stt.subtitle.timestamp":"stt.subtitle.timestamp"},I={"conv.session.started":"conv.session.started","conv.turn.user":"conv.turn.user","conv.turn.agent":"conv.turn.agent","conv.session.ended":"conv.session.ended"},V={...x,...p,...L,...I};import{DEFAULT_LOCALE as u,isSupportedLocale as v,resolveLocale as A,SUPPORTED_LOCALES as C}from"@contractspec/lib.contracts-spec/translations";import{createI18nFactory as D}from"@contractspec/lib.contracts-spec/translations";var r=D({specKey:"voice.messages",catalogs:[e,m,o]}),O=r.create,_=r.getDefault,g=r.resetRegistry;export{A as resolveLocale,g as resetI18nRegistry,v as isSupportedLocale,_ as getDefaultI18n,m as frMessages,o as esMessages,e as enMessages,O as createVoiceI18n,x as TTS_PROMPT_KEYS,p as TTS_PACE_KEYS,C as SUPPORTED_LOCALES,L as STT_KEYS,V as I18N_KEYS,u as DEFAULT_LOCALE,I as CONVERSATIONAL_KEYS};
@@ -1 +0,0 @@
1
- var b={"prompt.tts.system":"prompt.tts.system","prompt.pace.sceneMatched":"prompt.pace.sceneMatched","prompt.emphasis.system":"prompt.emphasis.system"},g={"pace.intro.description":"pace.intro.description","pace.problem.description":"pace.problem.description","pace.solution.description":"pace.solution.description","pace.metric.description":"pace.metric.description","pace.cta.description":"pace.cta.description","pace.transition.description":"pace.transition.description"},h={"stt.transcribing":"stt.transcribing","stt.diarization.speaker":"stt.diarization.speaker","stt.subtitle.timestamp":"stt.subtitle.timestamp"},j={"conv.session.started":"conv.session.started","conv.turn.user":"conv.turn.user","conv.turn.agent":"conv.turn.agent","conv.session.ended":"conv.session.ended"},m={...b,...g,...h,...j};export{b as TTS_PROMPT_KEYS,g as TTS_PACE_KEYS,h as STT_KEYS,m as I18N_KEYS,j as CONVERSATIONAL_KEYS};
@@ -1 +0,0 @@
1
- import{DEFAULT_LOCALE as g,isSupportedLocale as h,resolveLocale as j,SUPPORTED_LOCALES as k}from"@contractspec/lib.contracts-spec/translations";export{j as resolveLocale,h as isSupportedLocale,k as SUPPORTED_LOCALES,g as DEFAULT_LOCALE};
@@ -1,7 +0,0 @@
1
- import{defineTranslation as q}from"@contractspec/lib.contracts-spec/translations";var j=q({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"All user-facing, LLM-facing, and developer-facing strings for the voice package",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"prompt.tts.system":{value:`You are a voice narration script writer.
2
- Analyze the content and produce a narration script with pacing directives.
3
- Return JSON with segments, each having sceneId, text, and contentType.`,description:"TTS system prompt for LLM-enhanced script generation"},"prompt.pace.sceneMatched":{value:"Match voice pacing to scene durations. Adjust rate and emphasis per segment to fit the video timeline.",description:"Prompt for scene-matched pacing strategy"},"prompt.emphasis.system":{value:"You are a voice director. For each segment, determine the optimal emphasis, tone, and speaking rate.",description:"Emphasis planner LLM system prompt"},"pace.intro.description":{value:"Authoritative opening at a measured pace",description:"Description for intro pacing"},"pace.problem.description":{value:"Urgent emphasis on the challenge",description:"Description for problem pacing"},"pace.solution.description":{value:"Calm, clear delivery of the solution",description:"Description for solution pacing"},"pace.metric.description":{value:"Excited emphasis on key results",description:"Description for metric pacing"},"pace.cta.description":{value:"Authoritative call to action",description:"Description for CTA pacing"},"pace.transition.description":{value:"Quick neutral transition",description:"Description for transition pacing"},"stt.transcribing":{value:"Transcribing audio...",description:"Status message during transcription"},"stt.diarization.speaker":{value:"Speaker {index}",description:"Default speaker label",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Subtitle timestamp format",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Voice session started",description:"Session start notification"},"conv.turn.user":{value:"User is speaking",description:"User turn indicator"},"conv.turn.agent":{value:"Agent is responding",description:"Agent turn indicator"},"conv.session.ended":{value:"Voice session ended. Duration: {durationMs}ms",description:"Session end notification",placeholders:[{name:"durationMs",type:"number"}]}}});import{defineTranslation as u}from"@contractspec/lib.contracts-spec/translations";var k=u({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"Spanish translations for the voice package",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"prompt.tts.system":{value:`Eres un escritor de guiones de narracion vocal.
4
- Analiza el contenido y produce un guion de narracion con directivas de ritmo.
5
- Devuelve JSON con segmentos, cada uno con sceneId, text y contentType.`,description:"Prompt del sistema TTS para generacion de guiones mejorada con LLM"},"prompt.pace.sceneMatched":{value:"Ajusta el ritmo vocal a la duracion de las escenas. Modifica la velocidad y el enfasis por segmento para coincidir con la linea de tiempo del video.",description:"Prompt para la estrategia de ritmo sincronizado con escenas"},"prompt.emphasis.system":{value:"Eres un director vocal. Para cada segmento, determina el enfasis, tono y velocidad optimos.",description:"Prompt del sistema LLM para el planificador de enfasis"},"pace.intro.description":{value:"Apertura autoritaria a un ritmo medido",description:"Descripcion del ritmo de introduccion"},"pace.problem.description":{value:"Enfasis urgente en el desafio",description:"Descripcion del ritmo de problema"},"pace.solution.description":{value:"Entrega calmada y clara de la solucion",description:"Descripcion del ritmo de solucion"},"pace.metric.description":{value:"Enfasis entusiasta en los resultados clave",description:"Descripcion del ritmo de metrica"},"pace.cta.description":{value:"Llamada a la accion autoritaria",description:"Descripcion del ritmo CTA"},"pace.transition.description":{value:"Transicion rapida y neutral",description:"Descripcion del ritmo de transicion"},"stt.transcribing":{value:"Transcribiendo audio...",description:"Mensaje de estado durante la transcripcion"},"stt.diarization.speaker":{value:"Hablante {index}",description:"Etiqueta de hablante predeterminada",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Formato de marca de tiempo para subtitulos",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Sesion de voz iniciada",description:"Notificacion de inicio de sesion"},"conv.turn.user":{value:"El usuario esta hablando",description:"Indicador de turno del usuario"},"conv.turn.agent":{value:"El agente esta respondiendo",description:"Indicador de turno del agente"},"conv.session.ended":{value:"Sesion de voz finalizada. Duracion: {durationMs}ms",description:"Notificacion de fin de sesion",placeholders:[{name:"durationMs",type:"number"}]}}});import{defineTranslation as v}from"@contractspec/lib.contracts-spec/translations";var m=v({meta:{key:"voice.messages",version:"1.0.0",domain:"voice",description:"French translations for the voice package",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"prompt.tts.system":{value:`Vous etes un redacteur de scripts de narration vocale.
6
- Analysez le contenu et produisez un script de narration avec des directives de rythme.
7
- Retournez du JSON avec des segments, chacun ayant sceneId, text et contentType.`,description:"Prompt systeme TTS pour la generation de scripts amelioree par LLM"},"prompt.pace.sceneMatched":{value:"Adaptez le rythme vocal aux durees des scenes. Ajustez le debit et l'emphase par segment pour correspondre a la timeline video.",description:"Prompt pour la strategie de rythme alignee sur les scenes"},"prompt.emphasis.system":{value:"Vous etes un directeur vocal. Pour chaque segment, determinez l'emphase, le ton et le debit optimaux.",description:"Prompt systeme LLM pour le planificateur d'emphase"},"pace.intro.description":{value:"Ouverture autoritaire a un rythme mesure",description:"Description du rythme d'introduction"},"pace.problem.description":{value:"Emphase urgente sur le defi",description:"Description du rythme de probleme"},"pace.solution.description":{value:"Livraison calme et claire de la solution",description:"Description du rythme de solution"},"pace.metric.description":{value:"Emphase enthousiaste sur les resultats cles",description:"Description du rythme de metrique"},"pace.cta.description":{value:"Appel a l'action autoritaire",description:"Description du rythme CTA"},"pace.transition.description":{value:"Transition rapide et neutre",description:"Description du rythme de transition"},"stt.transcribing":{value:"Transcription en cours...",description:"Message de statut pendant la transcription"},"stt.diarization.speaker":{value:"Locuteur {index}",description:"Label de locuteur par defaut",placeholders:[{name:"index",type:"number"}]},"stt.subtitle.timestamp":{value:"{start} --> {end}",description:"Format de timestamp pour les sous-titres",placeholders:[{name:"start",type:"string"},{name:"end",type:"string"}]},"conv.session.started":{value:"Session vocale demarree",description:"Notification de debut de session"},"conv.turn.user":{value:"L'utilisateur parle",description:"Indicateur de tour de l'utilisateur"},"conv.turn.agent":{value:"L'agent repond",description:"Indicateur de tour de l'agent"},"conv.session.ended":{value:"Session vocale terminee. Duree : {durationMs}ms",description:"Notification de fin de session",placeholders:[{name:"durationMs",type:"number"}]}}});import{createI18nFactory as w}from"@contractspec/lib.contracts-spec/translations";var h=w({specKey:"voice.messages",catalogs:[j,m,k]}),K=h.create,L=h.getDefault,N=h.resetRegistry;export{N as resetI18nRegistry,L as getDefaultI18n,K as createVoiceI18n};
@@ -1,13 +0,0 @@
1
- class A{concatenate(q){if(q.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[J]=q;if(!J)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};if(q.length===1)return{...J};let{format:U,sampleRateHz:K}=J,X=J.channels??1;for(let V of q){if(V.format!==U)throw Error(`Format mismatch: expected ${U}, got ${V.format}`);if(V.sampleRateHz!==K)throw Error(`Sample rate mismatch: expected ${K}, got ${V.sampleRateHz}`)}let Q=q.reduce((V,L)=>V+L.data.length,0),Z=new Uint8Array(Q),$=0;for(let V of q)Z.set(V.data,$),$+=V.data.length;let Y=q.reduce((V,L)=>V+(L.durationMs??0),0);return{data:Z,format:U,sampleRateHz:K,durationMs:Y,channels:X}}}class G{static DEFAULT_WPM=150;estimateSeconds(q,J){let U=J??G.DEFAULT_WPM,K=q.split(/\s+/).filter(Boolean).length;return Math.ceil(K/U*60)}estimateMs(q,J){let U=J??G.DEFAULT_WPM,K=q.split(/\s+/).filter(Boolean).length;return Math.ceil(K/U*60*1000)}estimateWordCount(q,J){let U=J??G.DEFAULT_WPM;return Math.round(q/60*U)}}class P{convert(q,J){if(q.format===J)return q;return{...q,format:J}}isSupported(q,J){if(q===J)return!0;return{wav:["mp3","ogg","pcm","opus"],mp3:["wav"],ogg:["wav"],pcm:["wav"],opus:["wav"]}[q]?.includes(J)??!1}}class E{generate(q,J="wav",U=44100,K=1){let X=Math.ceil(U*q/1000),Q=2,Z=X*2*K;return{data:new Uint8Array(Z),format:J,sampleRateHz:U,durationMs:q,channels:K}}}class O{stt;llm;tts;modelSelector;selectionContext;conversationHistory=[];constructor(q,J,U){if("stt"in q)this.stt=q.stt,this.llm=q.llm,this.tts=q.tts,this.modelSelector=q.modelSelector,this.selectionContext=q.selectionContext;else{if(!J||!U)throw Error("ResponseOrchestrator requires llm and tts when constructed with positional arguments");this.stt=q,this.llm=J,this.tts=U}}async*processUserTurn(q,J){let K=(await this.stt.transcribe({audio:q,language:J.language,wordTimestamps:!1})).text;yield{type:"user_speech_ended",transcript:K},yield{type:"transcript",role:"user",text:K,timestamp:Date.now()},this.conversationHistory.push({role:"user",content:K});let X=await this.resolveModel(J.llmModel),Z=(await this.llm.chat([{role:"system",content:[{type:"text",text:J.systemPrompt}]},...this.conversationHistory.map((V)=>({role:V.role,content:[{type:"text",text:V.content}]}))],{model:X})).message.content.find((V)=>V.type==="text"),$=Z&&Z.type==="text"?Z.text:"I apologize, I could not generate a response.";this.conversationHistory.push({role:"assistant",content:$}),yield{type:"agent_speech_started",text:$},yield{type:"agent_audio",audio:(await this.tts.synthesize({text:$,voiceId:J.voiceId,language:J.language,format:J.outputFormat})).audio.data},yield{type:"agent_speech_ended"},yield{type:"transcript",role:"agent",text:$,timestamp:Date.now()}}async resolveModel(q){if(q)return q;if(this.modelSelector){let J=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(J)).modelId}return}reset(){this.conversationHistory.length=0}}class I{turns=[];currentTurn=null;sessionStartMs=Date.now();getTranscript(){return[...this.turns]}toText(){return this.turns.map((q)=>`[${q.role}] ${q.text}`).join(`
2
- `)}getTurnCount(){return this.turns.length}processEvent(q){switch(q.type){case"session_started":this.sessionStartMs=Date.now();break;case"user_speech_started":this.currentTurn={role:"user",startMs:Date.now()-this.sessionStartMs};break;case"user_speech_ended":if(this.currentTurn&&this.currentTurn.role==="user")this.currentTurn.text=q.transcript,this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"agent_speech_started":this.currentTurn={role:"agent",text:q.text,startMs:Date.now()-this.sessionStartMs};break;case"agent_speech_ended":if(this.currentTurn&&this.currentTurn.role==="agent")this.currentTurn.endMs=Date.now()-this.sessionStartMs,this.turns.push(this.currentTurn),this.currentTurn=null;break;case"transcript":break;default:break}}reset(){this.turns.length=0,this.currentTurn=null,this.sessionStartMs=Date.now()}}class k{silenceThresholdMs;energyThreshold;silenceStartMs=null;constructor(q=800,J=0.01){this.silenceThresholdMs=q,this.energyThreshold=J}processChunk(q,J){if(this.calculateEnergy(q)>this.energyThreshold)return this.silenceStartMs=null,!1;if(this.silenceStartMs===null)this.silenceStartMs=J;return J-this.silenceStartMs>=this.silenceThresholdMs}reset(){this.silenceStartMs=null}calculateEnergy(q){if(q.length<2)return 0;let J=0,U=Math.floor(q.length/2);for(let K=0;K<q.length-1;K+=2){let X=q[K]??0,Q=q[K+1]??0,$=((X|Q<<8)<<16>>16)/32768;J+=$*$}return Math.sqrt(J/U)}}class D{provider;constructor(q){this.provider=q.conversational}async startSession(q){let J=new I,U=await this.provider.startSession({voiceId:q.voiceId,language:q.language,systemPrompt:q.systemPrompt,llmModel:q.llmModel,inputFormat:q.inputFormat,outputFormat:q.outputFormat,turnDetection:q.turnDetection,silenceThresholdMs:q.silenceThresholdMs,maxDurationSeconds:q.maxDurationSeconds}),K={sessionId:"",status:"connecting",currentTurn:"idle",turnCount:0,durationMs:0,transcript:[]},X=this.wrapEvents(U.events,K,J);return{state:K,sendAudio:(Q)=>U.sendAudio(Q),sendText:(Q)=>U.sendText(Q),interrupt:()=>U.interrupt(),close:async()=>{let Q=await U.close();return K.status="ended",Q},events:X}}async*wrapEvents(q,J,U){for await(let K of q){switch(U.processEvent(K),K.type){case"session_started":J.sessionId=K.sessionId,J.status="active";break;case"user_speech_started":J.currentTurn="user";break;case"user_speech_ended":J.currentTurn="idle",J.turnCount+=1;break;case"agent_speech_started":J.currentTurn="agent";break;case"agent_speech_ended":J.currentTurn="idle",J.turnCount+=1;break;case"session_ended":J.status="ended",J.durationMs=K.durationMs;break}J.transcript=U.getTranscript(),yield K}}}class T{map(q,J="Speaker"){let U=[],K=new Map;for(let Z of q)if(Z.speakerId&&!U.includes(Z.speakerId))U.push(Z.speakerId),K.set(Z.speakerId,{segmentCount:0,totalSpeakingMs:0});let X=q.map((Z)=>{if(!Z.speakerId)return Z;let $=U.indexOf(Z.speakerId),Y=`${J} ${$+1}`,V=K.get(Z.speakerId);if(!V)return{...Z,speakerLabel:Y};return V.segmentCount+=1,V.totalSpeakingMs+=Z.endMs-Z.startMs,{...Z,speakerLabel:Y}}),Q=U.map((Z,$)=>{let Y=K.get(Z);return{id:Z,label:`${J} ${$+1}`,segmentCount:Y?.segmentCount??0,totalSpeakingMs:Y?.totalSpeakingMs??0}});return{segments:X,speakers:Q}}}class C{static DEFAULT_MAX_CHUNK_MS=300000;split(q,J=C.DEFAULT_MAX_CHUNK_MS){let U=q.durationMs??this.estimateDurationMs(q);if(U<=J)return[q];let K=[],X=q.data.length/Math.max(U,1),Q=0;while(Q<U){let Z=Math.min(J,U-Q),$=Math.floor(Q*X),Y=Math.floor((Q+Z)*X);K.push({data:q.data.slice($,Y),format:q.format,sampleRateHz:q.sampleRateHz,durationMs:Z,channels:q.channels}),Q+=Z}return K}estimateDurationMs(q){let U=q.channels??1,K=q.data.length/(2*U);return Math.ceil(K/q.sampleRateHz*1000)}}class N{toSRT(q){return q.map((J,U)=>{let K=this.formatTimeSRT(J.startMs),X=this.formatTimeSRT(J.endMs),Q=J.speakerLabel?`[${J.speakerLabel}] `:"";return`${U+1}
3
- ${K} --> ${X}
4
- ${Q}${J.text}`}).join(`
5
-
6
- `)}toVTT(q){return`WEBVTT
7
-
8
- `+q.map((K,X)=>{let Q=this.formatTimeVTT(K.startMs),Z=this.formatTimeVTT(K.endMs),$=K.speakerLabel?`<v ${K.speakerLabel}>`:"";return`${X+1}
9
- ${Q} --> ${Z}
10
- ${$}${K.text}`}).join(`
11
-
12
- `)}formatTimeSRT(q){let J=Math.floor(q/3600000),U=Math.floor(q%3600000/60000),K=Math.floor(q%60000/1000),X=q%1000;return`${this.pad(J,2)}:${this.pad(U,2)}:${this.pad(K,2)},${this.pad(X,3)}`}formatTimeVTT(q){let J=Math.floor(q/3600000),U=Math.floor(q%3600000/60000),K=Math.floor(q%60000/1000),X=q%1000;return`${this.pad(J,2)}:${this.pad(U,2)}:${this.pad(K,2)}.${this.pad(X,3)}`}pad(q,J){return q.toString().padStart(J,"0")}}class x{stt;segmentSplitter=new C;diarizationMapper=new T;subtitleFormatter=new N;constructor(q){this.stt=q.stt}async transcribe(q){let J=b(),U=this.segmentSplitter.split(q.audio),K=[],X="",Q=0,Z=0;for(let W of U){let R=await this.stt.transcribe({audio:W,language:q.language,diarize:q.diarize,speakerCount:q.speakerCount,wordTimestamps:!0,vocabularyHints:q.vocabularyHints}),h=R.segments.map((H)=>({text:H.text,startMs:H.startMs+Z,endMs:H.endMs+Z,speakerId:H.speakerId,speakerName:H.speakerName,confidence:H.confidence}));K.push(...h),X+=(X?" ":"")+R.text,Q+=R.durationMs,Z+=W.durationMs??0}let $=K,Y;if(q.diarize){let W=this.diarizationMapper.map(K);$=W.segments,Y=W.speakers}let V={text:X,segments:$,language:q.language??"en",durationMs:Q},L,_=q.subtitleFormat??"none";if(_==="srt")L=this.subtitleFormatter.toSRT($);else if(_==="vtt")L=this.subtitleFormatter.toVTT($);return{id:J,transcript:V,subtitles:L,speakers:Y}}async*transcribeStream(q,J){if(!this.stt.transcribeStream)throw Error("Streaming transcription not supported by the current STT provider");let U=this.stt.transcribeStream(q,{language:J?.language,diarize:J?.diarize,speakerCount:J?.speakerCount,wordTimestamps:!0,vocabularyHints:J?.vocabularyHints});for await(let K of U)yield{text:K.text,startMs:K.startMs,endMs:K.endMs,speakerId:K.speakerId,speakerLabel:K.speakerName,confidence:K.confidence}}}function b(){let q=Date.now().toString(36),J=Math.random().toString(36).slice(2,8);return`stt_${q}_${J}`}class B{static UPPER_THRESHOLD=1.1;static LOWER_THRESHOLD=0.7;static MAX_RATE=1.3;static MIN_RATE=0.8;negotiate(q,J){let U=[],K=q.segments.map((X)=>{let Q=J.get(X.sceneId);if(Q===void 0)return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:X.recommendedSceneDurationInFrames,voiceDurationInFrames:X.durationInFrames,action:"no_change",finalSceneDurationInFrames:X.recommendedSceneDurationInFrames}),X;let Z=X.durationInFrames/Q;if(Z>B.UPPER_THRESHOLD){let $=Math.min(Z,B.MAX_RATE);return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:Q,voiceDurationInFrames:X.durationInFrames,action:Z>B.MAX_RATE?"extend_scene":"suggest_rate_change",suggestedRate:$,finalSceneDurationInFrames:X.recommendedSceneDurationInFrames}),X}if(Z<B.LOWER_THRESHOLD){let $=Math.max(Z,B.MIN_RATE);return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:Q,voiceDurationInFrames:X.durationInFrames,action:"pad_silence",suggestedRate:$,finalSceneDurationInFrames:Q}),{...X,recommendedSceneDurationInFrames:Q}}return U.push({sceneId:X.sceneId,originalSceneDurationInFrames:Q,voiceDurationInFrames:X.durationInFrames,action:"no_change",finalSceneDurationInFrames:X.recommendedSceneDurationInFrames}),X});return{timingMap:{...q,segments:K},adjustments:U}}}class S{durationEstimator=new G;adapt(q){let J=q.scenes.filter((Q)=>Q.narrationText&&Q.narrationText.trim().length>0),U=J.map((Q,Z)=>{let $=Q.narrationText??"";return{sceneId:Q.id,text:$,estimatedDurationSeconds:this.durationEstimator.estimateSeconds($),contentType:this.inferContentType(Z,J.length)}}),K=U.map((Q)=>Q.text).join(" "),X=U.reduce((Q,Z)=>Q+Z.estimatedDurationSeconds,0);return{fullText:K,segments:U,estimatedDurationSeconds:X}}inferContentType(q,J){if(q===0)return"intro";if(q===J-1)return"cta";if(q===1&&J>3)return"problem";if(q===J-2&&J>3)return"metric";return"solution"}}class w{calculate(q,J,U=1.15){let K=q.map((Q)=>{let Z=Math.ceil(Q.durationMs/1000*J),$=Math.ceil(Z*U),Y=Q.wordTimings?.map((V)=>({word:V.word,startMs:V.startMs,endMs:V.endMs}));return{sceneId:Q.sceneId,durationMs:Q.durationMs,durationInFrames:Z,recommendedSceneDurationInFrames:$,wordTimings:Y}});return{totalDurationMs:q.reduce((Q,Z)=>Q+Z.durationMs,0),segments:K,fps:J}}recalculateForFps(q,J){let U=q.segments.map((K)=>{let X=Math.ceil(K.durationMs/1000*J),Q=K.recommendedSceneDurationInFrames/Math.max(K.durationInFrames,1);return{...K,durationInFrames:X,recommendedSceneDurationInFrames:Math.ceil(X*Q)}});return{...q,segments:U,fps:J}}}class z{concatenator=new A;silenceGenerator=new E;assemble(q,J,U=500){if(q.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[K]=q;if(!K)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let X=new Map(J.map(($)=>[$.sceneId,$])),Q=K.audio,Z=[];for(let $=0;$<q.length;$++){let Y=q[$];if(!Y)continue;let V=X.get(Y.sceneId),L=V?.leadingSilenceMs??0;if(L>0)Z.push(this.silenceGenerator.generate(L,Q.format,Q.sampleRateHz,Q.channels??1));Z.push(Y.audio);let _=V?.trailingSilenceMs??($<q.length-1?U:0);if(_>0)Z.push(this.silenceGenerator.generate(_,Q.format,Q.sampleRateHz,Q.channels??1))}return this.concatenator.concatenate(Z)}}var v={intro:{rate:0.95,emphasis:"normal",tone:"authoritative",leadingSilenceMs:0,trailingSilenceMs:500},problem:{rate:0.9,emphasis:"strong",tone:"urgent",leadingSilenceMs:300,trailingSilenceMs:500},solution:{rate:1,emphasis:"normal",tone:"calm",leadingSilenceMs:300,trailingSilenceMs:500},metric:{rate:0.85,emphasis:"strong",tone:"excited",leadingSilenceMs:300,trailingSilenceMs:600},cta:{rate:0.9,emphasis:"strong",tone:"authoritative",leadingSilenceMs:400,trailingSilenceMs:0},transition:{rate:1.1,emphasis:"reduced",tone:"neutral",leadingSilenceMs:200,trailingSilenceMs:300}};class j{analyze(q,J=1){return q.map((U)=>{let K=v[U.contentType];return{sceneId:U.sceneId,rate:K.rate*J,emphasis:K.emphasis,tone:K.tone,leadingSilenceMs:K.leadingSilenceMs,trailingSilenceMs:K.trailingSilenceMs}})}getDefaults(q){return{...v[q]}}}class F{llm;model;modelSelector;selectionContext;paceAnalyzer;constructor(q){this.llm=q?.llm,this.model=q?.model,this.modelSelector=q?.modelSelector,this.selectionContext=q?.selectionContext,this.paceAnalyzer=new j}async plan(q,J=1){if(!this.llm)return this.paceAnalyzer.analyze(q,J);try{return await this.planWithLlm(q,J)}catch{return this.paceAnalyzer.analyze(q,J)}}async resolveModel(){if(this.model)return this.model;if(this.modelSelector){let q=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(q)).modelId}return}async planWithLlm(q,J){if(!this.llm)return this.paceAnalyzer.analyze(q,J);let U=await this.resolveModel(),X=(await this.llm.chat([{role:"system",content:[{type:"text",text:["You are a voice director planning emphasis and pacing for TTS narration.","For each segment, return a JSON array of directives.","Each directive has: sceneId, rate (0.7-1.3), emphasis (reduced|normal|strong),","tone (neutral|urgent|excited|calm|authoritative), leadingSilenceMs, trailingSilenceMs.","Return ONLY a JSON array, no other text."].join(`
13
- `)}]},{role:"user",content:[{type:"text",text:JSON.stringify(q.map((Z)=>({sceneId:Z.sceneId,text:Z.text,contentType:Z.contentType})))}]}],{model:U,temperature:0.3,responseFormat:"json"})).message.content.find((Z)=>Z.type==="text");if(!X||X.type!=="text")return this.paceAnalyzer.analyze(q,J);return JSON.parse(X.text).map((Z)=>({...Z,rate:Z.rate*J}))}}class y{tts;constructor(q){this.tts=q}async synthesizeAll(q,J,U){let K=new Map(U.map((Q)=>[Q.sceneId,Q]));return await Promise.all(q.map((Q)=>this.synthesizeOne(Q,J,K.get(Q.sceneId))))}async synthesizeOne(q,J,U){let K=await this.tts.synthesize({text:q.text,voiceId:J.voiceId,language:J.language,style:J.style,stability:J.stability,rate:U?.rate,emphasis:U?.emphasis});return{sceneId:q.sceneId,audio:K.audio,durationMs:K.audio.durationMs??0,wordTimings:K.wordTimings?.map((X)=>({word:X.word,startMs:X.startMs,endMs:X.endMs}))}}}class M{segmentSynthesizer;emphasisPlanner;audioAssembler=new z;durationEstimator=new G;paceAnalyzer=new j;options;constructor(q){this.options=q,this.segmentSynthesizer=new y(q.tts),this.emphasisPlanner=new F({llm:q.llm,model:q.model})}async synthesize(q){let J=this.buildScript(q);return this.executePipeline(J,q.voice,q.pacing)}async synthesizeForVideo(q){let J=this.buildScriptFromScenePlan(q);return this.executePipeline(J,q.voice,q.pacing,q.fps)}async executePipeline(q,J,U,K){let X=f(),Q=U?.baseRate??1,Z=await this.emphasisPlanner.plan(q.segments,Q),$=await this.segmentSynthesizer.synthesizeAll(q.segments,J,Z),Y=U?.segmentPauseMs??500,V=this.audioAssembler.assemble($,Z,Y),L=K??this.options.fps??30,_=U?.breathingRoomFactor??1.15,W=this.buildTimingMap($,L,_);return{id:X,script:q,pacingDirectives:Z,segments:$,assembledAudio:V,timingMap:W}}buildScript(q){let J=[],U=`${q.content.title}. ${q.content.summary}`;if(J.push({sceneId:"intro",text:U,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(U),contentType:"intro"}),q.content.problems.length>0){let Q=q.content.problems.join(". ");J.push({sceneId:"problems",text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"problem"})}if(q.content.solutions.length>0){let Q=q.content.solutions.join(". ");J.push({sceneId:"solutions",text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"solution"})}if(q.content.metrics&&q.content.metrics.length>0){let Q=q.content.metrics.join(". ");J.push({sceneId:"metrics",text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"metric"})}if(q.content.callToAction)J.push({sceneId:"cta",text:q.content.callToAction,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(q.content.callToAction),contentType:"cta"});let K=J.map((Q)=>Q.text).join(" "),X=J.reduce((Q,Z)=>Q+Z.estimatedDurationSeconds,0);return{fullText:K,segments:J,estimatedDurationSeconds:X}}buildScriptFromScenePlan(q){let J=q.scenePlan.scenes.filter((X)=>X.narrationText).map((X)=>{let Q=X.narrationText??"";return{sceneId:X.id,text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"intro"}}),U=J.map((X)=>X.text).join(" "),K=J.reduce((X,Q)=>X+Q.estimatedDurationSeconds,0);return{fullText:U,segments:J,estimatedDurationSeconds:K}}buildTimingMap(q,J,U){let K=q.map((Q)=>{let Z=Math.ceil(Q.durationMs/1000*J);return{sceneId:Q.sceneId,durationMs:Q.durationMs,durationInFrames:Z,recommendedSceneDurationInFrames:Math.ceil(Z*U),wordTimings:Q.wordTimings?.map(($)=>({word:$.word,startMs:$.startMs,endMs:$.endMs}))}});return{totalDurationMs:q.reduce((Q,Z)=>Q+Z.durationMs,0),segments:K,fps:J}}}function f(){let q=Date.now().toString(36),J=Math.random().toString(36).slice(2,8);return`tts_${q}_${J}`}import{defineFeature as p}from"@contractspec/lib.contracts-spec/features";var gq=p({meta:{key:"libs.voice",version:"1.0.0",title:"Voice",description:"Voice capabilities: TTS, STT, and conversational AI",domain:"voice",owners:["@contractspec-core"],tags:["package","libs","voice"],stability:"experimental"}});export{M as VoiceSynthesizer,D as VoiceSessionManager,gq as VoiceFeature,k as TurnDetector,I as TranscriptBuilder,x as Transcriber,w as TimingCalculator,N as SubtitleFormatter,E as SilenceGenerator,y as SegmentSynthesizer,C as SegmentSplitter,S as SceneAdapter,O as ResponseOrchestrator,j as PaceAnalyzer,P as FormatConverter,F as EmphasisPlanner,B as DurationNegotiator,G as DurationEstimator,T as DiarizationMapper,A as AudioConcatenator,z as AudioAssembler};
@@ -1 +0,0 @@
1
- class D{map(z,A="Speaker"){let q=[],v=new Map;for(let h of z)if(h.speakerId&&!q.includes(h.speakerId))q.push(h.speakerId),v.set(h.speakerId,{segmentCount:0,totalSpeakingMs:0});let B=z.map((h)=>{if(!h.speakerId)return h;let w=q.indexOf(h.speakerId),j=`${A} ${w+1}`,y=v.get(h.speakerId);if(!y)return{...h,speakerLabel:j};return y.segmentCount+=1,y.totalSpeakingMs+=h.endMs-h.startMs,{...h,speakerLabel:j}}),C=q.map((h,w)=>{let j=v.get(h);return{id:h,label:`${A} ${w+1}`,segmentCount:j?.segmentCount??0,totalSpeakingMs:j?.totalSpeakingMs??0}});return{segments:B,speakers:C}}}export{D as DiarizationMapper};
@@ -1,11 +0,0 @@
1
- class W{map(T,q="Speaker"){let A=[],R=new Map;for(let w of T)if(w.speakerId&&!A.includes(w.speakerId))A.push(w.speakerId),R.set(w.speakerId,{segmentCount:0,totalSpeakingMs:0});let E=T.map((w)=>{if(!w.speakerId)return w;let J=A.indexOf(w.speakerId),K=`${q} ${J+1}`,N=R.get(w.speakerId);if(!N)return{...w,speakerLabel:K};return N.segmentCount+=1,N.totalSpeakingMs+=w.endMs-w.startMs,{...w,speakerLabel:K}}),G=A.map((w,J)=>{let K=R.get(w);return{id:w,label:`${q} ${J+1}`,segmentCount:K?.segmentCount??0,totalSpeakingMs:K?.totalSpeakingMs??0}});return{segments:E,speakers:G}}}class U{static DEFAULT_MAX_CHUNK_MS=300000;split(T,q=U.DEFAULT_MAX_CHUNK_MS){let A=T.durationMs??this.estimateDurationMs(T);if(A<=q)return[T];let R=[],E=T.data.length/Math.max(A,1),G=0;while(G<A){let w=Math.min(q,A-G),J=Math.floor(G*E),K=Math.floor((G+w)*E);R.push({data:T.data.slice(J,K),format:T.format,sampleRateHz:T.sampleRateHz,durationMs:w,channels:T.channels}),G+=w}return R}estimateDurationMs(T){let A=T.channels??1,R=T.data.length/(2*A);return Math.ceil(R/T.sampleRateHz*1000)}}class X{toSRT(T){return T.map((q,A)=>{let R=this.formatTimeSRT(q.startMs),E=this.formatTimeSRT(q.endMs),G=q.speakerLabel?`[${q.speakerLabel}] `:"";return`${A+1}
2
- ${R} --> ${E}
3
- ${G}${q.text}`}).join(`
4
-
5
- `)}toVTT(T){return`WEBVTT
6
-
7
- `+T.map((R,E)=>{let G=this.formatTimeVTT(R.startMs),w=this.formatTimeVTT(R.endMs),J=R.speakerLabel?`<v ${R.speakerLabel}>`:"";return`${E+1}
8
- ${G} --> ${w}
9
- ${J}${R.text}`}).join(`
10
-
11
- `)}formatTimeSRT(T){let q=Math.floor(T/3600000),A=Math.floor(T%3600000/60000),R=Math.floor(T%60000/1000),E=T%1000;return`${this.pad(q,2)}:${this.pad(A,2)}:${this.pad(R,2)},${this.pad(E,3)}`}formatTimeVTT(T){let q=Math.floor(T/3600000),A=Math.floor(T%3600000/60000),R=Math.floor(T%60000/1000),E=T%1000;return`${this.pad(q,2)}:${this.pad(A,2)}:${this.pad(R,2)}.${this.pad(E,3)}`}pad(T,q){return T.toString().padStart(q,"0")}}class ${stt;segmentSplitter=new U;diarizationMapper=new W;subtitleFormatter=new X;constructor(T){this.stt=T.stt}async transcribe(T){let q=z(),A=this.segmentSplitter.split(T.audio),R=[],E="",G=0,w=0;for(let Q of A){let Z=await this.stt.transcribe({audio:Q,language:T.language,diarize:T.diarize,speakerCount:T.speakerCount,wordTimestamps:!0,vocabularyHints:T.vocabularyHints}),j=Z.segments.map((L)=>({text:L.text,startMs:L.startMs+w,endMs:L.endMs+w,speakerId:L.speakerId,speakerName:L.speakerName,confidence:L.confidence}));R.push(...j),E+=(E?" ":"")+Z.text,G+=Z.durationMs,w+=Q.durationMs??0}let J=R,K;if(T.diarize){let Q=this.diarizationMapper.map(R);J=Q.segments,K=Q.speakers}let N={text:E,segments:J,language:T.language??"en",durationMs:G},Y,_=T.subtitleFormat??"none";if(_==="srt")Y=this.subtitleFormatter.toSRT(J);else if(_==="vtt")Y=this.subtitleFormatter.toVTT(J);return{id:q,transcript:N,subtitles:Y,speakers:K}}async*transcribeStream(T,q){if(!this.stt.transcribeStream)throw Error("Streaming transcription not supported by the current STT provider");let A=this.stt.transcribeStream(T,{language:q?.language,diarize:q?.diarize,speakerCount:q?.speakerCount,wordTimestamps:!0,vocabularyHints:q?.vocabularyHints});for await(let R of A)yield{text:R.text,startMs:R.startMs,endMs:R.endMs,speakerId:R.speakerId,speakerLabel:R.speakerName,confidence:R.confidence}}}function z(){let T=Date.now().toString(36),q=Math.random().toString(36).slice(2,8);return`stt_${T}_${q}`}export{$ as Transcriber,X as SubtitleFormatter,U as SegmentSplitter,W as DiarizationMapper};
@@ -1 +0,0 @@
1
- class F{static DEFAULT_MAX_CHUNK_MS=300000;split(j,z=F.DEFAULT_MAX_CHUNK_MS){let q=j.durationMs??this.estimateDurationMs(j);if(q<=z)return[j];let w=[],E=j.data.length/Math.max(q,1),v=0;while(v<q){let A=Math.min(z,q-v),G=Math.floor(v*E),H=Math.floor((v+A)*E);w.push({data:j.data.slice(G,H),format:j.format,sampleRateHz:j.sampleRateHz,durationMs:A,channels:j.channels}),v+=A}return w}estimateDurationMs(j){let q=j.channels??1,w=j.data.length/(2*q);return Math.ceil(w/j.sampleRateHz*1000)}}export{F as SegmentSplitter};
@@ -1,11 +0,0 @@
1
- class A{toSRT(f){return f.map((j,w)=>{let k=this.formatTimeSRT(j.startMs),q=this.formatTimeSRT(j.endMs),x=j.speakerLabel?`[${j.speakerLabel}] `:"";return`${w+1}
2
- ${k} --> ${q}
3
- ${x}${j.text}`}).join(`
4
-
5
- `)}toVTT(f){return`WEBVTT
6
-
7
- `+f.map((k,q)=>{let x=this.formatTimeVTT(k.startMs),y=this.formatTimeVTT(k.endMs),z=k.speakerLabel?`<v ${k.speakerLabel}>`:"";return`${q+1}
8
- ${x} --> ${y}
9
- ${z}${k.text}`}).join(`
10
-
11
- `)}formatTimeSRT(f){let j=Math.floor(f/3600000),w=Math.floor(f%3600000/60000),k=Math.floor(f%60000/1000),q=f%1000;return`${this.pad(j,2)}:${this.pad(w,2)}:${this.pad(k,2)},${this.pad(q,3)}`}formatTimeVTT(f){let j=Math.floor(f/3600000),w=Math.floor(f%3600000/60000),k=Math.floor(f%60000/1000),q=f%1000;return`${this.pad(j,2)}:${this.pad(w,2)}:${this.pad(k,2)}.${this.pad(q,3)}`}pad(f,j){return f.toString().padStart(j,"0")}}export{A as SubtitleFormatter};
@@ -1,11 +0,0 @@
1
- class Z{map(q,A="Speaker"){let G=[],w=new Map;for(let E of q)if(E.speakerId&&!G.includes(E.speakerId))G.push(E.speakerId),w.set(E.speakerId,{segmentCount:0,totalSpeakingMs:0});let J=q.map((E)=>{if(!E.speakerId)return E;let L=G.indexOf(E.speakerId),N=`${A} ${L+1}`,R=w.get(E.speakerId);if(!R)return{...E,speakerLabel:N};return R.segmentCount+=1,R.totalSpeakingMs+=E.endMs-E.startMs,{...E,speakerLabel:N}}),K=G.map((E,L)=>{let N=w.get(E);return{id:E,label:`${A} ${L+1}`,segmentCount:N?.segmentCount??0,totalSpeakingMs:N?.totalSpeakingMs??0}});return{segments:J,speakers:K}}}class W{static DEFAULT_MAX_CHUNK_MS=300000;split(q,A=W.DEFAULT_MAX_CHUNK_MS){let G=q.durationMs??this.estimateDurationMs(q);if(G<=A)return[q];let w=[],J=q.data.length/Math.max(G,1),K=0;while(K<G){let E=Math.min(A,G-K),L=Math.floor(K*J),N=Math.floor((K+E)*J);w.push({data:q.data.slice(L,N),format:q.format,sampleRateHz:q.sampleRateHz,durationMs:E,channels:q.channels}),K+=E}return w}estimateDurationMs(q){let G=q.channels??1,w=q.data.length/(2*G);return Math.ceil(w/q.sampleRateHz*1000)}}class _{toSRT(q){return q.map((A,G)=>{let w=this.formatTimeSRT(A.startMs),J=this.formatTimeSRT(A.endMs),K=A.speakerLabel?`[${A.speakerLabel}] `:"";return`${G+1}
2
- ${w} --> ${J}
3
- ${K}${A.text}`}).join(`
4
-
5
- `)}toVTT(q){return`WEBVTT
6
-
7
- `+q.map((w,J)=>{let K=this.formatTimeVTT(w.startMs),E=this.formatTimeVTT(w.endMs),L=w.speakerLabel?`<v ${w.speakerLabel}>`:"";return`${J+1}
8
- ${K} --> ${E}
9
- ${L}${w.text}`}).join(`
10
-
11
- `)}formatTimeSRT(q){let A=Math.floor(q/3600000),G=Math.floor(q%3600000/60000),w=Math.floor(q%60000/1000),J=q%1000;return`${this.pad(A,2)}:${this.pad(G,2)}:${this.pad(w,2)},${this.pad(J,3)}`}formatTimeVTT(q){let A=Math.floor(q/3600000),G=Math.floor(q%3600000/60000),w=Math.floor(q%60000/1000),J=q%1000;return`${this.pad(A,2)}:${this.pad(G,2)}:${this.pad(w,2)}.${this.pad(J,3)}`}pad(q,A){return q.toString().padStart(A,"0")}}class j{stt;segmentSplitter=new W;diarizationMapper=new Z;subtitleFormatter=new _;constructor(q){this.stt=q.stt}async transcribe(q){let A=z(),G=this.segmentSplitter.split(q.audio),w=[],J="",K=0,E=0;for(let U of G){let Y=await this.stt.transcribe({audio:U,language:q.language,diarize:q.diarize,speakerCount:q.speakerCount,wordTimestamps:!0,vocabularyHints:q.vocabularyHints}),V=Y.segments.map((Q)=>({text:Q.text,startMs:Q.startMs+E,endMs:Q.endMs+E,speakerId:Q.speakerId,speakerName:Q.speakerName,confidence:Q.confidence}));w.push(...V),J+=(J?" ":"")+Y.text,K+=Y.durationMs,E+=U.durationMs??0}let L=w,N;if(q.diarize){let U=this.diarizationMapper.map(w);L=U.segments,N=U.speakers}let R={text:J,segments:L,language:q.language??"en",durationMs:K},X,$=q.subtitleFormat??"none";if($==="srt")X=this.subtitleFormatter.toSRT(L);else if($==="vtt")X=this.subtitleFormatter.toVTT(L);return{id:A,transcript:R,subtitles:X,speakers:N}}async*transcribeStream(q,A){if(!this.stt.transcribeStream)throw Error("Streaming transcription not supported by the current STT provider");let G=this.stt.transcribeStream(q,{language:A?.language,diarize:A?.diarize,speakerCount:A?.speakerCount,wordTimestamps:!0,vocabularyHints:A?.vocabularyHints});for await(let w of G)yield{text:w.text,startMs:w.startMs,endMs:w.endMs,speakerId:w.speakerId,speakerLabel:w.speakerName,confidence:w.confidence}}}function z(){let q=Date.now().toString(36),A=Math.random().toString(36).slice(2,8);return`stt_${q}_${A}`}export{j as Transcriber};
File without changes
@@ -1 +0,0 @@
1
- class q{static UPPER_THRESHOLD=1.1;static LOWER_THRESHOLD=0.7;static MAX_RATE=1.3;static MIN_RATE=0.8;negotiate(z,A){let v=[],B=z.segments.map((b)=>{let k=A.get(b.sceneId);if(k===void 0)return v.push({sceneId:b.sceneId,originalSceneDurationInFrames:b.recommendedSceneDurationInFrames,voiceDurationInFrames:b.durationInFrames,action:"no_change",finalSceneDurationInFrames:b.recommendedSceneDurationInFrames}),b;let x=b.durationInFrames/k;if(x>q.UPPER_THRESHOLD){let y=Math.min(x,q.MAX_RATE);return v.push({sceneId:b.sceneId,originalSceneDurationInFrames:k,voiceDurationInFrames:b.durationInFrames,action:x>q.MAX_RATE?"extend_scene":"suggest_rate_change",suggestedRate:y,finalSceneDurationInFrames:b.recommendedSceneDurationInFrames}),b}if(x<q.LOWER_THRESHOLD){let y=Math.max(x,q.MIN_RATE);return v.push({sceneId:b.sceneId,originalSceneDurationInFrames:k,voiceDurationInFrames:b.durationInFrames,action:"pad_silence",suggestedRate:y,finalSceneDurationInFrames:k}),{...b,recommendedSceneDurationInFrames:k}}return v.push({sceneId:b.sceneId,originalSceneDurationInFrames:k,voiceDurationInFrames:b.durationInFrames,action:"no_change",finalSceneDurationInFrames:b.recommendedSceneDurationInFrames}),b});return{timingMap:{...z,segments:B},adjustments:v}}}export{q as DurationNegotiator};
@@ -1 +0,0 @@
1
- class O{static DEFAULT_WPM=150;estimateSeconds(A,B){let G=B??O.DEFAULT_WPM,H=A.split(/\s+/).filter(Boolean).length;return Math.ceil(H/G*60)}estimateMs(A,B){let G=B??O.DEFAULT_WPM,H=A.split(/\s+/).filter(Boolean).length;return Math.ceil(H/G*60*1000)}estimateWordCount(A,B){let G=B??O.DEFAULT_WPM;return Math.round(A/60*G)}}class L{static UPPER_THRESHOLD=1.1;static LOWER_THRESHOLD=0.7;static MAX_RATE=1.3;static MIN_RATE=0.8;negotiate(A,B){let G=[],H=A.segments.map((z)=>{let q=B.get(z.sceneId);if(q===void 0)return G.push({sceneId:z.sceneId,originalSceneDurationInFrames:z.recommendedSceneDurationInFrames,voiceDurationInFrames:z.durationInFrames,action:"no_change",finalSceneDurationInFrames:z.recommendedSceneDurationInFrames}),z;let J=z.durationInFrames/q;if(J>L.UPPER_THRESHOLD){let K=Math.min(J,L.MAX_RATE);return G.push({sceneId:z.sceneId,originalSceneDurationInFrames:q,voiceDurationInFrames:z.durationInFrames,action:J>L.MAX_RATE?"extend_scene":"suggest_rate_change",suggestedRate:K,finalSceneDurationInFrames:z.recommendedSceneDurationInFrames}),z}if(J<L.LOWER_THRESHOLD){let K=Math.max(J,L.MIN_RATE);return G.push({sceneId:z.sceneId,originalSceneDurationInFrames:q,voiceDurationInFrames:z.durationInFrames,action:"pad_silence",suggestedRate:K,finalSceneDurationInFrames:q}),{...z,recommendedSceneDurationInFrames:q}}return G.push({sceneId:z.sceneId,originalSceneDurationInFrames:q,voiceDurationInFrames:z.durationInFrames,action:"no_change",finalSceneDurationInFrames:z.recommendedSceneDurationInFrames}),z});return{timingMap:{...A,segments:H},adjustments:G}}}class U{durationEstimator=new O;adapt(A){let B=A.scenes.filter((q)=>q.narrationText&&q.narrationText.trim().length>0),G=B.map((q,J)=>{let K=q.narrationText??"";return{sceneId:q.id,text:K,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(K),contentType:this.inferContentType(J,B.length)}}),H=G.map((q)=>q.text).join(" "),z=G.reduce((q,J)=>q+J.estimatedDurationSeconds,0);return{fullText:H,segments:G,estimatedDurationSeconds:z}}inferContentType(A,B){if(A===0)return"intro";if(A===B-1)return"cta";if(A===1&&B>3)return"problem";if(A===B-2&&B>3)return"metric";return"solution"}}class X{calculate(A,B,G=1.15){let H=A.map((q)=>{let J=Math.ceil(q.durationMs/1000*B),K=Math.ceil(J*G),Y=q.wordTimings?.map((Q)=>({word:Q.word,startMs:Q.startMs,endMs:Q.endMs}));return{sceneId:q.sceneId,durationMs:q.durationMs,durationInFrames:J,recommendedSceneDurationInFrames:K,wordTimings:Y}});return{totalDurationMs:A.reduce((q,J)=>q+J.durationMs,0),segments:H,fps:B}}recalculateForFps(A,B){let G=A.segments.map((H)=>{let z=Math.ceil(H.durationMs/1000*B),q=H.recommendedSceneDurationInFrames/Math.max(H.durationInFrames,1);return{...H,durationInFrames:z,recommendedSceneDurationInFrames:Math.ceil(z*q)}});return{...A,segments:G,fps:B}}}export{X as TimingCalculator,U as SceneAdapter,L as DurationNegotiator};
@@ -1 +0,0 @@
1
- class G{static DEFAULT_WPM=150;estimateSeconds(q,j){let z=j??G.DEFAULT_WPM,H=q.split(/\s+/).filter(Boolean).length;return Math.ceil(H/z*60)}estimateMs(q,j){let z=j??G.DEFAULT_WPM,H=q.split(/\s+/).filter(Boolean).length;return Math.ceil(H/z*60*1000)}estimateWordCount(q,j){let z=j??G.DEFAULT_WPM;return Math.round(q/60*z)}}class L{durationEstimator=new G;adapt(q){let j=q.scenes.filter((B)=>B.narrationText&&B.narrationText.trim().length>0),z=j.map((B,I)=>{let J=B.narrationText??"";return{sceneId:B.id,text:J,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(J),contentType:this.inferContentType(I,j.length)}}),H=z.map((B)=>B.text).join(" "),K=z.reduce((B,I)=>B+I.estimatedDurationSeconds,0);return{fullText:H,segments:z,estimatedDurationSeconds:K}}inferContentType(q,j){if(q===0)return"intro";if(q===j-1)return"cta";if(q===1&&j>3)return"problem";if(q===j-2&&j>3)return"metric";return"solution"}}export{L as SceneAdapter};
@@ -1 +0,0 @@
1
- class z{calculate(h,j,l=1.15){let f=h.map((b)=>{let k=Math.ceil(b.durationMs/1000*j),x=Math.ceil(k*l),y=b.wordTimings?.map((v)=>({word:v.word,startMs:v.startMs,endMs:v.endMs}));return{sceneId:b.sceneId,durationMs:b.durationMs,durationInFrames:k,recommendedSceneDurationInFrames:x,wordTimings:y}});return{totalDurationMs:h.reduce((b,k)=>b+k.durationMs,0),segments:f,fps:j}}recalculateForFps(h,j){let l=h.segments.map((f)=>{let q=Math.ceil(f.durationMs/1000*j),b=f.recommendedSceneDurationInFrames/Math.max(f.durationInFrames,1);return{...f,durationInFrames:q,recommendedSceneDurationInFrames:Math.ceil(q*b)}});return{...h,segments:l,fps:j}}}export{z as TimingCalculator};
@@ -1 +0,0 @@
1
- class V{concatenate(j){if(j.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[x]=j;if(!x)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};if(j.length===1)return{...x};let{format:N,sampleRateHz:E}=x,Q=x.channels??1;for(let k of j){if(k.format!==N)throw Error(`Format mismatch: expected ${N}, got ${k.format}`);if(k.sampleRateHz!==E)throw Error(`Sample rate mismatch: expected ${E}, got ${k.sampleRateHz}`)}let J=j.reduce((k,O)=>k+O.data.length,0),K=new Uint8Array(J),q=0;for(let k of j)K.set(k.data,q),q+=k.data.length;let U=j.reduce((k,O)=>k+(O.durationMs??0),0);return{data:K,format:N,sampleRateHz:E,durationMs:U,channels:Q}}}class W{generate(j,x="wav",N=44100,E=1){let Q=Math.ceil(N*j/1000),J=2,K=Q*2*E;return{data:new Uint8Array(K),format:x,sampleRateHz:N,durationMs:j,channels:E}}}class Y{concatenator=new V;silenceGenerator=new W;assemble(j,x,N=500){if(j.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[E]=j;if(!E)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let Q=new Map(x.map((q)=>[q.sceneId,q])),J=E.audio,K=[];for(let q=0;q<j.length;q++){let U=j[q];if(!U)continue;let k=Q.get(U.sceneId),O=k?.leadingSilenceMs??0;if(O>0)K.push(this.silenceGenerator.generate(O,J.format,J.sampleRateHz,J.channels??1));K.push(U.audio);let X=k?.trailingSilenceMs??(q<j.length-1?N:0);if(X>0)K.push(this.silenceGenerator.generate(X,J.format,J.sampleRateHz,J.channels??1))}return this.concatenator.concatenate(K)}}export{Y as AudioAssembler};
@@ -1,2 +0,0 @@
1
- var H={intro:{rate:0.95,emphasis:"normal",tone:"authoritative",leadingSilenceMs:0,trailingSilenceMs:500},problem:{rate:0.9,emphasis:"strong",tone:"urgent",leadingSilenceMs:300,trailingSilenceMs:500},solution:{rate:1,emphasis:"normal",tone:"calm",leadingSilenceMs:300,trailingSilenceMs:500},metric:{rate:0.85,emphasis:"strong",tone:"excited",leadingSilenceMs:300,trailingSilenceMs:600},cta:{rate:0.9,emphasis:"strong",tone:"authoritative",leadingSilenceMs:400,trailingSilenceMs:0},transition:{rate:1.1,emphasis:"reduced",tone:"neutral",leadingSilenceMs:200,trailingSilenceMs:300}};class F{analyze(j,q=1){return j.map((B)=>{let w=H[B.contentType];return{sceneId:B.sceneId,rate:w.rate*q,emphasis:w.emphasis,tone:w.tone,leadingSilenceMs:w.leadingSilenceMs,trailingSilenceMs:w.trailingSilenceMs}})}getDefaults(j){return{...H[j]}}}class J{llm;model;modelSelector;selectionContext;paceAnalyzer;constructor(j){this.llm=j?.llm,this.model=j?.model,this.modelSelector=j?.modelSelector,this.selectionContext=j?.selectionContext,this.paceAnalyzer=new F}async plan(j,q=1){if(!this.llm)return this.paceAnalyzer.analyze(j,q);try{return await this.planWithLlm(j,q)}catch{return this.paceAnalyzer.analyze(j,q)}}async resolveModel(){if(this.model)return this.model;if(this.modelSelector){let j=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(j)).modelId}return}async planWithLlm(j,q){if(!this.llm)return this.paceAnalyzer.analyze(j,q);let B=await this.resolveModel(),D=(await this.llm.chat([{role:"system",content:[{type:"text",text:["You are a voice director planning emphasis and pacing for TTS narration.","For each segment, return a JSON array of directives.","Each directive has: sceneId, rate (0.7-1.3), emphasis (reduced|normal|strong),","tone (neutral|urgent|excited|calm|authoritative), leadingSilenceMs, trailingSilenceMs.","Return ONLY a JSON array, no other text."].join(`
2
- `)}]},{role:"user",content:[{type:"text",text:JSON.stringify(j.map((v)=>({sceneId:v.sceneId,text:v.text,contentType:v.contentType})))}]}],{model:B,temperature:0.3,responseFormat:"json"})).message.content.find((v)=>v.type==="text");if(!D||D.type!=="text")return this.paceAnalyzer.analyze(j,q);return JSON.parse(D.text).map((v)=>({...v,rate:v.rate*q}))}}export{J as EmphasisPlanner};
@@ -1,2 +0,0 @@
1
- class H{concatenate(V){if(V.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[q]=V;if(!q)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};if(V.length===1)return{...q};let{format:J,sampleRateHz:K}=q,U=q.channels??1;for(let Z of V){if(Z.format!==J)throw Error(`Format mismatch: expected ${J}, got ${Z.format}`);if(Z.sampleRateHz!==K)throw Error(`Sample rate mismatch: expected ${K}, got ${Z.sampleRateHz}`)}let B=V.reduce((Z,$)=>Z+$.data.length,0),Q=new Uint8Array(B),X=0;for(let Z of V)Q.set(Z.data,X),X+=Z.data.length;let j=V.reduce((Z,$)=>Z+($.durationMs??0),0);return{data:Q,format:J,sampleRateHz:K,durationMs:j,channels:U}}}class O{static DEFAULT_WPM=150;estimateSeconds(V,q){let J=q??O.DEFAULT_WPM,K=V.split(/\s+/).filter(Boolean).length;return Math.ceil(K/J*60)}estimateMs(V,q){let J=q??O.DEFAULT_WPM,K=V.split(/\s+/).filter(Boolean).length;return Math.ceil(K/J*60*1000)}estimateWordCount(V,q){let J=q??O.DEFAULT_WPM;return Math.round(V/60*J)}}class _{generate(V,q="wav",J=44100,K=1){let U=Math.ceil(J*V/1000),B=2,Q=U*2*K;return{data:new Uint8Array(Q),format:q,sampleRateHz:J,durationMs:V,channels:K}}}class T{concatenator=new H;silenceGenerator=new _;assemble(V,q,J=500){if(V.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[K]=V;if(!K)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let U=new Map(q.map((X)=>[X.sceneId,X])),B=K.audio,Q=[];for(let X=0;X<V.length;X++){let j=V[X];if(!j)continue;let Z=U.get(j.sceneId),$=Z?.leadingSilenceMs??0;if($>0)Q.push(this.silenceGenerator.generate($,B.format,B.sampleRateHz,B.channels??1));Q.push(j.audio);let k=Z?.trailingSilenceMs??(X<V.length-1?J:0);if(k>0)Q.push(this.silenceGenerator.generate(k,B.format,B.sampleRateHz,B.channels??1))}return this.concatenator.concatenate(Q)}}var G={intro:{rate:0.95,emphasis:"normal",tone:"authoritative",leadingSilenceMs:0,trailingSilenceMs:500},problem:{rate:0.9,emphasis:"strong",tone:"urgent",leadingSilenceMs:300,trailingSilenceMs:500},solution:{rate:1,emphasis:"normal",tone:"calm",leadingSilenceMs:300,trailingSilenceMs:500},metric:{rate:0.85,emphasis:"strong",tone:"excited",leadingSilenceMs:300,trailingSilenceMs:600},cta:{rate:0.9,emphasis:"strong",tone:"authoritative",leadingSilenceMs:400,trailingSilenceMs:0},transition:{rate:1.1,emphasis:"reduced",tone:"neutral",leadingSilenceMs:200,trailingSilenceMs:300}};class W{analyze(V,q=1){return V.map((J)=>{let K=G[J.contentType];return{sceneId:J.sceneId,rate:K.rate*q,emphasis:K.emphasis,tone:K.tone,leadingSilenceMs:K.leadingSilenceMs,trailingSilenceMs:K.trailingSilenceMs}})}getDefaults(V){return{...G[V]}}}class Y{llm;model;modelSelector;selectionContext;paceAnalyzer;constructor(V){this.llm=V?.llm,this.model=V?.model,this.modelSelector=V?.modelSelector,this.selectionContext=V?.selectionContext,this.paceAnalyzer=new W}async plan(V,q=1){if(!this.llm)return this.paceAnalyzer.analyze(V,q);try{return await this.planWithLlm(V,q)}catch{return this.paceAnalyzer.analyze(V,q)}}async resolveModel(){if(this.model)return this.model;if(this.modelSelector){let V=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(V)).modelId}return}async planWithLlm(V,q){if(!this.llm)return this.paceAnalyzer.analyze(V,q);let J=await this.resolveModel(),U=(await this.llm.chat([{role:"system",content:[{type:"text",text:["You are a voice director planning emphasis and pacing for TTS narration.","For each segment, return a JSON array of directives.","Each directive has: sceneId, rate (0.7-1.3), emphasis (reduced|normal|strong),","tone (neutral|urgent|excited|calm|authoritative), leadingSilenceMs, trailingSilenceMs.","Return ONLY a JSON array, no other text."].join(`
2
- `)}]},{role:"user",content:[{type:"text",text:JSON.stringify(V.map((Q)=>({sceneId:Q.sceneId,text:Q.text,contentType:Q.contentType})))}]}],{model:J,temperature:0.3,responseFormat:"json"})).message.content.find((Q)=>Q.type==="text");if(!U||U.type!=="text")return this.paceAnalyzer.analyze(V,q);return JSON.parse(U.text).map((Q)=>({...Q,rate:Q.rate*q}))}}class L{tts;constructor(V){this.tts=V}async synthesizeAll(V,q,J){let K=new Map(J.map((B)=>[B.sceneId,B]));return await Promise.all(V.map((B)=>this.synthesizeOne(B,q,K.get(B.sceneId))))}async synthesizeOne(V,q,J){let K=await this.tts.synthesize({text:V.text,voiceId:q.voiceId,language:q.language,style:q.style,stability:q.stability,rate:J?.rate,emphasis:J?.emphasis});return{sceneId:V.sceneId,audio:K.audio,durationMs:K.audio.durationMs??0,wordTimings:K.wordTimings?.map((U)=>({word:U.word,startMs:U.startMs,endMs:U.endMs}))}}}class N{segmentSynthesizer;emphasisPlanner;audioAssembler=new T;durationEstimator=new O;paceAnalyzer=new W;options;constructor(V){this.options=V,this.segmentSynthesizer=new L(V.tts),this.emphasisPlanner=new Y({llm:V.llm,model:V.model})}async synthesize(V){let q=this.buildScript(V);return this.executePipeline(q,V.voice,V.pacing)}async synthesizeForVideo(V){let q=this.buildScriptFromScenePlan(V);return this.executePipeline(q,V.voice,V.pacing,V.fps)}async executePipeline(V,q,J,K){let U=F(),B=J?.baseRate??1,Q=await this.emphasisPlanner.plan(V.segments,B),X=await this.segmentSynthesizer.synthesizeAll(V.segments,q,Q),j=J?.segmentPauseMs??500,Z=this.audioAssembler.assemble(X,Q,j),$=K??this.options.fps??30,k=J?.breathingRoomFactor??1.15,C=this.buildTimingMap(X,$,k);return{id:U,script:V,pacingDirectives:Q,segments:X,assembledAudio:Z,timingMap:C}}buildScript(V){let q=[],J=`${V.content.title}. ${V.content.summary}`;if(q.push({sceneId:"intro",text:J,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(J),contentType:"intro"}),V.content.problems.length>0){let B=V.content.problems.join(". ");q.push({sceneId:"problems",text:B,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(B),contentType:"problem"})}if(V.content.solutions.length>0){let B=V.content.solutions.join(". ");q.push({sceneId:"solutions",text:B,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(B),contentType:"solution"})}if(V.content.metrics&&V.content.metrics.length>0){let B=V.content.metrics.join(". ");q.push({sceneId:"metrics",text:B,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(B),contentType:"metric"})}if(V.content.callToAction)q.push({sceneId:"cta",text:V.content.callToAction,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(V.content.callToAction),contentType:"cta"});let K=q.map((B)=>B.text).join(" "),U=q.reduce((B,Q)=>B+Q.estimatedDurationSeconds,0);return{fullText:K,segments:q,estimatedDurationSeconds:U}}buildScriptFromScenePlan(V){let q=V.scenePlan.scenes.filter((U)=>U.narrationText).map((U)=>{let B=U.narrationText??"";return{sceneId:U.id,text:B,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(B),contentType:"intro"}}),J=q.map((U)=>U.text).join(" "),K=q.reduce((U,B)=>U+B.estimatedDurationSeconds,0);return{fullText:J,segments:q,estimatedDurationSeconds:K}}buildTimingMap(V,q,J){let K=V.map((B)=>{let Q=Math.ceil(B.durationMs/1000*q);return{sceneId:B.sceneId,durationMs:B.durationMs,durationInFrames:Q,recommendedSceneDurationInFrames:Math.ceil(Q*J),wordTimings:B.wordTimings?.map((X)=>({word:X.word,startMs:X.startMs,endMs:X.endMs}))}});return{totalDurationMs:V.reduce((B,Q)=>B+Q.durationMs,0),segments:K,fps:q}}}function F(){let V=Date.now().toString(36),q=Math.random().toString(36).slice(2,8);return`tts_${V}_${q}`}export{N as VoiceSynthesizer,L as SegmentSynthesizer,W as PaceAnalyzer,Y as EmphasisPlanner,T as AudioAssembler};
@@ -1 +0,0 @@
1
- var v={intro:{rate:0.95,emphasis:"normal",tone:"authoritative",leadingSilenceMs:0,trailingSilenceMs:500},problem:{rate:0.9,emphasis:"strong",tone:"urgent",leadingSilenceMs:300,trailingSilenceMs:500},solution:{rate:1,emphasis:"normal",tone:"calm",leadingSilenceMs:300,trailingSilenceMs:500},metric:{rate:0.85,emphasis:"strong",tone:"excited",leadingSilenceMs:300,trailingSilenceMs:600},cta:{rate:0.9,emphasis:"strong",tone:"authoritative",leadingSilenceMs:400,trailingSilenceMs:0},transition:{rate:1.1,emphasis:"reduced",tone:"neutral",leadingSilenceMs:200,trailingSilenceMs:300}};class x{analyze(k,w=1){return k.map((q)=>{let j=v[q.contentType];return{sceneId:q.sceneId,rate:j.rate*w,emphasis:j.emphasis,tone:j.tone,leadingSilenceMs:j.leadingSilenceMs,trailingSilenceMs:j.trailingSilenceMs}})}getDefaults(k){return{...v[k]}}}export{x as PaceAnalyzer};
@@ -1 +0,0 @@
1
- class C{tts;constructor(j){this.tts=j}async synthesizeAll(j,k,A){let q=new Map(A.map((x)=>[x.sceneId,x]));return await Promise.all(j.map((x)=>this.synthesizeOne(x,k,q.get(x.sceneId))))}async synthesizeOne(j,k,A){let q=await this.tts.synthesize({text:j.text,voiceId:k.voiceId,language:k.language,style:k.style,stability:k.stability,rate:A?.rate,emphasis:A?.emphasis});return{sceneId:j.sceneId,audio:q.audio,durationMs:q.audio.durationMs??0,wordTimings:q.wordTimings?.map((B)=>({word:B.word,startMs:B.startMs,endMs:B.endMs}))}}}export{C as SegmentSynthesizer};
File without changes
@@ -1,2 +0,0 @@
1
- class Y{concatenate(q){if(q.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[J]=q;if(!J)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};if(q.length===1)return{...J};let{format:Q,sampleRateHz:U}=J,Z=J.channels??1;for(let V of q){if(V.format!==Q)throw Error(`Format mismatch: expected ${Q}, got ${V.format}`);if(V.sampleRateHz!==U)throw Error(`Sample rate mismatch: expected ${U}, got ${V.sampleRateHz}`)}let K=q.reduce((V,W)=>V+W.data.length,0),X=new Uint8Array(K),$=0;for(let V of q)X.set(V.data,$),$+=V.data.length;let j=q.reduce((V,W)=>V+(W.durationMs??0),0);return{data:X,format:Q,sampleRateHz:U,durationMs:j,channels:Z}}}class k{static DEFAULT_WPM=150;estimateSeconds(q,J){let Q=J??k.DEFAULT_WPM,U=q.split(/\s+/).filter(Boolean).length;return Math.ceil(U/Q*60)}estimateMs(q,J){let Q=J??k.DEFAULT_WPM,U=q.split(/\s+/).filter(Boolean).length;return Math.ceil(U/Q*60*1000)}estimateWordCount(q,J){let Q=J??k.DEFAULT_WPM;return Math.round(q/60*Q)}}class L{generate(q,J="wav",Q=44100,U=1){let Z=Math.ceil(Q*q/1000),K=2,X=Z*2*U;return{data:new Uint8Array(X),format:J,sampleRateHz:Q,durationMs:q,channels:U}}}class H{concatenator=new Y;silenceGenerator=new L;assemble(q,J,Q=500){if(q.length===0)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let[U]=q;if(!U)return{data:new Uint8Array(0),format:"wav",sampleRateHz:44100,durationMs:0,channels:1};let Z=new Map(J.map(($)=>[$.sceneId,$])),K=U.audio,X=[];for(let $=0;$<q.length;$++){let j=q[$];if(!j)continue;let V=Z.get(j.sceneId),W=V?.leadingSilenceMs??0;if(W>0)X.push(this.silenceGenerator.generate(W,K.format,K.sampleRateHz,K.channels??1));X.push(j.audio);let O=V?.trailingSilenceMs??($<q.length-1?Q:0);if(O>0)X.push(this.silenceGenerator.generate(O,K.format,K.sampleRateHz,K.channels??1))}return this.concatenator.concatenate(X)}}var N={intro:{rate:0.95,emphasis:"normal",tone:"authoritative",leadingSilenceMs:0,trailingSilenceMs:500},problem:{rate:0.9,emphasis:"strong",tone:"urgent",leadingSilenceMs:300,trailingSilenceMs:500},solution:{rate:1,emphasis:"normal",tone:"calm",leadingSilenceMs:300,trailingSilenceMs:500},metric:{rate:0.85,emphasis:"strong",tone:"excited",leadingSilenceMs:300,trailingSilenceMs:600},cta:{rate:0.9,emphasis:"strong",tone:"authoritative",leadingSilenceMs:400,trailingSilenceMs:0},transition:{rate:1.1,emphasis:"reduced",tone:"neutral",leadingSilenceMs:200,trailingSilenceMs:300}};class B{analyze(q,J=1){return q.map((Q)=>{let U=N[Q.contentType];return{sceneId:Q.sceneId,rate:U.rate*J,emphasis:U.emphasis,tone:U.tone,leadingSilenceMs:U.leadingSilenceMs,trailingSilenceMs:U.trailingSilenceMs}})}getDefaults(q){return{...N[q]}}}class _{llm;model;modelSelector;selectionContext;paceAnalyzer;constructor(q){this.llm=q?.llm,this.model=q?.model,this.modelSelector=q?.modelSelector,this.selectionContext=q?.selectionContext,this.paceAnalyzer=new B}async plan(q,J=1){if(!this.llm)return this.paceAnalyzer.analyze(q,J);try{return await this.planWithLlm(q,J)}catch{return this.paceAnalyzer.analyze(q,J)}}async resolveModel(){if(this.model)return this.model;if(this.modelSelector){let q=this.selectionContext??{taskDimension:"reasoning"};return(await this.modelSelector.select(q)).modelId}return}async planWithLlm(q,J){if(!this.llm)return this.paceAnalyzer.analyze(q,J);let Q=await this.resolveModel(),Z=(await this.llm.chat([{role:"system",content:[{type:"text",text:["You are a voice director planning emphasis and pacing for TTS narration.","For each segment, return a JSON array of directives.","Each directive has: sceneId, rate (0.7-1.3), emphasis (reduced|normal|strong),","tone (neutral|urgent|excited|calm|authoritative), leadingSilenceMs, trailingSilenceMs.","Return ONLY a JSON array, no other text."].join(`
2
- `)}]},{role:"user",content:[{type:"text",text:JSON.stringify(q.map((X)=>({sceneId:X.sceneId,text:X.text,contentType:X.contentType})))}]}],{model:Q,temperature:0.3,responseFormat:"json"})).message.content.find((X)=>X.type==="text");if(!Z||Z.type!=="text")return this.paceAnalyzer.analyze(q,J);return JSON.parse(Z.text).map((X)=>({...X,rate:X.rate*J}))}}class G{tts;constructor(q){this.tts=q}async synthesizeAll(q,J,Q){let U=new Map(Q.map((K)=>[K.sceneId,K]));return await Promise.all(q.map((K)=>this.synthesizeOne(K,J,U.get(K.sceneId))))}async synthesizeOne(q,J,Q){let U=await this.tts.synthesize({text:q.text,voiceId:J.voiceId,language:J.language,style:J.style,stability:J.stability,rate:Q?.rate,emphasis:Q?.emphasis});return{sceneId:q.sceneId,audio:U.audio,durationMs:U.audio.durationMs??0,wordTimings:U.wordTimings?.map((Z)=>({word:Z.word,startMs:Z.startMs,endMs:Z.endMs}))}}}class I{segmentSynthesizer;emphasisPlanner;audioAssembler=new H;durationEstimator=new k;paceAnalyzer=new B;options;constructor(q){this.options=q,this.segmentSynthesizer=new G(q.tts),this.emphasisPlanner=new _({llm:q.llm,model:q.model})}async synthesize(q){let J=this.buildScript(q);return this.executePipeline(J,q.voice,q.pacing)}async synthesizeForVideo(q){let J=this.buildScriptFromScenePlan(q);return this.executePipeline(J,q.voice,q.pacing,q.fps)}async executePipeline(q,J,Q,U){let Z=C(),K=Q?.baseRate??1,X=await this.emphasisPlanner.plan(q.segments,K),$=await this.segmentSynthesizer.synthesizeAll(q.segments,J,X),j=Q?.segmentPauseMs??500,V=this.audioAssembler.assemble($,X,j),W=U??this.options.fps??30,O=Q?.breathingRoomFactor??1.15,F=this.buildTimingMap($,W,O);return{id:Z,script:q,pacingDirectives:X,segments:$,assembledAudio:V,timingMap:F}}buildScript(q){let J=[],Q=`${q.content.title}. ${q.content.summary}`;if(J.push({sceneId:"intro",text:Q,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(Q),contentType:"intro"}),q.content.problems.length>0){let K=q.content.problems.join(". ");J.push({sceneId:"problems",text:K,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(K),contentType:"problem"})}if(q.content.solutions.length>0){let K=q.content.solutions.join(". ");J.push({sceneId:"solutions",text:K,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(K),contentType:"solution"})}if(q.content.metrics&&q.content.metrics.length>0){let K=q.content.metrics.join(". ");J.push({sceneId:"metrics",text:K,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(K),contentType:"metric"})}if(q.content.callToAction)J.push({sceneId:"cta",text:q.content.callToAction,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(q.content.callToAction),contentType:"cta"});let U=J.map((K)=>K.text).join(" "),Z=J.reduce((K,X)=>K+X.estimatedDurationSeconds,0);return{fullText:U,segments:J,estimatedDurationSeconds:Z}}buildScriptFromScenePlan(q){let J=q.scenePlan.scenes.filter((Z)=>Z.narrationText).map((Z)=>{let K=Z.narrationText??"";return{sceneId:Z.id,text:K,estimatedDurationSeconds:this.durationEstimator.estimateSeconds(K),contentType:"intro"}}),Q=J.map((Z)=>Z.text).join(" "),U=J.reduce((Z,K)=>Z+K.estimatedDurationSeconds,0);return{fullText:Q,segments:J,estimatedDurationSeconds:U}}buildTimingMap(q,J,Q){let U=q.map((K)=>{let X=Math.ceil(K.durationMs/1000*J);return{sceneId:K.sceneId,durationMs:K.durationMs,durationInFrames:X,recommendedSceneDurationInFrames:Math.ceil(X*Q),wordTimings:K.wordTimings?.map(($)=>({word:$.word,startMs:$.startMs,endMs:$.endMs}))}});return{totalDurationMs:q.reduce((K,X)=>K+X.durationMs,0),segments:U,fps:J}}}function C(){let q=Date.now().toString(36),J=Math.random().toString(36).slice(2,8);return`tts_${q}_${J}`}export{I as VoiceSynthesizer};
File without changes
@@ -1 +0,0 @@
1
- import{defineFeature as g}from"@contractspec/lib.contracts-spec/features";var j=g({meta:{key:"libs.voice",version:"1.0.0",title:"Voice",description:"Voice capabilities: TTS, STT, and conversational AI",domain:"voice",owners:["@contractspec-core"],tags:["package","libs","voice"],stability:"experimental"}});export{j as VoiceFeature};
@@ -1,19 +0,0 @@
1
- import type { SpeakerMap, TranscriptionSegment } from './types';
2
- /**
3
- * Map raw speaker IDs from STT provider output to human-readable labels.
4
- *
5
- * Assigns sequential labels (Speaker 1, Speaker 2, ...) based on
6
- * first-appearance order. Calculates per-speaker statistics.
7
- */
8
- export declare class DiarizationMapper {
9
- /**
10
- * Map speaker IDs to labels and compute statistics.
11
- *
12
- * @param segments - Transcription segments with optional speakerId
13
- * @param labelPrefix - Prefix for speaker labels (default "Speaker")
14
- */
15
- map(segments: TranscriptionSegment[], labelPrefix?: string): {
16
- segments: TranscriptionSegment[];
17
- speakers: SpeakerMap[];
18
- };
19
- }
@@ -1,2 +0,0 @@
1
- // @bun
2
- class D{map(z,A="Speaker"){let q=[],v=new Map;for(let h of z)if(h.speakerId&&!q.includes(h.speakerId))q.push(h.speakerId),v.set(h.speakerId,{segmentCount:0,totalSpeakingMs:0});let B=z.map((h)=>{if(!h.speakerId)return h;let w=q.indexOf(h.speakerId),j=`${A} ${w+1}`,y=v.get(h.speakerId);if(!y)return{...h,speakerLabel:j};return y.segmentCount+=1,y.totalSpeakingMs+=h.endMs-h.startMs,{...h,speakerLabel:j}}),C=q.map((h,w)=>{let j=v.get(h);return{id:h,label:`${A} ${w+1}`,segmentCount:j?.segmentCount??0,totalSpeakingMs:j?.totalSpeakingMs??0}});return{segments:B,speakers:C}}}export{D as DiarizationMapper};
@@ -1,5 +0,0 @@
1
- export { DiarizationMapper } from './diarization-mapper';
2
- export { SegmentSplitter } from './segment-splitter';
3
- export { SubtitleFormatter } from './subtitle-formatter';
4
- export { Transcriber } from './transcriber';
5
- export type { SpeakerMap, STTBrief, STTOptions, TranscriptionProject, TranscriptionResult, TranscriptionSegment, } from './types';
package/dist/stt/index.js DELETED
@@ -1,12 +0,0 @@
1
- // @bun
2
- class W{map(T,q="Speaker"){let A=[],R=new Map;for(let w of T)if(w.speakerId&&!A.includes(w.speakerId))A.push(w.speakerId),R.set(w.speakerId,{segmentCount:0,totalSpeakingMs:0});let E=T.map((w)=>{if(!w.speakerId)return w;let J=A.indexOf(w.speakerId),K=`${q} ${J+1}`,N=R.get(w.speakerId);if(!N)return{...w,speakerLabel:K};return N.segmentCount+=1,N.totalSpeakingMs+=w.endMs-w.startMs,{...w,speakerLabel:K}}),G=A.map((w,J)=>{let K=R.get(w);return{id:w,label:`${q} ${J+1}`,segmentCount:K?.segmentCount??0,totalSpeakingMs:K?.totalSpeakingMs??0}});return{segments:E,speakers:G}}}class U{static DEFAULT_MAX_CHUNK_MS=300000;split(T,q=U.DEFAULT_MAX_CHUNK_MS){let A=T.durationMs??this.estimateDurationMs(T);if(A<=q)return[T];let R=[],E=T.data.length/Math.max(A,1),G=0;while(G<A){let w=Math.min(q,A-G),J=Math.floor(G*E),K=Math.floor((G+w)*E);R.push({data:T.data.slice(J,K),format:T.format,sampleRateHz:T.sampleRateHz,durationMs:w,channels:T.channels}),G+=w}return R}estimateDurationMs(T){let A=T.channels??1,R=T.data.length/(2*A);return Math.ceil(R/T.sampleRateHz*1000)}}class X{toSRT(T){return T.map((q,A)=>{let R=this.formatTimeSRT(q.startMs),E=this.formatTimeSRT(q.endMs),G=q.speakerLabel?`[${q.speakerLabel}] `:"";return`${A+1}
3
- ${R} --> ${E}
4
- ${G}${q.text}`}).join(`
5
-
6
- `)}toVTT(T){return`WEBVTT
7
-
8
- `+T.map((R,E)=>{let G=this.formatTimeVTT(R.startMs),w=this.formatTimeVTT(R.endMs),J=R.speakerLabel?`<v ${R.speakerLabel}>`:"";return`${E+1}
9
- ${G} --> ${w}
10
- ${J}${R.text}`}).join(`
11
-
12
- `)}formatTimeSRT(T){let q=Math.floor(T/3600000),A=Math.floor(T%3600000/60000),R=Math.floor(T%60000/1000),E=T%1000;return`${this.pad(q,2)}:${this.pad(A,2)}:${this.pad(R,2)},${this.pad(E,3)}`}formatTimeVTT(T){let q=Math.floor(T/3600000),A=Math.floor(T%3600000/60000),R=Math.floor(T%60000/1000),E=T%1000;return`${this.pad(q,2)}:${this.pad(A,2)}:${this.pad(R,2)}.${this.pad(E,3)}`}pad(T,q){return T.toString().padStart(q,"0")}}class ${stt;segmentSplitter=new U;diarizationMapper=new W;subtitleFormatter=new X;constructor(T){this.stt=T.stt}async transcribe(T){let q=z(),A=this.segmentSplitter.split(T.audio),R=[],E="",G=0,w=0;for(let Q of A){let Z=await this.stt.transcribe({audio:Q,language:T.language,diarize:T.diarize,speakerCount:T.speakerCount,wordTimestamps:!0,vocabularyHints:T.vocabularyHints}),j=Z.segments.map((L)=>({text:L.text,startMs:L.startMs+w,endMs:L.endMs+w,speakerId:L.speakerId,speakerName:L.speakerName,confidence:L.confidence}));R.push(...j),E+=(E?" ":"")+Z.text,G+=Z.durationMs,w+=Q.durationMs??0}let J=R,K;if(T.diarize){let Q=this.diarizationMapper.map(R);J=Q.segments,K=Q.speakers}let N={text:E,segments:J,language:T.language??"en",durationMs:G},Y,_=T.subtitleFormat??"none";if(_==="srt")Y=this.subtitleFormatter.toSRT(J);else if(_==="vtt")Y=this.subtitleFormatter.toVTT(J);return{id:q,transcript:N,subtitles:Y,speakers:K}}async*transcribeStream(T,q){if(!this.stt.transcribeStream)throw Error("Streaming transcription not supported by the current STT provider");let A=this.stt.transcribeStream(T,{language:q?.language,diarize:q?.diarize,speakerCount:q?.speakerCount,wordTimestamps:!0,vocabularyHints:q?.vocabularyHints});for await(let R of A)yield{text:R.text,startMs:R.startMs,endMs:R.endMs,speakerId:R.speakerId,speakerLabel:R.speakerName,confidence:R.confidence}}}function z(){let T=Date.now().toString(36),q=Math.random().toString(36).slice(2,8);return`stt_${T}_${q}`}export{$ as Transcriber,X as SubtitleFormatter,U as SegmentSplitter,W as DiarizationMapper};
@@ -1,19 +0,0 @@
1
- import type { AudioData } from '../types';
2
- /**
3
- * Split long audio into processable chunks.
4
- *
5
- * Useful for providers with maximum audio length limits.
6
- * Splits at silence boundaries when possible (approximated by byte position).
7
- */
8
- export declare class SegmentSplitter {
9
- /** Default maximum chunk duration in ms (5 minutes) */
10
- private static readonly DEFAULT_MAX_CHUNK_MS;
11
- /**
12
- * Split audio into chunks of at most maxChunkMs duration.
13
- *
14
- * @param audio - Input audio data
15
- * @param maxChunkMs - Maximum chunk duration in milliseconds
16
- */
17
- split(audio: AudioData, maxChunkMs?: number): AudioData[];
18
- private estimateDurationMs;
19
- }