Files
epub-tts/src/epub_tts/tts_generic.py
T
2026-08-07 00:03:19 -03:00

125 lines
4.4 KiB
Python

#!/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