feat: Refactor speaker configuration and UI components, enhance custom mix functionality, and improve styling for better usability

This commit is contained in:
JB
2025-10-09 10:12:11 -07:00
parent 63f9474741
commit da0c453205
6 changed files with 263 additions and 274 deletions
+32 -37
View File
@@ -43,16 +43,6 @@
<div class="alert alert--info">{{ notice }}</div>
{% endif %}
<div class="prepare-step-nav" data-role="prepare-step-nav">
<button type="button" class="button button--ghost{% if is_chapters %} is-active{% endif %}" data-step-target="chapters">Step 2 · Chapters</button>
<button type="button"
class="button button--ghost{% if is_speakers %} is-active{% endif %}"
data-step-target="speakers"
{% if not is_speakers %}disabled aria-disabled="true" data-state="locked"{% else %}data-state="unlocked"{% endif %}>
Step 3 · Speakers
</button>
</div>
<div class="prepare-steps" data-role="prepare-step-panels">
<section class="prepare-step" data-step-panel="chapters">
<header class="prepare-step__header">
@@ -198,16 +188,6 @@
{% if not speaker_configs %}disabled{% endif %}>
Apply preset
</button>
<button type="submit"
class="button button--ghost"
name="randomize_speaker_config"
value="1"
formaction="{{ url_for('web.analyze_pending_job', pending_id=pending.id) }}"
formmethod="post"
formnovalidate
{% if not speaker_configs %}disabled{% endif %}>
Randomize compatible voices
</button>
<label class="toggle-pill">
<input type="checkbox" name="save_speaker_config" value="1">
<span>Save roster updates back to preset</span>
@@ -274,9 +254,6 @@
{% if speaker.get('analysis_count') %}
<span class="badge badge--muted">{{ speaker.analysis_count }} lines · {{ speaker.analysis_confidence|default('low')|title }} confidence</span>
{% endif %}
{% if settings.speaker_random_languages %}
<span class="badge badge--muted">Randomizer: {{ settings.speaker_random_languages | map('upper') | join(', ') }}</span>
{% endif %}
</div>
<label class="speaker-list__field" for="speaker-{{ speaker_id }}-pronunciation">
<span>Pronunciation</span>
@@ -293,9 +270,11 @@
<select id="speaker-{{ speaker_id }}-voice"
name="speaker-{{ speaker_id }}-voice"
data-role="speaker-voice"
data-default-voice="{{ pending.voice }}"
{% if speaker.randomize %}disabled{% endif %}>
data-default-voice="{{ pending.voice }}">
<option value="" {% if not selected_voice %}selected{% endif %}>Use narrator voice ({{ pending.voice }})</option>
<option value="__custom_mix" data-role="custom-mix-option" {% if speaker.voice_formula %}selected{% else %}hidden disabled{% endif %}>
Custom mix
</option>
{% if speaker.recommended_voices %}
<optgroup label="Recommended">
{% for voice_id in speaker.recommended_voices[:6] %}
@@ -333,21 +312,38 @@
data-speaker-id="{{ speaker_id }}">
Generate voice
</button>
</div>
<div class="speaker-list__inline">
<label class="toggle-pill">
<input type="checkbox"
name="speaker-{{ speaker_id }}-randomize"
value="1"
data-role="randomize-toggle"
{% if speaker.randomize %}checked{% endif %}>
<span>Randomize compatible voice on analyze</span>
</label>
<button type="button"
class="button button--ghost button--small"
data-role="speaker-preview"
data-preview-kind="generated"
data-speaker-id="{{ speaker_id }}"
data-preview-text="{{ preview_text|e }}"
data-language="{{ pending.language }}"
data-voice="{{ speaker.voice_formula or selected_voice or pending.voice }}"
data-speed="{{ '%.2f'|format(pending.speed) }}"
data-use-gpu="{{ 'true' if pending.use_gpu else 'false' }}"
{% if not speaker.voice_formula %}hidden{% endif %}>
Preview generated
</button>
</div>
<div class="speaker-list__mix" data-role="speaker-mix" {% if not speaker.voice_formula %}hidden{% endif %}>
<span class="tag">Custom mix</span>
<span data-role="speaker-mix-label">{{ speaker.voice_formula or '' }}</span>
<button type="button" class="button button--ghost button--small" data-role="clear-mix">Clear</button>
<div class="speaker-list__mix-actions">
<button type="button"
class="button button--ghost button--small"
data-role="speaker-preview"
data-preview-context="mix"
data-speaker-id="{{ speaker_id }}"
data-preview-text="{{ preview_text|e }}"
data-language="{{ pending.language }}"
data-voice="{{ speaker.voice_formula or selected_voice or pending.voice }}"
data-speed="{{ '%.2f'|format(pending.speed) }}"
data-use-gpu="{{ 'true' if pending.use_gpu else 'false' }}">
Preview mix
</button>
<button type="button" class="button button--ghost button--small" data-role="clear-mix">Clear</button>
</div>
</div>
<input type="hidden" name="speaker-{{ speaker_id }}-formula" value="{{ speaker.voice_formula or '' }}" data-role="speaker-formula">
</div>
@@ -447,7 +443,6 @@
<button type="button" class="button button--ghost button--small" data-role="voice-modal-gender" data-value="f">Female</button>
<button type="button" class="button button--ghost button--small" data-role="voice-modal-gender" data-value="m">Male</button>
</div>
<button type="button" class="button button--ghost" data-role="voice-modal-random">Random blend</button>
<button type="button" class="button button--ghost" data-role="voice-modal-clear">Clear selection</button>
</aside>
<section class="voice-browser__catalog" aria-label="Voice catalog">
+3 -8
View File
@@ -2,7 +2,6 @@
{% macro speaker_row(row_key, speaker, options) %}
{% set gender = (speaker.gender or 'unknown') %}
{% set randomize = speaker.randomize %}
<div class="speaker-config-row" data-role="speaker-row" data-row-id="{{ row_key }}">
<input type="hidden" name="speaker_rows" value="{{ row_key }}">
<input type="hidden" name="speaker-{{ row_key }}-id" value="{{ speaker.id or row_key }}">
@@ -30,10 +29,6 @@
</label>
</div>
<div class="speaker-config-row__footer">
<label class="toggle-pill">
<input type="checkbox" name="speaker-{{ row_key }}-randomize" value="1" {% if randomize %}checked{% endif %}>
<span>Randomize compatible voice when applied</span>
</label>
<button type="button" class="button button--ghost button--small" data-action="remove-speaker">
Remove
</button>
@@ -60,7 +55,7 @@
</span>
</div>
<div class="card__title">Speaker presets</div>
<p class="card__subtitle">Store recurring casts, control randomization defaults, and keep voices consistent between books.</p>
<p class="card__subtitle">Store recurring casts and keep voices consistent between books.</p>
{% if message %}
<div class="alert alert--success">{{ message }}</div>
@@ -125,7 +120,7 @@
<h3>Speakers</h3>
<button type="button" class="button button--ghost button--small" data-action="add-speaker">Add speaker</button>
</div>
<p class="hint">Add each recurring character, set their gender, and decide whether to randomize a compatible voice automatically.</p>
<p class="hint">Add each recurring character, set their gender, and choose a preferred voice.</p>
<div class="speaker-config-rows__list" data-role="speaker-rows">
{% set speakers_map = editing.speakers or {} %}
{% if speakers_map %}
@@ -147,7 +142,7 @@
</div>
<template id="speaker-row-template">
{{ speaker_row('__ROW__', {'id': '', 'label': '', 'gender': 'unknown', 'voice': '', 'randomize': True}, options) | safe }}
{{ speaker_row('__ROW__', {'id': '', 'label': '', 'gender': 'unknown', 'voice': ''}, options) | safe }}
</template>
</section>
{% endblock %}