import asyncio import edge_tts import subprocess import wave import contextlib from pathlib import Path from utils.config import EDGE_RATE, EDGE_PITCH, SAMPLE_RATE def wav_duration_seconds(path: Path) -> float: with contextlib.closing(wave.open(str(path), "rb")) as wf: return wf.getnframes() / float(wf.getframerate()) async def _edge_save_mp3(text: str, voice_name: str, out_mp3: Path, rate: str, pitch: str): comm = edge_tts.Communicate(text=text, voice=voice_name, rate=rate, pitch=pitch) await comm.save(str(out_mp3)) async def tts_to_wav(text: str, out_wav: Path, voice_name: str) -> None: """ Generates consistent PCM WAV (mono, SAMPLE_RATE) for easy concatenation. """ out_wav.parent.mkdir(parents=True, exist_ok=True) tmp_mp3 = out_wav.with_suffix(".tmp.mp3") await _edge_save_mp3(text, voice_name, tmp_mp3, EDGE_RATE, EDGE_PITCH) # Convert mp3 to wav using ffmpeg process = await asyncio.create_subprocess_exec( "ffmpeg", "-y", "-i", str(tmp_mp3), "-ac", "1", "-ar", str(SAMPLE_RATE), "-c:a", "pcm_s16le", str(out_wav), stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, stderr = await process.communicate() if process.returncode != 0: raise RuntimeError(f"FFmpeg failed with error:\\n{stderr.decode()}") try: tmp_mp3.unlink() except Exception: pass async def concat_wavs_by_timeline(wav_paths: list[Path], out_wav: Path): """ Concatenates multiple WAV files into a single WAV file. """ if not wav_paths: raise ValueError("No WAV files to concatenate.") concat_file = out_wav.with_suffix(".concat.txt") with concat_file.open("w", encoding="utf-8") as f: for p in wav_paths: # ffmpeg concat demuxer requires absolute paths or relative to the list file f.write(f"file '{str(p.resolve())}'\\n") process = await asyncio.create_subprocess_exec( "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat_file), "-ac", "1", "-ar", str(SAMPLE_RATE), "-c:a", "pcm_s16le", str(out_wav), stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, stderr = await process.communicate() if process.returncode != 0: raise RuntimeError(f"FFmpeg concatenation failed:\\n{stderr.decode()}") try: concat_file.unlink() except Exception: pass