mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Implement normalization settings UI with dynamic groups and options
This commit is contained in:
@@ -802,6 +802,34 @@ def build_pending_job_from_extraction(
|
|||||||
apply_config=bool(speaker_config_payload),
|
apply_config=bool(speaker_config_payload),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _extract_checkbox(name: str, default: bool) -> bool:
|
||||||
|
values: List[str] = []
|
||||||
|
getter = getattr(form, "getlist", None)
|
||||||
|
if callable(getter):
|
||||||
|
raw_values = getter(name)
|
||||||
|
if raw_values:
|
||||||
|
values = list(cast(Iterable[str], raw_values))
|
||||||
|
else:
|
||||||
|
raw_flag = form.get(name)
|
||||||
|
if raw_flag is not None:
|
||||||
|
values = [raw_flag]
|
||||||
|
if values:
|
||||||
|
return coerce_bool(values[-1], default)
|
||||||
|
return default
|
||||||
|
|
||||||
|
normalization_overrides = {}
|
||||||
|
for key in _NORMALIZATION_BOOLEAN_KEYS:
|
||||||
|
default_val = bool(settings.get(key, True))
|
||||||
|
normalization_overrides[key] = _extract_checkbox(key, default_val)
|
||||||
|
|
||||||
|
for key in _NORMALIZATION_STRING_KEYS:
|
||||||
|
default_val = str(settings.get(key, ""))
|
||||||
|
val = form.get(key)
|
||||||
|
if val is not None:
|
||||||
|
normalization_overrides[key] = str(val)
|
||||||
|
else:
|
||||||
|
normalization_overrides[key] = default_val
|
||||||
|
|
||||||
pending = PendingJob(
|
pending = PendingJob(
|
||||||
id=uuid.uuid4().hex,
|
id=uuid.uuid4().hex,
|
||||||
original_filename=original_name,
|
original_filename=original_name,
|
||||||
@@ -826,10 +854,7 @@ def build_pending_job_from_extraction(
|
|||||||
max_subtitle_words=max_subtitle_words,
|
max_subtitle_words=max_subtitle_words,
|
||||||
metadata_tags=metadata_tags,
|
metadata_tags=metadata_tags,
|
||||||
chapters=chapters_payload,
|
chapters=chapters_payload,
|
||||||
normalization_overrides={
|
normalization_overrides=normalization_overrides,
|
||||||
**{key: bool(settings.get(key, True)) for key in _NORMALIZATION_BOOLEAN_KEYS},
|
|
||||||
**{key: str(settings.get(key, "")) for key in _NORMALIZATION_STRING_KEYS},
|
|
||||||
},
|
|
||||||
created_at=time.time(),
|
created_at=time.time(),
|
||||||
cover_image_path=cover_path,
|
cover_image_path=cover_path,
|
||||||
cover_image_mime=cover_mime,
|
cover_image_mime=cover_mime,
|
||||||
|
|||||||
@@ -100,6 +100,38 @@ BOOLEAN_SETTINGS = {
|
|||||||
FLOAT_SETTINGS = {"silence_between_chapters", "chapter_intro_delay", "llm_timeout"}
|
FLOAT_SETTINGS = {"silence_between_chapters", "chapter_intro_delay", "llm_timeout"}
|
||||||
INT_SETTINGS = {"max_subtitle_words", "speaker_analysis_threshold"}
|
INT_SETTINGS = {"max_subtitle_words", "speaker_analysis_threshold"}
|
||||||
|
|
||||||
|
_NORMALIZATION_GROUPS = [
|
||||||
|
{
|
||||||
|
"label": "General Rules",
|
||||||
|
"options": [
|
||||||
|
{"key": "normalization_numbers", "label": "Convert grouped numbers to words"},
|
||||||
|
{"key": "normalization_currency", "label": "Convert currency symbols ($10 → ten dollars)"},
|
||||||
|
{"key": "normalization_titles", "label": "Expand titles and suffixes (Dr., St., Jr., …)"},
|
||||||
|
{"key": "normalization_footnotes", "label": "Remove footnote indicators ([1], [2])"},
|
||||||
|
{"key": "normalization_terminal", "label": "Ensure sentences end with terminal punctuation"},
|
||||||
|
{"key": "normalization_caps_quotes", "label": "Convert ALL CAPS dialogue inside quotes"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Apostrophes & Contractions",
|
||||||
|
"options": [
|
||||||
|
{"key": "normalization_apostrophes_contractions", "label": "Expand contractions (it's → it is)"},
|
||||||
|
{"key": "normalization_apostrophes_plural_possessives", "label": "Collapse plural possessives (dogs' → dogs)"},
|
||||||
|
{"key": "normalization_apostrophes_sibilant_possessives", "label": "Mark sibilant possessives (boss's → boss + IZ marker)"},
|
||||||
|
{"key": "normalization_apostrophes_decades", "label": "Expand decades ('90s → 1990s)"},
|
||||||
|
{"key": "normalization_apostrophes_leading_elisions", "label": "Expand leading elisions ('tis → it is)"},
|
||||||
|
{"key": "normalization_phoneme_hints", "label": "Add phoneme hints for possessives"},
|
||||||
|
{"key": "normalization_contraction_aux_be", "label": "Expand auxiliary 'be' (I'm → I am)"},
|
||||||
|
{"key": "normalization_contraction_aux_have", "label": "Expand auxiliary 'have' (I've → I have)"},
|
||||||
|
{"key": "normalization_contraction_modal_will", "label": "Expand modal 'will' (I'll → I will)"},
|
||||||
|
{"key": "normalization_contraction_modal_would", "label": "Expand modal 'would' (I'd → I would)"},
|
||||||
|
{"key": "normalization_contraction_negation_not", "label": "Expand negation 'not' (don't → do not)"},
|
||||||
|
{"key": "normalization_contraction_let_us", "label": "Expand 'let's' → let us"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def integration_defaults() -> Dict[str, Dict[str, Any]]:
|
def integration_defaults() -> Dict[str, Dict[str, Any]]:
|
||||||
return {
|
return {
|
||||||
"calibre_opds": {
|
"calibre_opds": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import numpy as np
|
|||||||
|
|
||||||
from abogen.speaker_configs import slugify_label
|
from abogen.speaker_configs import slugify_label
|
||||||
from abogen.speaker_analysis import analyze_speakers
|
from abogen.speaker_analysis import analyze_speakers
|
||||||
from abogen.web.routes.utils.settings import load_settings, settings_defaults, _DEFAULT_ANALYSIS_THRESHOLD, _CHUNK_LEVEL_OPTIONS, _APOSTROPHE_MODE_OPTIONS
|
from abogen.web.routes.utils.settings import load_settings, settings_defaults, _DEFAULT_ANALYSIS_THRESHOLD, _CHUNK_LEVEL_OPTIONS, _APOSTROPHE_MODE_OPTIONS, _NORMALIZATION_GROUPS
|
||||||
from abogen.web.routes.utils.common import split_profile_spec
|
from abogen.web.routes.utils.common import split_profile_spec
|
||||||
from abogen.voice_profiles import (
|
from abogen.voice_profiles import (
|
||||||
load_profiles,
|
load_profiles,
|
||||||
@@ -605,6 +605,7 @@ def template_options() -> Dict[str, Any]:
|
|||||||
"speaker_pronunciation_sentence", settings_defaults()["speaker_pronunciation_sentence"]
|
"speaker_pronunciation_sentence", settings_defaults()["speaker_pronunciation_sentence"]
|
||||||
),
|
),
|
||||||
"apostrophe_modes": _APOSTROPHE_MODE_OPTIONS,
|
"apostrophe_modes": _APOSTROPHE_MODE_OPTIONS,
|
||||||
|
"normalization_groups": _NORMALIZATION_GROUPS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -480,19 +480,6 @@ const initDashboard = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectFirstProfileIfAvailable = () => {
|
|
||||||
if (!profileSelect) return false;
|
|
||||||
const saved = Array.from(profileSelect.options).filter(
|
|
||||||
(option) => option.value && option.value !== "__standard" && option.value !== "__formula",
|
|
||||||
);
|
|
||||||
if (!saved.length) {
|
|
||||||
profileSelect.value = "__standard";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
saved[0].selected = true;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const applySavedProfile = (option) => {
|
const applySavedProfile = (option) => {
|
||||||
if (!option) return;
|
if (!option) return;
|
||||||
const presetFormula = option.dataset.formula || "";
|
const presetFormula = option.dataset.formula || "";
|
||||||
@@ -562,15 +549,11 @@ const initDashboard = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (profileSelect) {
|
if (profileSelect) {
|
||||||
const hasSaved = selectFirstProfileIfAvailable();
|
|
||||||
if (profileSelect.dataset.dashboardBound !== "true") {
|
if (profileSelect.dataset.dashboardBound !== "true") {
|
||||||
profileSelect.dataset.dashboardBound = "true";
|
profileSelect.dataset.dashboardBound = "true";
|
||||||
profileSelect.addEventListener("change", updateVoiceControls);
|
profileSelect.addEventListener("change", updateVoiceControls);
|
||||||
}
|
}
|
||||||
updateVoiceControls();
|
updateVoiceControls();
|
||||||
if (!hasSaved) {
|
|
||||||
hydrateDefaultVoice();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
hydrateDefaultVoice();
|
hydrateDefaultVoice();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,88 +127,6 @@
|
|||||||
{% set narrator_voice = '' %}
|
{% set narrator_voice = '' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% set normalization_overrides = pending.normalization_overrides if pending and pending.normalization_overrides else {} %}
|
{% set normalization_overrides = pending.normalization_overrides if pending and pending.normalization_overrides else {} %}
|
||||||
{% set normalization_toggles = [
|
|
||||||
{
|
|
||||||
'name': 'normalization_numbers',
|
|
||||||
'label': "Convert grouped numbers to words",
|
|
||||||
'value': normalization_overrides.get('normalization_numbers', settings_dict.get('normalization_numbers', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_titles',
|
|
||||||
'label': "Expand titles and suffixes (Dr., St., Jr., …)",
|
|
||||||
'value': normalization_overrides.get('normalization_titles', settings_dict.get('normalization_titles', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_terminal',
|
|
||||||
'label': "Ensure sentences end with terminal punctuation",
|
|
||||||
'value': normalization_overrides.get('normalization_terminal', settings_dict.get('normalization_terminal', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_phoneme_hints',
|
|
||||||
'label': "Apply phoneme hints for common abbreviations",
|
|
||||||
'value': normalization_overrides.get('normalization_phoneme_hints', settings_dict.get('normalization_phoneme_hints', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_caps_quotes',
|
|
||||||
'label': "Convert ALL CAPS dialogue inside quotes",
|
|
||||||
'value': normalization_overrides.get('normalization_caps_quotes', settings_dict.get('normalization_caps_quotes', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_apostrophes_contractions',
|
|
||||||
'label': "Expand contractions (it's -> it is)",
|
|
||||||
'value': normalization_overrides.get('normalization_apostrophes_contractions', settings_dict.get('normalization_apostrophes_contractions', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_apostrophes_plural_possessives',
|
|
||||||
'label': "Collapse plural possessives (dogs' -> dogs)",
|
|
||||||
'value': normalization_overrides.get('normalization_apostrophes_plural_possessives', settings_dict.get('normalization_apostrophes_plural_possessives', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_apostrophes_sibilant_possessives',
|
|
||||||
'label': "Mark sibilant possessives (boss's -> boss + IZ marker)",
|
|
||||||
'value': normalization_overrides.get('normalization_apostrophes_sibilant_possessives', settings_dict.get('normalization_apostrophes_sibilant_possessives', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_apostrophes_decades',
|
|
||||||
'label': "Expand decades ('90s -> 1990s)",
|
|
||||||
'value': normalization_overrides.get('normalization_apostrophes_decades', settings_dict.get('normalization_apostrophes_decades', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_apostrophes_leading_elisions',
|
|
||||||
'label': "Expand leading elisions ('tis -> it is)",
|
|
||||||
'value': normalization_overrides.get('normalization_apostrophes_leading_elisions', settings_dict.get('normalization_apostrophes_leading_elisions', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_contraction_aux_be',
|
|
||||||
'label': "Expand auxiliary 'be' (I'm -> I am)",
|
|
||||||
'value': normalization_overrides.get('normalization_contraction_aux_be', settings_dict.get('normalization_contraction_aux_be', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_contraction_aux_have',
|
|
||||||
'label': "Expand auxiliary 'have' (I've -> I have)",
|
|
||||||
'value': normalization_overrides.get('normalization_contraction_aux_have', settings_dict.get('normalization_contraction_aux_have', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_contraction_modal_will',
|
|
||||||
'label': "Expand modal 'will' (I'll -> I will)",
|
|
||||||
'value': normalization_overrides.get('normalization_contraction_modal_will', settings_dict.get('normalization_contraction_modal_will', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_contraction_modal_would',
|
|
||||||
'label': "Expand modal 'would' (I'd -> I would)",
|
|
||||||
'value': normalization_overrides.get('normalization_contraction_modal_would', settings_dict.get('normalization_contraction_modal_would', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_contraction_negation_not',
|
|
||||||
'label': "Expand negation 'not' (don't -> do not)",
|
|
||||||
'value': normalization_overrides.get('normalization_contraction_negation_not', settings_dict.get('normalization_contraction_negation_not', True)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'normalization_contraction_let_us',
|
|
||||||
'label': "Expand 'let's' -> let us",
|
|
||||||
'value': normalization_overrides.get('normalization_contraction_let_us', settings_dict.get('normalization_contraction_let_us', True)),
|
|
||||||
},
|
|
||||||
] %}
|
|
||||||
{% set voice_formula_value = '' %}
|
{% set voice_formula_value = '' %}
|
||||||
{% set profile_value = narrator_profile if narrator_profile else '__standard' %}
|
{% set profile_value = narrator_profile if narrator_profile else '__standard' %}
|
||||||
{% if profile_value == '__formula' %}
|
{% if profile_value == '__formula' %}
|
||||||
@@ -371,15 +289,17 @@
|
|||||||
<option value="off" {% if normalization_overrides.get('normalization_numbers_year_style', settings_dict.get('normalization_numbers_year_style', 'american')) == 'off' %}selected{% endif %}>Off (read as number)</option>
|
<option value="off" {% if normalization_overrides.get('normalization_numbers_year_style', settings_dict.get('normalization_numbers_year_style', 'american')) == 'off' %}selected{% endif %}>Off (read as number)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{% for toggle in normalization_toggles %}
|
{% for group in options.normalization_groups %}
|
||||||
|
{% for option in group.options %}
|
||||||
<div class="field field--stack">
|
<div class="field field--stack">
|
||||||
<label class="toggle-pill">
|
<label class="toggle-pill">
|
||||||
<input type="hidden" name="{{ toggle.name }}" value="false">
|
<input type="hidden" name="{{ option.key }}" value="false">
|
||||||
<input type="checkbox" name="{{ toggle.name }}" value="true" {% if toggle.value %}checked{% endif %} {{ 'disabled' if readonly else '' }}>
|
<input type="checkbox" name="{{ option.key }}" value="true" {% if normalization_overrides.get(option.key, settings_dict.get(option.key, True)) %}checked{% endif %} {{ 'disabled' if readonly else '' }}>
|
||||||
<span>{{ toggle.label }}</span>
|
<span>{{ option.label }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -283,38 +283,10 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="settings-panel" data-section="normalization">
|
<section class="settings-panel" data-section="normalization">
|
||||||
|
{% for group in options.normalization_groups %}
|
||||||
<fieldset class="settings__section">
|
<fieldset class="settings__section">
|
||||||
<legend>General Rules</legend>
|
<legend>{{ group.label }}</legend>
|
||||||
<div class="field field--choices">
|
{% if group.label == "Apostrophes & Contractions" %}
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_numbers" value="true" {% if settings.normalization_numbers %}checked{% endif %}>
|
|
||||||
<span>Convert grouped numbers to words</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_currency" value="true" {% if settings.normalization_currency %}checked{% endif %}>
|
|
||||||
<span>Convert currency symbols ($10 → ten dollars)</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_titles" value="true" {% if settings.normalization_titles %}checked{% endif %}>
|
|
||||||
<span>Expand titles and suffixes (Dr., St., Jr., …)</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_footnotes" value="true" {% if settings.normalization_footnotes %}checked{% endif %}>
|
|
||||||
<span>Remove footnote indicators ([1], [2])</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_terminal" value="true" {% if settings.normalization_terminal %}checked{% endif %}>
|
|
||||||
<span>Ensure sentences end with terminal punctuation</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_caps_quotes" value="true" {% if settings.normalization_caps_quotes %}checked{% endif %}>
|
|
||||||
<span>Convert ALL CAPS dialogue inside quotes</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset class="settings__section">
|
|
||||||
<legend>Apostrophes & Contractions</legend>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="field__label">Strategy</span>
|
<span class="field__label">Strategy</span>
|
||||||
<div class="choices choices--inline">
|
<div class="choices choices--inline">
|
||||||
@@ -329,38 +301,23 @@
|
|||||||
<p class="hint hint--warning">Configure the LLM connection before using it for audiobook runs.</p>
|
<p class="hint hint--warning">Configure the LLM connection before using it for audiobook runs.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="field field--choices">
|
<div class="field field--choices">
|
||||||
|
{% for option in group.options %}
|
||||||
<label class="toggle-pill">
|
<label class="toggle-pill">
|
||||||
<input type="checkbox" name="normalization_apostrophes_contractions" value="true" {% if settings.normalization_apostrophes_contractions %}checked{% endif %}>
|
<input type="checkbox" name="{{ option.key }}" value="true" {% if settings[option.key] %}checked{% endif %}>
|
||||||
<span>Expand contractions ("it's" → "it is")</span>
|
<span>{{ option.label }}</span>
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_apostrophes_plural_possessives" value="true" {% if settings.normalization_apostrophes_plural_possessives %}checked{% endif %}>
|
|
||||||
<span>Collapse plural possessives (dogs' → dogs)</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_apostrophes_sibilant_possessives" value="true" {% if settings.normalization_apostrophes_sibilant_possessives %}checked{% endif %}>
|
|
||||||
<span>Add guidance for sibilant possessives (boss's → boss + IZ marker)</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_apostrophes_decades" value="true" {% if settings.normalization_apostrophes_decades %}checked{% endif %}>
|
|
||||||
<span>Expand decades ('90s → 1990s)</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_apostrophes_leading_elisions" value="true" {% if settings.normalization_apostrophes_leading_elisions %}checked{% endif %}>
|
|
||||||
<span>Expand leading elisions ('tis → it is)</span>
|
|
||||||
</label>
|
|
||||||
<label class="toggle-pill">
|
|
||||||
<input type="checkbox" name="normalization_phoneme_hints" value="true" {% if settings.normalization_phoneme_hints %}checked{% endif %}>
|
|
||||||
<span>Add phoneme hints for possessives</span>
|
|
||||||
</label>
|
</label>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if group.label == "Apostrophes & Contractions" %}
|
||||||
<div class="field field--inline field--actions">
|
<div class="field field--inline field--actions">
|
||||||
<button type="button" class="button button--ghost button--small" data-action="contraction-modal-open">Advanced Contraction Settings…</button>
|
<button type="button" class="button button--ghost button--small" data-action="contraction-modal-open">Advanced Contraction Settings…</button>
|
||||||
<p class="hint">Choose which contraction families are expanded.</p>
|
<p class="hint">Choose which contraction families are expanded.</p>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<fieldset class="settings__section">
|
<fieldset class="settings__section">
|
||||||
<legend>Sample & Preview</legend>
|
<legend>Sample & Preview</legend>
|
||||||
|
|||||||
Reference in New Issue
Block a user