| | |
| | try: |
| | import dotenv |
| | dotenv.load_dotenv() |
| | except ImportError: |
| | pass |
| |
|
| | import argparse |
| | import os |
| | import pysbd |
| | import queue |
| | import sys |
| | import tempfile |
| | import threading |
| | import shutil |
| | import sys |
| | import tempfile |
| | import contextlib |
| |
|
| | import openai |
| |
|
| | try: |
| | from playsound import playsound |
| | except ImportError: |
| | print("Error: missing required package 'playsound'. !pip install playsound") |
| | sys.exit(1) |
| |
|
| | @contextlib.contextmanager |
| | def tempdir(): |
| | path = tempfile.mkdtemp() |
| | try: |
| | yield path |
| | finally: |
| | try: |
| | shutil.rmtree(path) |
| | except IOError: |
| | sys.stderr.write('Failed to clean up temp dir {}'.format(path)) |
| |
|
| | class SimpleAudioPlayer: |
| | def __init__(self): |
| | self._queue = queue.Queue() |
| | self.running = True |
| | self._thread = threading.Thread(target=self.__play_audio_loop, daemon=True) |
| | self._thread.start() |
| |
|
| | def put(self, file): |
| | self._queue.put(file) |
| |
|
| | def stop(self): |
| | self.running = False |
| | self._thread.join() |
| | try: |
| | while True: |
| | file = self._queue.get_nowait() |
| | if os.path.exists(file): |
| | os.unlink(file) |
| | except queue.Empty as e: |
| | pass |
| |
|
| | def __play_audio_loop(self): |
| | while self.running: |
| | try: |
| | while True: |
| | file = self._queue.get(block=True, timeout=0.01) |
| |
|
| | try: |
| | playsound(file) |
| | finally: |
| | os.unlink(file) |
| |
|
| | except queue.Empty as e: |
| | continue |
| |
|
| | class OpenAI_tts: |
| | def __init__(self, model, voice, speed, base_dir): |
| | self.base_dir = base_dir |
| | self.openai_client = openai.OpenAI( |
| | |
| | |
| | api_key = os.environ.get("OPENAI_API_KEY", "sk-ip"), |
| | base_url = os.environ.get("OPENAI_BASE_URL", "http://localhost:8000/v1"), |
| | ) |
| | |
| | self.params = { |
| | 'model': model, |
| | 'voice': voice, |
| | 'speed': speed |
| | } |
| |
|
| | def speech_to_file(self, text: str) -> None: |
| | with self.openai_client.audio.speech.with_streaming_response.create( |
| | input=text, response_format='opus', **self.params |
| | ) as response: |
| | tf, output_filename = tempfile.mkstemp(suffix='.wav', prefix="audio_reader_", dir=self.base_dir) |
| | response.stream_to_file(output_filename) |
| | return output_filename |
| |
|
| |
|
| | if __name__ == "__main__": |
| | parser = argparse.ArgumentParser( |
| | description='Text to speech player', |
| | formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| |
|
| | parser.add_argument('-m', '--model', action='store', default="tts-1", help="The OpenAI model") |
| | parser.add_argument('-v', '--voice', action='store', default="alloy", help="The voice to use") |
| | parser.add_argument('-s', '--speed', action='store', default=1.0, help="How fast to read the audio") |
| |
|
| | args = parser.parse_args() |
| |
|
| | try: |
| | with tempdir() as base_dir: |
| | player = SimpleAudioPlayer() |
| | reader = OpenAI_tts(voice=args.voice, model=args.model, speed=args.speed, base_dir=base_dir) |
| | seg = pysbd.Segmenter(language='en', clean=True) |
| |
|
| | for raw_line in sys.stdin: |
| | for line in seg.segment(raw_line): |
| | if not line: |
| | continue |
| |
|
| | print(line) |
| | player.put(reader.speech_to_file(line)) |
| |
|
| | player.stop() |
| |
|
| | except KeyboardInterrupt: |
| | pass |
| |
|