Changes to be committed:
new file: AGENTS.md modified: pyproject.toml modified: src/epub_tts/__main__.py new file: src/epub_tts/tts_backend.py
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
# Instructions to LLM Agents
|
||||||
|
|
||||||
|
This is a Python project designed to be run in a separate virtual environment with Python version >=3.11 nad its main objective is to convert any epub file without DRM to an audiobook with text-to-speech tools available on Github, Hugging Face or publicy available.
|
||||||
|
|
||||||
|
You may search for new tools to use, as long as they are kept as a separate package, preferrably as a git submodule under `./src/vendor/<repository-name>` and creating an interface inside `./src/epub_tts/<package-name>.py` to call the package in its own virtual environment, using python's builtin `subprocess.call()` or `subprocess.run()`.
|
||||||
|
|
||||||
|
If you add such a new tool, include a new item to the `backend` dictionary inside `./src/epub_tts/main.py`.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
- For every python file created, add the following 4 lines to its top and follow it with your attribution string, authorship and then with a short docstring describing the file's purpose.
|
||||||
|
```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)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package source code
|
||||||
|
- This project is called `epub-tts` and it is a Python>=3.11 package available under the folder [src/epub_tts](./src/epub_tts)
|
||||||
|
- Parse metadata inside [pyproject.toml](./pyproject.toml), including coding style configuration for `ruff`
|
||||||
|
- The subfolders under [src/vendor](./src/vendor) are external packages required for this project
|
||||||
|
|
||||||
|
## Dev environment tips
|
||||||
|
- Use `pip install '.[dev]'` to install package in editable mode with optional dependencies.
|
||||||
|
|
||||||
|
## PR instructions
|
||||||
|
- Title format: `[epub-tts] <Title>`
|
||||||
|
- Always run `ruff check --fix ./src/epub_tts` and `ruff format ./src/epub_tts` before committing.
|
||||||
|
- Write clear commit messages stating a general description of changes.
|
||||||
|
- Do not change any version or git tags, because they are handled automaticaly by `setuptools-scm`
|
||||||
|
|
||||||
|
## Operating Model
|
||||||
|
- Ask quick clarifying questions if versions, entry points, or expected behaviors are ambiguous.
|
||||||
|
- Prefer small, incremental changes with brief plans for multi-step work.
|
||||||
|
- Do not commit secrets, especially .env files or any TOKENS, APITOKENS, APIKEYS or KEYS in general;
|
||||||
|
- avoid committing large build artifacts unless requested.
|
||||||
|
|
||||||
|
|
||||||
|
## Typical Tasks
|
||||||
|
- Implement features, fix bugs, run tests, and update docs as requested by the user.
|
||||||
|
- Keep the environment reproducible and synced with the declared requirements.
|
||||||
+1
-1
@@ -100,7 +100,7 @@ exclude = [
|
|||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
# select = [...] # See the Default Rules page for the full listing.
|
# select = [...] # See the Default Rules page for the full listing.
|
||||||
ignore = []
|
ignore = ["E501"]
|
||||||
|
|
||||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
fixable = ["ALL"]
|
fixable = ["ALL"]
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
from epub_tts import tts_backend
|
||||||
|
from epub_tts import tts_edge
|
||||||
|
|
||||||
|
backend = {
|
||||||
|
# keys are the backend names, values are the corresponding TTS classes
|
||||||
|
"default": tts_backend.GenericTTSBackend,
|
||||||
|
"edge": tts_edge.TTSEdge,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
p = ArgumentParser(description="Convert EPUB to audio using TTS")
|
p = ArgumentParser(description="Convert EPUB to audio using TTS")
|
||||||
@@ -20,10 +29,15 @@ def main(args=None):
|
|||||||
p.add_argument("-o", "--output", help="Output audio file", required=True)
|
p.add_argument("-o", "--output", help="Output audio file", required=True)
|
||||||
p.add_argument("-l", "--language", help="Language to use for TTS")
|
p.add_argument("-l", "--language", help="Language to use for TTS")
|
||||||
p.add_argument("-v", "--voice", help="Voice to use for TTS")
|
p.add_argument("-v", "--voice", help="Voice to use for TTS")
|
||||||
|
p.add_argument("-b", "--backend",
|
||||||
|
help="Backend to use for TTS",
|
||||||
|
default="default",
|
||||||
|
choices=backend.keys())
|
||||||
args = p.parse_args(args or sys.argv[1:])
|
args = p.parse_args(args or sys.argv[1:])
|
||||||
print(
|
print(
|
||||||
f"Converting {args.input} to {args.output} using voice {args.voice}, language {args.language}, ..."
|
f"Converting {args.input} to {args.output} using voice "
|
||||||
|
f"{args.voice}, language {args.language}, "
|
||||||
|
f"backend {args.backend}, ..."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#!/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)
|
||||||
|
"""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.
|
||||||
|
_input_flag(input_path: Path) -> str: Return backend flag for input file or directory.
|
||||||
|
_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]
|
||||||
|
|
||||||
|
|
||||||
|
class GenericTTSBackend:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
backend_cmd: str,
|
||||||
|
language: Optional[str] = None,
|
||||||
|
voice: Optional[str] = None,
|
||||||
|
):
|
||||||
|
self.backend_cmd = backend_cmd
|
||||||
|
self.language = language
|
||||||
|
self.voice = voice
|
||||||
|
|
||||||
|
def _normalize_path(self, value: PathLike) -> Path:
|
||||||
|
if isinstance(value, Path):
|
||||||
|
return value
|
||||||
|
return Path(value)
|
||||||
|
|
||||||
|
def _input_flag(self, input_path: Path) -> str:
|
||||||
|
return "--input-dir" if input_path.is_dir() else "--input-file"
|
||||||
|
|
||||||
|
def _output_flag(self, output_path: Path) -> str:
|
||||||
|
return "--output-dir" if output_path.is_dir() else "--output-file"
|
||||||
|
|
||||||
|
def _build_command(
|
||||||
|
self, input_source: PathLike, output_dest: PathLike
|
||||||
|
) -> list[str]:
|
||||||
|
input_path = self._normalize_path(input_source)
|
||||||
|
output_path = self._normalize_path(output_dest)
|
||||||
|
|
||||||
|
command = [
|
||||||
|
self.backend_cmd,
|
||||||
|
self._input_flag(input_path),
|
||||||
|
str(input_path),
|
||||||
|
self._output_flag(output_path),
|
||||||
|
str(output_path),
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.language:
|
||||||
|
command.extend(["--language", self.language])
|
||||||
|
if self.voice:
|
||||||
|
command.extend(["--voice", self.voice])
|
||||||
|
|
||||||
|
return command
|
||||||
|
|
||||||
|
def run(
|
||||||
|
self, input_source: PathLike, output_dest: PathLike
|
||||||
|
) -> subprocess.CompletedProcess:
|
||||||
|
command = self._build_command(input_source, output_dest)
|
||||||
|
return subprocess.run(command, check=True)
|
||||||
Reference in New Issue
Block a user