| import { mkdtempSync, writeFileSync } from 'fs' |
| import { tmpdir } from 'os' |
| import path from 'node:path' |
|
|
| export type TTSResult = { |
| success: boolean |
| audioPath?: string |
| error?: string |
| } |
|
|
| export type TTSSpeakOptions = { |
| voice?: string |
| outputPath?: string |
| } |
|
|
| |
| |
| |
| |
| export async function speakWithEdgeTTS( |
| text: string, |
| options?: TTSSpeakOptions, |
| ): Promise<TTSResult> { |
| try { |
| const { EdgeTTS } = await import('node-edge-tts') |
| const audioPath = |
| options?.outputPath ?? |
| path.join( |
| mkdtempSync(path.join(tmpdir(), 'codev-tts-')), |
| `voice-${Date.now()}.mp3`, |
| ) |
| const tts = new EdgeTTS({ voice: options?.voice ?? 'en-US-JennyNeural' }) |
| await tts.ttsPromise(text, audioPath) |
| return { success: true, audioPath } |
| } catch (err) { |
| return { success: false, error: String(err) } |
| } |
| } |
|
|
| |
| export async function checkEdgeTTSAvailable(): Promise<boolean> { |
| try { |
| await import('node-edge-tts') |
| return true |
| } catch { |
| return false |
| } |
| } |
|
|
| |
| |
| |
| |
| export async function edgeTts(opts: { |
| text: string |
| voice?: string |
| }): Promise<{ success: boolean; audioPath?: string; error?: string }> { |
| return speakWithEdgeTTS(opts.text, { voice: opts.voice }) |
| } |
|
|
| |
| export function playAudioFile(filePath: string): Promise<void> { |
| return new Promise((resolve, reject) => { |
| const platform = process.platform |
| let cmd: string |
| let args: string[] |
|
|
| if (platform === 'darwin') { |
| cmd = 'afplay' |
| args = [filePath] |
| } else if (platform === 'linux') { |
| cmd = 'ffplay' |
| args = ['-nodisp', '-autoexit', '-infbuf', filePath] |
| } else if (platform === 'win32') { |
| cmd = 'start' |
| args = [filePath] |
| } else { |
| reject(new Error(`Unsupported platform: ${platform}`)) |
| return |
| } |
|
|
| const { spawn } = require('child_process') as typeof import('child_process') |
|
|
| if (platform === 'linux') { |
| const killProc = spawn('pkill', ['-9', 'ffplay'], { stdio: 'ignore' }) |
| killProc.on('close', () => { |
| setTimeout(() => { |
| const proc = spawn(cmd, args, { stdio: 'ignore' }) |
| proc.on('close', () => resolve()) |
| proc.on('error', () => resolve()) |
| }, 50) |
| }) |
| killProc.on('error', () => { |
| const proc = spawn(cmd, args, { stdio: 'ignore' }) |
| proc.on('close', () => resolve()) |
| proc.on('error', () => resolve()) |
| }) |
| } else { |
| const proc = spawn(cmd, args, { stdio: 'ignore' }) |
| proc.on('close', code => { |
| if (code === 0) resolve() |
| else reject(new Error(`Playback exited with code ${code}`)) |
| }) |
| proc.on('error', reject) |
| } |
| }) |
| } |
|
|