From 550fdbf537c517e2a76d91a3285f80a3dabfd750 Mon Sep 17 00:00:00 2001 From: JB Date: Thu, 27 Nov 2025 10:49:29 -0800 Subject: [PATCH] feat: Enhance normalization settings with additional options and UI elements --- abogen/web/routes.py | 28 ++++++-- abogen/web/service.py | 9 ++- .../templates/partials/new_job_step_book.html | 68 +++++++++++++++++++ 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/abogen/web/routes.py b/abogen/web/routes.py index 2ee0ade..c9ea24d 100644 --- a/abogen/web/routes.py +++ b/abogen/web/routes.py @@ -1778,10 +1778,17 @@ def _apply_book_step_form( return default overrides_existing = getattr(pending, "normalization_overrides", None) - overrides: Dict[str, bool] = dict(overrides_existing or {}) - for key in _APOSTROPHE_OVERRIDE_KEYS: + overrides: Dict[str, Any] = dict(overrides_existing or {}) + for key in _NORMALIZATION_BOOLEAN_KEYS: default_toggle = overrides.get(key, bool(settings.get(key, True))) overrides[key] = _extract_checkbox(key, default_toggle) + for key in _NORMALIZATION_STRING_KEYS: + default_val = overrides.get(key, str(settings.get(key, ""))) + val = form.get(key) + if val is not None: + overrides[key] = str(val) + else: + overrides[key] = default_val pending.normalization_overrides = overrides speed_value = form.get("speed") @@ -2014,6 +2021,7 @@ def _template_options() -> Dict[str, Any]: "speaker_pronunciation_sentence": current_settings.get( "speaker_pronunciation_sentence", _settings_defaults()["speaker_pronunciation_sentence"] ), + "apostrophe_modes": _APOSTROPHE_MODE_OPTIONS, } @@ -2112,7 +2120,12 @@ BOOLEAN_SETTINGS = { FLOAT_SETTINGS = {"silence_between_chapters", "chapter_intro_delay", "llm_timeout"} INT_SETTINGS = {"max_subtitle_words", "speaker_analysis_threshold"} -_APOSTROPHE_OVERRIDE_KEYS = ( +_NORMALIZATION_BOOLEAN_KEYS = ( + "normalization_numbers", + "normalization_titles", + "normalization_terminal", + "normalization_phoneme_hints", + "normalization_caps_quotes", "normalization_apostrophes_contractions", "normalization_apostrophes_plural_possessives", "normalization_apostrophes_sibilant_possessives", @@ -2124,7 +2137,11 @@ _APOSTROPHE_OVERRIDE_KEYS = ( "normalization_contraction_modal_would", "normalization_contraction_negation_not", "normalization_contraction_let_us", - "normalization_caps_quotes", +) + +_NORMALIZATION_STRING_KEYS = ( + "normalization_numbers_year_style", + "normalization_apostrophe_mode", ) @@ -4431,7 +4448,8 @@ def _build_pending_job_from_extraction( metadata_tags=metadata_tags, chapters=chapters_payload, normalization_overrides={ - key: bool(settings.get(key, True)) for key in _APOSTROPHE_OVERRIDE_KEYS + **{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(), cover_image_path=cover_path, diff --git a/abogen/web/service.py b/abogen/web/service.py index f2e89f0..2deb312 100644 --- a/abogen/web/service.py +++ b/abogen/web/service.py @@ -1095,10 +1095,13 @@ class ConversionService: if existing_items: job.add_log( - f"Audiobookshelf upload skipped: '{display_title}' already exists in the configured folder.", - level="warning", + f"Removing existing Audiobookshelf item(s) for '{display_title}' before upload.", + level="info", ) - return + try: + client.delete_items(existing_items) + except Exception as exc: + job.add_log(f"Failed to remove existing item(s): {exc}", level="warning") client.upload_audiobook( audio_path, diff --git a/abogen/web/templates/partials/new_job_step_book.html b/abogen/web/templates/partials/new_job_step_book.html index 6ba95c0..7b01886 100644 --- a/abogen/web/templates/partials/new_job_step_book.html +++ b/abogen/web/templates/partials/new_job_step_book.html @@ -128,6 +128,26 @@ {% endif %} {% 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", @@ -158,6 +178,36 @@ '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 profile_value = narrator_profile if narrator_profile else '__standard' %} @@ -303,6 +353,24 @@

Text normalization

+
+ Apostrophe strategy +
+ {% for option in options.apostrophe_modes %} + + {% endfor %} +
+
+
+ + +
{% for toggle in normalization_toggles %}