Commit Graph
846 Commits
Author SHA1 Message Date
Artem Akymenko f802fb2af6 refactor: centralize integration config in domain layer, fix OutputFormat enum serialization
- Move stored_integration_config(), build_audiobookshelf_config() to domain/settings_core.py
- Remove legacy fallback from stored_integration_config() (only config[integrations])
- Add load_audiobookshelf_config() as combined entry point
- PostConversionHooks reads config directly via stored_integration_config()
- WebUI imports from domain/settings_core instead of webui/routes/utils/settings
- Remove duplicate _build_abs_config() from PostConversionHooks
- Remove dead audiobookshelf code from infrastructure/exporters.py
- Remove duplicate audiobookshelf functions from webui/service.py
- Fix OutputFormat enum serialization in output_layout_service.py and conversion_executor.py
  (f'{enum}' gave 'OutputFormat.WAV' instead of '.wav')
- Mock spaCy in test_returns_at_least_one_segment instead of loading real model
- Add 28 tests for PostConversionHooks and build_audiobookshelf_config
2026-07-29 13:11:56 +00:00
Artem Akymenko c293cc90f6 chore: suppress phonemizer word-count-mismatch warnings (normal behavior) 2026-07-29 11:26:43 +00:00
Artem Akymenko 696ce1ebd0 fix: use existing to_float32 from audio_helpers, keep INFO log level
- synthesize.py: import to_float32 from domain.audio_helpers instead of duplicate
- Revert all log levels back to INFO (user controls verbosity via --log-level)
2026-07-29 11:24:01 +00:00
Artem Akymenko d3ded8af0e feat: add comprehensive logging throughout conversion pipeline
- voice_resolver: log spec resolution and cache hits
- conversion_executor: log chapter start/end, voice resolution, timing
- conversion_planner: log plan summary
- conversion_service: log entry point params and outcome
- conversion_runner: log job params and lifecycle
- form.py: log PendingJob creation
- synthesize.py: log pipeline creation and device selection
2026-07-29 11:05:52 +00:00
Artem Akymenko c706f7714a fix: language enum coercion, preview logging, file picker
- form.py: convert language string to Language enum at all entry points
- conversion_request.py: add _coerce_enums() for defense-in-depth
- synthesize.py: fix NameError (lang -> language) in preview pipeline
- voice.py: render LANGUAGE_DESCRIPTIONS keys as .value strings for Jinja
- dashboard.js: open file picker on dropzone click
- api.py: add logging to preview endpoint for debugging
2026-07-29 10:57:21 +00:00
Artem Akymenko 2b70b9ca45 feat: Supertonic language + total_steps propagation
Language enum expanded from 9 to 33 languages:
- Added 24 new ISO 639-1 languages: AR, BG, CS, DA, DE, EL, ET, FI,
  HR, HU, ID, KO, LT, LV, NL, PL, RO, RU, SK, SL, SV, TR, UK, VI
- Updated display_name, is_cjk (added KO)

Supertonic language mapping (32 languages, no ZH):
- engine.py: _SUPERTONIC_LANG_MAP, engine_language(), supported_languages()
- __init__.py: create_engine() passes config.language to pipeline
- pipeline.py: __init__() accepts language, resolves to ISO code;
  __call__() passes lang= to TTS.synthesize()

total_steps propagation:
- tts_segments(): +total_steps param, conditionally passed to backend
- synthesize_text(): +total_steps param
- run_tts_segment_loop(): +total_steps param
- executor: all 5 synthesize_text() calls pass total_steps

Integration:
- pipeline_factory: create_pipeline_for_job() passes language to supertonic
- preview path: create_pipeline('supertonic', language=language)

Tests updated to accept total_steps in FakeBackend.__call__
2026-07-28 14:09:42 +03:00
Artem Akymenko 953bef1e71 refactor: group ConversionRequest fields into config objects
Domain config types (domain/config_types.py):
- PronunciationConfig: pronunciation/heteronym/normalization overrides
- SubtitleConfig: mode, format, max_words
- CoverConfig: path, mime

Domain functions now accept config objects:
- build_tts_context(subtitle=, pronunciation=) instead of 9 individual params
- make_subtitle_writer(subtitle=) instead of 3 params
- process_and_write_subtitles(subtitle=) instead of 2 params
- embed_m4b_metadata(cover=) instead of 2 params
- build_epub3_package(cover=) instead of 2 params

