aboutsummaryrefslogtreecommitdiff
path: root/app/converter
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-09 15:55:06 -0400
committerhistoria <historiavg@proton.me>2026-09-09 15:55:06 -0400
commit71f2c85aa5ea2aa5fe5f31537f5979459e82fcdd (patch)
tree2bdb01e2d2e4d3bc334c9014cff4cffbb0bcda08 /app/converter
parent0157ce4a347f9625e1e9d09e2bbf0fbfad722557 (diff)
downloadtts-audiobook-generator-71f2c85aa5ea2aa5fe5f31537f5979459e82fcdd.tar.gz
feat: instructions options translated automatically by --instructions
Diffstat (limited to 'app/converter')
-rw-r--r--app/converter/clients/__init__.py1
-rw-r--r--app/converter/clients/audiocpp.py39
-rw-r--r--app/converter/config.py6
3 files changed, 38 insertions, 8 deletions
diff --git a/app/converter/clients/__init__.py b/app/converter/clients/__init__.py
index d2a7f8d..5f38786 100644
--- a/app/converter/clients/__init__.py
+++ b/app/converter/clients/__init__.py
@@ -83,6 +83,7 @@ __all__ = [
"AUDIOCPP_FAMILY_QWEN3_TTS", "AUDIOCPP_TASK_TTS", "AUDIOCPP_TASK_VDES",
"AUDIOCPP_SYNTHESIS_TASKS", "AUDIOCPP_VOICE_SPEAKER",
"AUDIOCPP_VOICE_CLONE", "AUDIOCPP_VOICE_DESIGN",
+ "AUDIOCPP_INSTRUCTION_FIELD", "AUDIOCPP_INSTRUCTION_OPTION",
"AUDIOCPP_CLONE_ONLY_FAMILIES", "AUDIOCPP_VOICE_REQUIRED",
"AUDIOCPP_VOICE_OPTIONAL", "AUDIOCPP_VOICE_NONE",
"AudioCppFamilyProfile", "AUDIOCPP_DEFAULT_FAMILY_PROFILE",
diff --git a/app/converter/clients/audiocpp.py b/app/converter/clients/audiocpp.py
index 98d64ba..578a13b 100644
--- a/app/converter/clients/audiocpp.py
+++ b/app/converter/clients/audiocpp.py
@@ -58,6 +58,14 @@ AUDIOCPP_VOICE_SPEAKER = "speaker" # built-in speaker name (Qwen CustomVoice)
AUDIOCPP_VOICE_CLONE = "clone" # server-side preset / voice_dir (Base, others)
AUDIOCPP_VOICE_DESIGN = "design" # voice described by --instructions (vdes)
+# How a family's speech endpoint consumes --instructions: the OpenAI-style
+# top-level "instructions" field (the default, read by most design/style
+# implementations), or the "instruction" request option inside the
+# "options" object (BreezeTTS 2: --request-option instruction=... and the
+# endpoint example send it there; its loader ignores the top-level field).
+AUDIOCPP_INSTRUCTION_FIELD = "field"
+AUDIOCPP_INSTRUCTION_OPTION = "option"
+
# HTTP error body fragments identifying deterministic request-configuration
# problems: the identical request will fail on every retry, so the chunk
# loop must give up immediately instead of burning its attempt budget.
@@ -531,13 +539,19 @@ class AudioCppFamilyProfile:
"""
def __init__(self, language_style: str = AUDIOCPP_LANG_OMIT,
- script_prefix: Optional[str] = None):
+ script_prefix: Optional[str] = None,
+ instruction_channel: str = AUDIOCPP_INSTRUCTION_FIELD):
self.language_style = language_style
# SCRIPT_PREFIX, when set, formats every request's text as one
# "<prefix>: text" script line (audiocpp_script_input): the
# family's server implementation parses the prompt as a
# speaker-script and silently drops unprefixed lines (VibeVoice).
self.script_prefix = script_prefix
+ # INSTRUCTION_CHANNEL picks where --instructions go: the OpenAI
+ # "instructions" field (AUDIOCPP_INSTRUCTION_FIELD) or the
+ # "instruction" request option (AUDIOCPP_INSTRUCTION_OPTION,
+ # BreezeTTS 2 — see its profile below).
+ self.instruction_channel = instruction_channel
# Generic profile for families not listed in AUDIOCPP_FAMILY_PROFILES:
@@ -563,6 +577,12 @@ AUDIOCPP_FAMILY_PROFILES = {
# client flattens each request into one Speaker-1 line (the server
# renormalizes the lowest speaker id to zero — the cloned reference).
"vibevoice": AudioCppFamilyProfile(script_prefix="Speaker 1"),
+ # BreezeTTS 2 (text design/clone/direction) reads the instruction as a
+ # request option, not the OpenAI "instructions" field: upstream's own
+ # endpoint example sends {"options": {"instruction": "..."}}. The
+ # model detects the language (zh/en) itself, so no language field.
+ "breeze_tts": AudioCppFamilyProfile(
+ instruction_channel=AUDIOCPP_INSTRUCTION_OPTION),
}
@@ -1191,14 +1211,23 @@ class AudioCppTTSClient(BaseTTSClient):
# audio.cpp has no negative "randomize" seed; a negative seed
# means "let the server randomize", so the field is omitted.
payload["seed"] = self._seed
- if self.instructions:
- # Explicit voice-design or style instruction (required for task
- # "vdes" entries; a Ctrl/style control on families that read it).
- payload["instructions"] = self.instructions
if self.request_options:
# Generic per-model controls (--option KEY=VALUE): forwarded
# verbatim; the model ignores keys it does not know.
payload["options"] = dict(self.request_options)
+ if self.instructions:
+ # Explicit voice-design or style instruction (required for task
+ # "vdes" entries; a voice/style control on families that read
+ # it). Families on the option channel take it as a request
+ # option; an explicit --option instruction=... from the caller
+ # lands here too and is never overwritten.
+ if self.profile.instruction_channel == AUDIOCPP_INSTRUCTION_OPTION:
+ if not (self.request_options or {}).get("instruction", ""):
+ options = dict(self.request_options)
+ options["instruction"] = self.instructions
+ payload["options"] = options
+ else:
+ payload["instructions"] = self.instructions
request = urllib.request.Request(
url, data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"}, method="POST")
diff --git a/app/converter/config.py b/app/converter/config.py
index 6fd65dd..da73427 100644
--- a/app/converter/config.py
+++ b/app/converter/config.py
@@ -12,14 +12,14 @@ CHUNK_SIZE = 250
# Where books are read from and where finished audiobooks are written.
# Relative paths resolve against the project root.
-INPUT_DIR = "/home/workhorse/projects/tts-audiobook-generator/input"
-OUTPUT_DIR = "/home/workhorse/projects/tts-audiobook-generator/output"
+INPUT_DIR = "input"
+OUTPUT_DIR = "output"
# Where voice-cloning reference .wavs live; the TUI Settings menu exposes
# this as "Clone .wav directory" and the qwen-tts Base / SGLang-Omni voice
# pickers list the .wav files found here. Relative paths resolve against
# the project root.
-CLONE_WAV_DIR = "/home/workhorse/downloads/git/tts-audiobook-generator/voices"
+CLONE_WAV_DIR = "voices"
# Output audiobook file at a different tempo.
SPEED = 1.0