refactor: Remove speaker mode handling from various components

This commit is contained in:
JB
2025-10-12 06:59:39 -07:00
parent e6d2649d5d
commit 7d11ebc338
8 changed files with 28 additions and 127 deletions
+8 -54
View File
@@ -106,13 +106,7 @@ _CHUNK_LEVEL_OPTIONS = [
{"value": "sentence", "label": "Sentences"},
]
_SPEAKER_MODE_OPTIONS = [
{"value": "single", "label": "Single Speaker"},
{"value": "multi", "label": "Multi-Speaker"},
]
_CHUNK_LEVEL_VALUES = {option["value"] for option in _CHUNK_LEVEL_OPTIONS}
_SPEAKER_MODE_VALUES = {option["value"] for option in _SPEAKER_MODE_OPTIONS}
_DEFAULT_ANALYSIS_THRESHOLD = 3
@@ -835,7 +829,6 @@ def _prepare_speaker_metadata(
chapters: List[Dict[str, Any]],
chunks: List[Dict[str, Any]],
analysis_chunks: Optional[List[Dict[str, Any]]] = None,
speaker_mode: str,
voice: str,
voice_profile: Optional[str],
threshold: int,
@@ -848,7 +841,7 @@ def _prepare_speaker_metadata(
chunk_list = [dict(chunk) for chunk in chunks]
analysis_source = [dict(chunk) for chunk in (analysis_chunks or chunks)]
threshold_value = max(1, int(threshold))
analysis_enabled = speaker_mode == "multi" and run_analysis
analysis_enabled = run_analysis
settings_state = _load_settings()
global_random_languages = [
code
@@ -1323,10 +1316,7 @@ def _apply_prepare_form(
pending.chunk_level = raw_chunk_level
chunk_level_literal = cast(ChunkLevel, pending.chunk_level)
raw_speaker_mode = (form.get("speaker_mode") or pending.speaker_mode or "single").strip().lower()
if raw_speaker_mode not in _SPEAKER_MODE_VALUES:
raw_speaker_mode = "single"
pending.speaker_mode = raw_speaker_mode
pending.speaker_mode = "single"
pending.generate_epub3 = _coerce_bool(form.get("generate_epub3"), False)
@@ -1660,13 +1650,12 @@ def _template_options() -> Dict[str, Any]:
"voice_profiles": ordered_profiles,
"voice_profile_options": profile_options,
"separate_formats": ["wav", "flac", "mp3", "opus"],
"voice_catalog": voice_catalog,
"voice_catalog_map": {entry["id"]: entry for entry in voice_catalog},
"voice_catalog": voice_catalog,
"voice_catalog_map": {entry["id"]: entry for entry in voice_catalog},
"sample_voice_texts": SAMPLE_VOICE_TEXTS,
"voice_profiles_data": profiles,
"speaker_configs": list_configs(),
"chunk_levels": _CHUNK_LEVEL_OPTIONS,
"speaker_modes": _SPEAKER_MODE_OPTIONS,
"speaker_analysis_threshold": current_settings.get(
"speaker_analysis_threshold", _DEFAULT_ANALYSIS_THRESHOLD
),
@@ -1718,7 +1707,6 @@ def _settings_defaults() -> Dict[str, Any]:
"chapter_intro_delay": 0.5,
"max_subtitle_words": 50,
"chunk_level": "paragraph",
"speaker_mode": "single",
"generate_epub3": False,
"speaker_analysis_threshold": _DEFAULT_ANALYSIS_THRESHOLD,
"speaker_pronunciation_sentence": "This is {{name}} speaking.",
@@ -1788,10 +1776,6 @@ def _normalize_setting_value(key: str, value: Any, defaults: Dict[str, Any]) ->
if isinstance(value, str) and value in _CHUNK_LEVEL_VALUES:
return value
return defaults[key]
if key == "speaker_mode":
if isinstance(value, str) and value in _SPEAKER_MODE_VALUES:
return value
return defaults[key]
if key == "speaker_random_languages":
if isinstance(value, (list, tuple, set)):
return [code for code in value if isinstance(code, str) and code in LANGUAGE_DESCRIPTIONS]
@@ -1995,9 +1979,6 @@ def settings_page() -> ResponseReturnValue:
updated["chunk_level"] = _normalize_setting_value(
"chunk_level", form.get("chunk_level"), defaults
)
updated["speaker_mode"] = _normalize_setting_value(
"speaker_mode", form.get("speaker_mode"), defaults
)
updated["separate_chapters_format"] = _normalize_setting_value(
"separate_chapters_format", form.get("separate_chapters_format"), defaults
)
@@ -2744,11 +2725,7 @@ def enqueue_job() -> ResponseReturnValue:
chunk_level_value = raw_chunk_level
chunk_level_literal = cast(ChunkLevel, chunk_level_value)
speaker_mode_default = str(settings.get("speaker_mode", "single")).strip().lower()
raw_speaker_mode = (request.form.get("speaker_mode") or speaker_mode_default).strip().lower()
if raw_speaker_mode not in _SPEAKER_MODE_VALUES:
raw_speaker_mode = "single"
speaker_mode_value = raw_speaker_mode
speaker_mode_value = "single"
generate_epub3_default = bool(settings.get("generate_epub3", False))
generate_epub3 = _coerce_bool(request.form.get("generate_epub3"), generate_epub3_default)
@@ -2764,12 +2741,11 @@ def enqueue_job() -> ResponseReturnValue:
maximum=25,
)
initial_analysis = speaker_mode_value == "multi"
initial_analysis = False
processed_chunks, speakers, analysis_payload, config_languages, _ = _prepare_speaker_metadata(
chapters=selected_chapter_sources,
chunks=raw_chunks,
analysis_chunks=analysis_chunks,
speaker_mode=speaker_mode_value,
voice=voice,
voice_profile=selected_profile or None,
threshold=analysis_threshold,
@@ -2868,20 +2844,6 @@ def analyze_pending_job(pending_id: str) -> ResponseReturnValue:
)
abort(400, message)
if pending.speaker_mode != "multi":
setattr(pending, "analysis_requested", False)
pending.chunks = []
pending.speaker_analysis = {}
error_message = "Switch to multi-speaker mode to analyze speakers."
if _wants_wizard_json():
return _wizard_json_response(
pending,
"chapters",
error=error_message,
status=400,
)
abort(400, error_message)
if not enabled_overrides:
setattr(pending, "analysis_requested", False)
pending.chunks = []
@@ -2911,7 +2873,6 @@ def analyze_pending_job(pending_id: str) -> ResponseReturnValue:
chapters=enabled_overrides,
chunks=raw_chunks,
analysis_chunks=analysis_chunks,
speaker_mode=pending.speaker_mode,
voice=pending.voice,
voice_profile=pending.voice_profile,
threshold=pending.speaker_analysis_threshold,
@@ -2981,9 +2942,6 @@ def finalize_job(pending_id: str) -> ResponseReturnValue:
)
abort(400, message)
if pending.speaker_mode != "multi":
setattr(pending, "analysis_requested", False)
if not enabled_overrides:
pending.chunks = []
error_message = "Select at least one chapter to convert."
@@ -3003,9 +2961,8 @@ def finalize_job(pending_id: str) -> ResponseReturnValue:
normalized_step = _normalize_wizard_step(active_step, pending)
raw_chunks = build_chunks_for_chapters(enabled_overrides, level=chunk_level_literal)
analysis_chunks = build_chunks_for_chapters(enabled_overrides, level="sentence")
is_multi = pending.speaker_mode == "multi"
analysis_requested = bool(getattr(pending, "analysis_requested", False))
should_force_entities = is_multi and normalized_step != "entities"
should_force_entities = analysis_requested and normalized_step != "entities"
if analysis_requested:
existing_roster: Optional[Mapping[str, Any]] = pending.speakers
@@ -3019,12 +2976,11 @@ def finalize_job(pending_id: str) -> ResponseReturnValue:
config_name = pending.applied_speaker_config or selected_config
speaker_config_payload = get_config(config_name) if config_name else None
run_analysis = is_multi and (should_force_entities or analysis_requested)
run_analysis = should_force_entities or analysis_requested
processed_chunks, roster, analysis_payload, config_languages, updated_config = _prepare_speaker_metadata(
chapters=enabled_overrides,
chunks=raw_chunks,
analysis_chunks=analysis_chunks,
speaker_mode=pending.speaker_mode,
voice=pending.voice,
voice_profile=pending.voice_profile,
threshold=pending.speaker_analysis_threshold,
@@ -3183,8 +3139,6 @@ def _normalize_wizard_step(step: Optional[str], pending: Optional[PendingJob] =
chosen = normalized
else:
chosen = default_step
if chosen == "entities" and pending is not None and pending.speaker_mode != "multi":
return "chapters"
return chosen
-32
View File
@@ -474,38 +474,6 @@ const initPrepare = (root = document) => {
});
});
const speakerModeSelect = form.querySelector("#speaker_mode");
const analysisToggleButtons = Array.from(form.querySelectorAll('[data-step-toggle="analysis"]'));
const finalizeToggleButtons = Array.from(form.querySelectorAll('[data-step-toggle="finalize"]'));
const setButtonVisibility = (button, isVisible) => {
if (!button) return;
if (isVisible) {
button.hidden = false;
button.removeAttribute("aria-hidden");
button.removeAttribute("tabindex");
} else {
button.hidden = true;
button.setAttribute("aria-hidden", "true");
button.setAttribute("tabindex", "-1");
}
};
const updateStepButtons = () => {
if (!speakerModeSelect) {
return;
}
const modeValue = (speakerModeSelect.value || "").toLowerCase();
const isMulti = modeValue === "multi";
analysisToggleButtons.forEach((button) => setButtonVisibility(button, isMulti));
finalizeToggleButtons.forEach((button) => setButtonVisibility(button, !isMulti));
};
if (speakerModeSelect) {
updateStepButtons();
speakerModeSelect.addEventListener("change", updateStepButtons);
}
const voiceModal = document.querySelector('[data-role="voice-modal"]');
let activeGenderFilter = "";
+11
View File
@@ -273,7 +273,18 @@ const submitWizardForm = async (form, submitter) => {
}
const action = submitter?.getAttribute("formaction") || form.getAttribute("action") || window.location.href;
const method = (submitter?.getAttribute("formmethod") || form.getAttribute("method") || "GET").toUpperCase();
const stepTarget = submitter?.dataset?.stepTarget || "";
const normalizedStepTarget = stepTarget ? stepTarget.toLowerCase() : "";
if (normalizedStepTarget) {
const activeInput = form.querySelector('[data-role="active-step-input"]');
if (activeInput) {
activeInput.value = normalizedStepTarget;
}
}
const formData = new FormData(form);
if (normalizedStepTarget) {
formData.set("active_step", normalizedStepTarget);
}
if (submitter && submitter.name && !formData.has(submitter.name)) {
formData.append(submitter.name, submitter.value ?? "");
}
-1
View File
@@ -27,7 +27,6 @@
<li><strong>Max words per subtitle:</strong> {{ job.max_subtitle_words }}</li>
<li><strong>Project folder:</strong> {{ 'Yes' if job.save_as_project else 'No' }}</li>
<li><strong>Chunk granularity:</strong> {{ job.chunk_level|replace('_', ' ')|title }}</li>
<li><strong>Speaker mode:</strong> {{ job.speaker_mode|replace('_', ' ')|title }}</li>
<li><strong>Speaker analysis threshold:</strong> {{ job.speaker_analysis_threshold }}</li>
<li><strong>Generate EPUB 3:</strong> {{ 'Yes' if job.generate_epub3 else 'No' }}</li>
</ul>
@@ -31,14 +31,6 @@
{% else %}
{% set generate_epub3 = pending.generate_epub3 if pending else settings_dict.get('generate_epub3', False) %}
{% endif %}
{% set speaker_mode_value = form_values.get('speaker_mode') if form_values else None %}
{% if not speaker_mode_value %}
{% if pending and pending.speaker_mode %}
{% set speaker_mode_value = pending.speaker_mode %}
{% else %}
{% set speaker_mode_value = settings_dict.get('speaker_mode', 'single') %}
{% endif %}
{% endif %}
{% set chunk_level_value = form_values.get('chunk_level') if form_values else None %}
{% if not chunk_level_value %}
{% if pending and pending.chunk_level %}
@@ -247,25 +239,15 @@
<section class="form-section">
<h3 class="form-section__title">Entities &amp; casting</h3>
<div class="form-section__layout">
<div class="field-grid field-grid--two">
<div class="field field--stack">
<label for="speaker_config">Speaker preset</label>
<select id="speaker_config" name="speaker_config" {{ 'disabled' if readonly else '' }}>
<option value="">None</option>
{% for config in options.speaker_configs %}
<option value="{{ config.name }}" {% if selected_config == config.name %}selected{% endif %}>{{ config.name }} · {{ config.speakers|length }} speaker{% if config.speakers|length != 1 %}s{% endif %}</option>
{% endfor %}
</select>
<p class="hint">Reuse a saved roster to keep character voices consistent.</p>
</div>
<div class="field">
<label for="speaker_mode">Speaker mode</label>
<select id="speaker_mode" name="speaker_mode" {{ 'disabled' if readonly else '' }}>
{% for option in options.speaker_modes %}
<option value="{{ option.value }}" {% if speaker_mode_value == option.value %}selected{% endif %}>{{ option.label }}</option>
{% endfor %}
</select>
</div>
<div class="field field--stack">
<label for="speaker_config">Speaker preset</label>
<select id="speaker_config" name="speaker_config" {{ 'disabled' if readonly else '' }}>
<option value="">None</option>
{% for config in options.speaker_configs %}
<option value="{{ config.name }}" {% if selected_config == config.name %}selected{% endif %}>{{ config.name }} · {{ config.speakers|length }} speaker{% if config.speakers|length != 1 %}s{% endif %}</option>
{% endfor %}
</select>
<p class="hint">Reuse a saved roster to keep character voices consistent.</p>
</div>
<div class="field-grid field-grid--two">
<div class="field">
@@ -1,4 +1,3 @@
{% set is_multi_speaker = pending.speaker_mode == 'multi' %}
<form method="post"
action="{{ url_for('web.finalize_job', pending_id=pending.id) }}"
class="prepare-form"
@@ -7,12 +6,10 @@
data-wizard-form="true"
data-step="chapters"
data-pending-id="{{ pending.id }}"
data-speaker-mode="{{ pending.speaker_mode }}"
data-analyze-url="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}">
<input type="hidden" name="active_step" value="chapters" data-role="active-step-input">
<div class="wizard-hidden-inputs" aria-hidden="true">
<input type="hidden" name="chunk_level" value="{{ pending.chunk_level }}">
<input type="hidden" name="speaker_mode" value="{{ pending.speaker_mode }}">
<input type="hidden" name="speaker_analysis_threshold" value="{{ pending.speaker_analysis_threshold }}">
<input type="hidden" name="chapter_intro_delay" value="{{ '%.2f'|format(pending.chapter_intro_delay) }}">
{% if pending.generate_epub3 %}
@@ -1,4 +1,3 @@
{% set is_multi_speaker = pending.speaker_mode == 'multi' %}
{% set embed_scripts = embed_scripts if embed_scripts is defined else True %}
<form method="post"
action="{{ url_for('web.finalize_job', pending_id=pending.id) }}"
@@ -8,7 +7,6 @@
data-wizard-form="true"
data-step="entities"
data-pending-id="{{ pending.id }}"
data-speaker-mode="{{ pending.speaker_mode }}"
data-analyze-url="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}"
data-entities-url="{{ url_for('api.api_pending_entities', pending_id=pending.id) }}"
data-manual-list-url="{{ url_for('api.api_list_manual_overrides', pending_id=pending.id) }}"
-8
View File
@@ -23,14 +23,6 @@
</select>
<p class="hint">Used whenever “Standard voice” is selected for a new job.</p>
</div>
<div class="field">
<label for="speaker_mode_default">Speaker Mode</label>
<select id="speaker_mode_default" name="speaker_mode">
{% for option in options.speaker_modes %}
<option value="{{ option.value }}" {% if settings.speaker_mode == option.value %}selected{% endif %}>{{ option.label }}</option>
{% endfor %}
</select>
</div>
<div class="field">
<label for="chunk_level_default">Chunk Granularity</label>
<select id="chunk_level_default" name="chunk_level">