ConversionRequest: 18 flat fields + 8 config objects
Application/config.py re-exports domain types
All tests updated to new API
2026-07-28 13:41:45 +03:00
Artem Akymenko 146cc81271 refactor: centralize cleanup in app layer
- New: application/cleanup.py — flush_cuda(), dispose_engines(), cleanup(), register_ui_cleanup()
- conversion_service.py finally: pool.dispose_all() + voice_cache.clear() + flush_cuda()
- webui/conversion_runner.py: removed gc/cuda finally block (cleanup in run_conversion)
- shutdown.py: 160→120 lines, 5 inline cleanups → 4 process-level + app_cleanup() delegation
- Fixed bugs: _PIPELINES (didn't exist), PluginManager.dispose_all() (never called),
  VoiceCache.clear() (never called in finally), duplicate cleanup removed
2026-07-28 13:41:28 +03:00
Artem Akymenko 61204cc389 refactor: move pool/cache/resolver into run_conversion()
- New: application/voice_resolver.py — AppVoiceResolver (app-layer, takes ConversionRequest)
- conversion_service.py: run_conversion(request, events) creates PipelinePool, VoiceCache, AppVoiceResolver internally
- conversion_runner.py: 268→165 lines, removed WebUIVoiceResolver, pool/cache creation
- Tests: mock fixture for pool/resolver, removed unused variables
- Added pool.dispose_all() in finally block for cleanup
2026-07-27 13:37:19 +03:00
Artem Akymenko f7a224cc46 chore: remove dead test files and deleted conversion_adapter
All 8 deleted test files were duplicates of existing domain tests:
- test_chapter_overrides → covered by test_chapter_merge_normalize
- test_conversion_chapter_titles → covered by test_chapter_titles
- test_conversion_series → covered by test_title_builder
- test_conversion_voice_resolution → covered by test_voice_resolution
- test_voice_cache → covered by test_voice_resolution
- test_manual_overrides_applied_first → covered by test_pronunciation
- test_conversion_adapters → tested deleted conversion_adapter module
- test_import_layering → tested deleted conversion_adapter module

Also removed deleted conversion_adapter.py (logic moved to conversion_runner).
2026-07-27 09:56:51 +00:00
Artem Akymenko 98ab2d925e refactor: simplify _build_request, restore epub3_export as config object
Simplify _build_request() in WebUI
- Remove profiles loading (load_profiles, normalize_profile_entry)
- Remove PronunciationConfig/Epub3ExportConfig building
- Pass raw fields directly to ConversionRequest
- Clean up unused imports

Bugfix: epub3_export as Optional[Epub3ExportConfig] (None = disabled)
- Reverted generate_epub3: bool + epub3_book_id: str back to object pattern
- Consistent with word_substitution, subtitle_input, chapter_chunk
- Updated conversion_service.py _finalize() to use request.epub3_export
2026-07-27 12:41:29 +03:00
Artem Akymenko 625b6610e2 refactor: remove _prepare_tts_context, use build_tts_context directly
- Deleted _prepare_tts_context() (104 lines of duplicated logic)
- Replaced with direct build_tts_context() call in run_conversion()
- Fixed _finalize() to use raw fields (generate_epub3, epub3_book_id)
- Removed _MockJob antipattern
- Updated tests to use raw normalization_overrides field
- ConversionRequest uses raw fields instead of PronunciationConfig/Epub3ExportConfig
2026-07-27 12:19:59 +03:00
Artem Akymenko 0dc491e420 refactor: unify Language enum across all layers
- EngineConfig.language: Language (was lang_code: str = 'a')
- Engine owns _KOKORO_LANG_MAP, engine_language(), supported_languages()
- Engine provides language_for_voice_id() for voice catalog
- Plugins/kokoro/__init__.py calls engine_language() internally
- create_pipeline(plugin_id, language=Language) — no kokoro codes
- pipeline_factory.py clean of kokoro-specific code
- Domain functions raise TypeError if non-enum passed
- WebUI api.py: _parse_language() helper at API boundary
- Voice catalog returns ISO codes (lang.value)
- Constants: LANGUAGE_DESCRIPTIONS keyed by Language enum
- All tests updated for Language enum
- 1414 tests pass
2026-07-27 07:36:36 +00:00
Artem Akymenko 713abdfd73 refactor: voice resolution on ConversionRequest, PipelinePool without job param
- Added speakers field to ConversionRequest
- Rewrote collect_required_voice_ids(), initialize_voice_cache(),
  job_voice_fallback(), chapter_voice_spec(), chunk_voice_spec()
  to accept ConversionRequest instead of job
- PipelinePool.get() now takes request= instead of job=
- Updated all tests to use ConversionRequest interface
- 1493 tests passing
2026-07-26 14:11:55 +03:00
Artem Akymenko 73f42e9563 refactor: voice fallback logging + base voice validation 2026-07-26 11:42:56 +03:00
Artem Akymenko 2c61f55f81 refactor: spaCy pre-TTS segmentation moved to shared layer
- conversion_pipeline.py: new spacy_pre_tts_segmentation() function
- Handles: condition checks, language exclusion, split_pattern override
- Executor: integrated spaCy pre-TTS before each synthesize_text() call
- 6 new tests for condition checks and fallback behavior
- English excluded from pre-TTS (spaCy only for post-TTS subtitles)
- Fallback to regex when spaCy unavailable
2026-07-26 11:42:43 +03:00
Artem Akymenko 6497e8c47a refactor: metadata + markers unified in shared layer
- MarkerCollector: SRP extraction from executor (observation, not execution)
- Outro marker: executor now records outro as chapter marker
- Voice format: chapter voices [{provider, voice}], chunk voice {provider, voice}
- _finalize(): build_metadata_payload() + metadata.json + record_override_usage()
- ffmetadata: voices list → comma-separated string
- EPUB3: ChunkOverlay.voice = dict, _render_chunk_inline handles dict format
- 8 new tests: multi-speaker, ffmetadata format, EPUB3 format
- Updated existing tests for new voice format
2026-07-26 11:42:27 +03:00
Artem Akymenko 0b953d48e8 refactor: heading transforms + dedup moved to shared layer
- conversion_planner.py: caps normalization in _build_chapters()
- conversion_executor.py: heading dedup + state machine via headings_equivalent()
- Fixed seg_start_time → chapter_body_start bug in executor
- Removed getattr fallback defaults in both adapters
- Added 4 tests for caps normalization and heading dedup
- Updated ARCHITECTURE_REFACTOR_PLAN.md with deferred WebUI cleanup
2026-07-26 11:41:59 +03:00
Artem Akymenko f516cf1985 merge: resolve conflicts with origin/main
- Import run_tts_segment_loop (used in pyqt/conversion.py)
- Use build_tts_context() instead of manual _MergeJob + TTSContext
- Drop unused AudioSink import
2026-07-24 18:34:49 +00:00
Artem Akymenko 3857c27aae refactor: clean unused imports (ruff F401/F811), fix build_tts_context defaults
- Remove 77 unused imports across domain/application/runner files via ruff
- Add # noqa: F401 to re-exports used by tests and debug_tts_runner
- Fix build_tts_context: usage_counter uses 'is not None' instead of truthiness
- Fix test assertions: compiled rules use 'replacement' key not 'pronunciation'
- Add re-exports: _compile_pronunciation_rules, _merge_pronunciation_overrides
2026-07-24 21:26:36 +03:00
Artem Akymenko 7ed2addb11 refactor: add build_metadata_payload() to domain, unify metadata assembly in both UIs 2026-07-24 21:26:21 +03:00
Artem Akymenko cfc7de7abf refactor: unify PUNCTUATION constants in domain/split_pattern.py 2026-07-24 19:17:39 +03:00
Artem Akymenko 654c395943 refactor: move sanitize_name_for_os to domain/output_paths.py 2026-07-24 19:17:39 +03:00
Artem Akymenko d1a84cfb8b refactor: move voice marker functions to domain/voice_markers.py 2026-07-24 19:17:38 +03:00
Artem Akymenko 332934c0cf test: tests for apply_overrides, LLM mode, usage_counter, chunk_groups 2026-07-24 19:17:38 +03:00
Artem Akymenko c79838a5a1 fix: chunk_groups_by_chapter in planner, chunks assigned to correct chapter 2026-07-24 19:17:38 +03:00
Artem Akymenko d51a9118e4 fix: apply_overrides, LLM mode check, usage_counter in service 2026-07-24 19:17:38 +03:00
Artem Akymenko 3311bef2f7 feat: EPUB3 finalizer in service, extraction in ConversionPlan 2026-07-24 19:17:37 +03:00
Artem Akymenko 4123cadd87 feat: m4b finalizer in service (embed_m4b_metadata) 2026-07-24 19:17:37 +03:00
Artem Akymenko 2f83d10a1e feat: per-chapter subtitle writer in executor 2026-07-24 19:17:37 +03:00
Artem Akymenko a1241ee9ca refactor: word substitution in planner, add planner tests 2026-07-24 19:17:37 +03:00
Artem Akymenko 0ee5bb0496 refactor: config objects for feature toggles in ConversionRequest 2026-07-24 19:17:36 +03:00
Artem Akymenko 7d28b7eb52 test: mock subprocess.Popen in test_executor_m4b_forces_merge to eliminate ffmpeg dependency 2026-07-24 19:17:36 +03:00
Deniz Şafak 9201f58770 Add k0sm0naft to GitHub funding list 2026-07-23 16:27:54 +03:00
Deniz Şafak 6274a02d5e Fix empty voice list when launched via desktop shortcut
PluginManager.discover() used a relative path 'plugins', which resolved
against the CWD. When launched from a desktop shortcut the CWD is ~, so
the plugins directory was never found and no voices appeared in the list.

Fall back to the project-relative plugins path when the default relative
path doesn't resolve.
2026-07-23 03:55:07 +03:00
Deniz Şafak 342ea0dfac Fix spaCy unknown language error: map Kokoro single-letter codes to Language enum in get_spacy_model 2026-07-23 03:33:06 +03:00
Deniz Şafak 27f88b759d Fix spurious HF HEAD requests: return early on cache hit in tracked_hf_hub_download 2026-07-23 03:26:58 +03:00
Deniz Şafak dbcbb1c8a9 Fix NameError: add missing 'from pathlib import Path' in pyqt/conversion.py 2026-07-23 03:18:51 +03:00
Deniz Şafak bd99ee1ba1 fix: subtitle FakeToken split, missing run_tts_segment_loop import
- subtitle_generation: split multi-sentence FakeToken into separate entries
- conversion.py: add missing run_tts_segment_loop import

No changes to spacy_utils or Language enum.
2026-07-23 03:15:21 +03:00
Deniz Şafak d5cddb9749 fix: pass mock job object to merge_pronunciation_overrides instead of positional args 2026-07-23 02:33:18 +03:00
Deniz Şafak ec55918b04 fix: add load_single_voice to Pipeline wrapper to prevent formula string being used as download filename 2026-07-23 02:28:41 +03:00
Deniz Şafak 14913b45e9 fix: import importlib.util explicitly (not auto-loaded in Python 3.12) — broke plugin loading, causing empty voice lists 2026-07-23 02:17:46 +03:00
Deniz Şafak a0fdabd81f fix: suppress harmless Qt portal registration warning on Linux 2026-07-23 01:42:41 +03:00
Deniz Şafak 473631b84e fix: use theme-aware GREY_BACKGROUND for word substitutions instructions label 2026-07-23 01:41:13 +03:00
Deniz Şafak 0f5003dfdd fix: add missing imports for get_resource_path and load_integration_settings 2026-07-23 01:37:50 +03:00
Artem Akymenko fcec4e9fe5 fix: test_stretch_reduces_duration — remove stale self param, fix mock data size, fix atempo assertion 2026-07-22 15:32:18 +03:00
Artem Akymenko 72d5e3d1db fix: add keys() method to VoiceCache for resolve_intro compatibility 2026-07-22 15:31:43 +03:00
Artem Akymenko d3682e7672 refactor: dynamic ConversionRequest validation, remove 'or default' from adapters
- __post_init__: _apply_none_defaults() iterates dataclasses.fields() dynamically
- _NUMERIC_CONSTRAINTS and _ENUM_CONSTRAINTS dicts replace per-field if chains
- Both adapters pass values as-is (no 'or default' fallbacks)
- 18 validation tests + updated adapter tests for Enum assertions
2026-07-22 11:33:44 +00:00
Artem Akymenko 0805e9fdae refactor: Language Enum with ISO codes
- Language enum: en-US, en-GB, es, fr, hi, it, ja, pt-BR, zh
- Engine-specific mappings (kokoro → single-letter) live in pipeline_factory and synthesize
- spacy_utils uses Language enum keys for model mapping
- split_pattern uses Language enum properties (is_cjk)
- Updated all tests to use ISO codes
2026-07-22 10:54:39 +00:00
Artem Akymenko 4aef73ff85 refactor: remove infrastructure enum duplicates
- SubtitleFormat/SubtitleMode now only in domain/enums.py
- Added VTT to SubtitleFormat
- Renamed SENTENCE_HIGHLIGHTING → SENTENCE_HIGHLIGHT for consistency
- Infrastructure subtitle_writer imports from domain
2026-07-22 09:16:40 +00:00