#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (c) 2025-2026 Renato Xavier da Silveira Rosa # See [LICENSE](./LICENSE) or [BSD-3-Clause-Clear](https://spdx.org/licenses/BSD-3-Clause-Clear.html) # Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> """Generic TTS Backend for handling text-to-speech conversion. This module provides a generic TTS backend that can be used to convert text to speech using various TTS engines. It defines a `GenericTTSBackend` class that can be initialized with a specific backend command, language, and voice. The class provides methods to normalize paths, determine input and output flags based on the type of input/output (file or directory), build the command for the TTS engine, and run the command. Classes: GenericTTSBackend: Parameters: backend_cmd (str): Command or executable for the backend. language (Optional[str]): Language code for TTS output. voice (Optional[str]): Voice name for TTS output. Methods: _normalize_path(value: PathLike) -> Path: Normalize a string or Path to Path. gen_input_flag(input_path: Path) -> str: Return backend flag for input file or directory. gen_output_flag(output_path: Path) -> str: Return backend flag for output file or directory. _build_command(input_source: PathLike, output_dest: PathLike) -> list[str]: Build command list for subprocess. run(input_source: PathLike, output_dest: PathLike) -> subprocess.CompletedProcess: Execute backend command. """ import subprocess from pathlib import Path from typing import Optional, Union PathLike = Union[str, Path] from .vendor_utils import build_run_command class GenericTTSBackend: """Generic TTS backend for handling text-to-speech conversion. Parameters: backend_cmd (str): Command or executable for the backend. language (Optional[str]): Language code for TTS output. voice (Optional[str]): Voice name for TTS output. """ FLAGS = {} INTERMEDIATE_TXT = False DEFAULT_SPEAKER = None DEFAULT_SAMPLE = None SUPPORTED_AUDIO_FORMATS = { # subclasses may override this ".m4b": "m4b", ".wav": "wav", ".flac": "flac", ".mp3": "mp3", ".ogg": "ogg", } _INPUT_FLAG_MAP = lambda x: [ "--input-dir", x] if x else ["--input-file", x] _OUTPUT_FLAG_MAP = lambda x: [ "--output-dir", x] if x else ["--output-file", x] def __init__(self,**kwargs): for k,v in kwargs.items(): setattr(self, k, v) def _normalize_path(self, value: PathLike) -> Path: if isinstance(value, Path): return value.resolve() else: return Path(value).resolve() def gen_input_flag(self, input_path: Path) -> str: return self._INPUT_FLAG_MAP(self._normalize_path(input_path).is_dir()) def gen_output_flag(self, output_path: Path) -> str: return self._OUTPUT_FLAG_MAP(self._normalize_path(output_path).is_dir()) def _build_command(self, input_source: PathLike, output_dest: PathLike) -> list[str]: command = [self.backend_cmd] command.extend(self.gen_input_flag(input_source)) command.extend(self.gen_output_flag(output_dest)) for flag_name, var_name in self.FLAGS.items(): if getattr(self, flag_name, None) is not None: command.extend([ f"--{flag_name.replace('_', '-')}", getattr(self, var_name)]) return command def run(self, input_source: PathLike, output_dest: PathLike, **kwargs ) -> subprocess.CompletedProcess: completed = subprocess.run( self._build_command(input_source, output_dest), cwd=str(self._normalize_path(input_source).parent), check=True) if self.INTERMEDIATE_TXT: txt_file = self._normalize_path(input_source).with_suffix(".txt") if txt_file.exists(): completed = subprocess.run( self._build_command(txt_file, output_dest), cwd=str(self._normalize_path(input_source).parent), check=True ) return completed