- 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.
- 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
- 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
- SubtitleMode, OutputFormat, SaveMode, SubtitleFormat, InputFormat
- Properties: dot_ext, is_lossless, is_book, is_subtitle
- from_str/from_path class methods with normalization
- Updated domain and application layers to use Enums
- 17 new tests for enum validation and properties
- Frozen dataclass in domain/conversion_engine.py with common params
- synthesize_text now takes params=SynthParams + unique kwargs
- Executor, PyQt legacy, WebUI legacy, and tests updated
- Adding new common params now only requires changing the dataclass
Two pre-existing bugs found during test coverage analysis:
1. sanitize_output_stem() only accepted 1 arg but resolve_project_layout
passed 2 args (name, index) via sanitize_fn parameter.
Fix: added optional index parameter to sanitize_output_stem.
2. audio_sink.py imported get_internal_cache_path from
abogen.infrastructure.cache which doesn't exist.
Fix: import from abogen.utils where the function lives.
Main orchestrator for the conversion flow. Both UIs call run_conversion().
Functions:
- run_conversion(request, events, pipeline_provider, voice_resolver) -> ConversionResult
- _prepare_tts_context(request, events) -> TTSContext
The service ties together planner, executor, and finalizers.
Converts WebUI Job to ConversionRequest for the application layer.
Functions:
- build_conversion_request_from_job(job) -> ConversionRequest
- WebJobEvents: wraps Job for logging, progress, cancellation
- WebPipelineProvider: wraps PipelinePool for TTS backends
- WebVoiceResolver: wraps voice resolution function
The adapter is the bridge between WebUI layer and application/domain.
Application layer never accesses Job directly.
7 tests for the unified conversion executor:
- simple text conversion
- multi-chapter with separate chapter output
- voice markers
- intro/outro
- cancellation behavior
- progress reporting
- metadata preservation
Uses FakeBackend, FakeAudioSink, FakeSubtitleWriter, FakeEvents,
FakePipelineProvider, FakeVoiceResolver to test without real TTS.
Pure function that takes ConversionRequest -> ConversionPlan.
Handles chapter parsing, voice markers, chunks, intro/outro, output layout.
Replaces duplicated planning logic in both PyQt and WebUI runners.
PyQt output path resolution now uses resolve_unique_path() from domain
instead of a hand-rolled counter loop. Output path logic is now fully
shared via domain functions.
Tests: 1253 passed
PyQt now calls process_subtitle_tokens() from domain instead of a thin
wrapper that just forwarded self.subtitle_mode/lang_code/use_spacy.
Tests: 1253 passed
Combines TTSContext.normalize() + run_tts_segment_loop() into a single
domain function. Both UIs call synthesize_text() instead of inlining
normalize → TTS loop. UI-specific concerns (provider resolution,
progress display, cancellation) stay in the UI layer.
Tests: 1253 passed
Bundles pronunciation_rules, heteronym_rules, normalization_overrides,
usage_counter, and split_pattern into a single TTSContext dataclass.
Both UIs create it once and use tts_context.normalize() instead of
threading 5 separate parameters through prepare_text_for_tts calls.
Tests: 1253 passed
- domain/intro_outro.py: resolve_intro(), resolve_outro() return IntroOutroSpec
- Both UIs call domain for text building + voice spec resolution
- PyQt uses resolve_intro/resolve_outro instead of direct calls
- infrastructure/subtitle_writer.py: resolve_subtitle_format(), make_subtitle_writer()
- Deleted duplicate _create_subtitle_writer() from WebUI
- Deleted duplicate _subtitle_alignment_from_format() from PyQt
- domain/conversion_engine.py: run_tts_segment_loop() for TTS iteration
- VoiceCache class in domain/voice_loader.py used by both UIs
- Tests: 1253 passed
- domain/voice_loader.py: VoiceCache class now used by both UIs;
resolve_voice() and load_voice_cached() accept VoiceCache or plain dict;
added hasattr(pipeline, 'load_single_voice') safety check from WebUI
- conversion_runner.py: replaced local _resolve_voice() with domain's
resolve_voice(); voice_cache changed from Dict to VoiceCache instance;
all cache access uses VoiceCache.get()/set() API
- pyqt/conversion.py: self.voice_cache changed from Dict to VoiceCache
- debug_tts_runner.py: imports resolve_voice from domain instead of
removed _resolve_voice from conversion_runner
- infrastructure/subtitle_writer.py: add resolve_subtitle_format() that
maps format strings (e.g. 'ass_centered_narrow') to (extension, alignment),
and make_subtitle_writer() convenience that resolves + creates writer or None
- conversion_runner.py: replace _create_subtitle_writer() with make_subtitle_writer()
- pyqt/conversion.py: replace _subtitle_alignment_from_format() and 3 manual
create_subtitle_writer() call sites with resolve_subtitle_format()/make_subtitle_writer()
- load_settings() now in domain/settings_core.py (shared by all UIs)
- settings.py delegates to domain instead of reimplementing
- settings.py: 456 → 430 lines