- Rename abogen/webui/routes/utils/preview.py → synthesize.py
The file contains the core TTS synthesis pipeline (generate_preview_audio,
synthesize_preview), not just preview logic. Name now matches responsibility.
- Remove dead code from voice.py: get_preview_pipeline(), synthesize_audio_from_normalized(),
_preview_pipeline_lock, _preview_pipelines, and unused imports (threading, numpy,
create_pipeline, get_new_voice, _select_device, _to_float32, SAMPLE_RATE, SPLIT_PATTERN).
These were never called — identical logic lives in synthesize.py.
- Update imports in api.py, voices.py, and test_preview_applies_manual_overrides.py
- 7 new tests in test_synthesize_module.py enforce file naming and import rules
- 7 tests in test_domain_imports.py updated for renamed module
Replace private imports (_select_device, _to_float32, SAMPLE_RATE, SPLIT_PATTERN)
from abogen.webui.conversion_runner with proper domain imports:
- select_device from abogen.domain.device
- to_float32, SAMPLE_RATE from abogen.domain.audio_helpers
- SPLIT_PATTERN defined locally (r'\n+')
Also verifies preview.py already uses domain imports correctly.
7 new tests in tests/test_domain_imports.py enforce the architecture rule.
- Replace manual metadata extraction with regex in pyqt/conversion.py
with calls to domain/metadata_extraction.py functions
- Remove duplicate _embed_m4b_metadata and _apply_m4b_chapters_with_mutagen
functions from webui/conversion_runner.py
- Use ExportService.embed_m4b_metadata for m4b metadata embedding
- Reduce code duplication between PyQt and WebUI interfaces
- Fix mix_audio to return target buffer (was not modifying in-place)
- Fix samples_for_duration to return 0 for negative durations
- Fix test assertions for numpy 2.x compatibility (share_memory -> shares_memory)
- Adjust subtitle_generation tests to match actual behavior
- Add abogen/domain/voice_loader.py with:
- VoiceCache class: unified cache for loaded voices
- resolve_voice(): load voice with optional caching
- load_voice_cached(): compatibility wrapper for PyQt
- Update abogen/pyqt/conversion.py:
- Replace load_voice_cached method body with call to domain function
- Maintain backward compatibility with existing interface
- Add tests/test_voice_loader.py with unit tests for VoiceCache and voice loading
- Add abogen/domain/subtitle_generation.py with:
- process_subtitle_tokens(): main function for converting TTS tokens to subtitles
- Support for all subtitle modes: Line, Sentence, Sentence + Comma, Sentence + Highlighting
- Support for word-count based grouping (e.g., '5' for 5 words per entry)
- spaCy integration for English sentence boundary detection
- Karaoke highlighting tags for Sentence + Highlighting mode
- Punctuation constants for sentence splitting
- Update abogen/pyqt/conversion.py:
- Replace _process_subtitle_tokens method body with call to domain function
- Remove ~260 lines of duplicate logic
- Add tests/test_subtitle_generation.py with comprehensive unit tests
- Add abogen/domain/audio_buffer.py with core audio operations:
- create_silence(): create silence audio buffer
- mix_audio(): mix source into target buffer with auto-resize
- normalize_audio(): normalize to prevent clipping
- ensure_buffer_size(): extend buffer to minimum size
- concatenate_audio(): join multiple audio buffers
- audio_duration(): calculate duration from samples
- samples_for_duration(): calculate samples from duration
- SAMPLE_RATE constant (24000)
- Update abogen/pyqt/conversion.py:
- Import and use create_silence for chapter silence
- Use mix_audio for subtitle file mixing
- Use normalize_audio for clipping prevention
- Use create_silence for padding in subtitle processing
- Update abogen/webui/conversion_runner.py:
- Import and use create_silence in append_silence
- Replace np.zeros with domain function
- Add tests/test_audio_buffer.py with comprehensive unit tests
- Update chapter_overrides.py to return tuple matching original signature
- Import apply_chapter_overrides and merge_metadata from domain modules
- Remove old function bodies from conversion_runner.py
- Add tests/test_chapter_merge_normalize.py (19 tests)
- conversion_runner.py: 1677 → 1574 lines
- All tests pass
- Extract infer_provider_from_spec, supertonic_voice_from_spec, split_speaker_reference, formula_from_kokoro_entry, coerce_truthy to domain/voice_utils.py
- Add tests/test_voice_utils.py with 24 tests
- All tests match old behavior
- Extract build_title_intro_text and build_outro_text into domain/title_builder.py
- Uses metadata_helpers for metadata processing
- Remove _build_title_intro_text and _build_outro_text from conversion_runner.py
- Add tests/test_title_builder.py with 12 tests
- All tests match old behavior
- Extract unified split pattern logic to domain/split_pattern.py
- Add get_split_pattern() function with language and subtitle_mode support
- Remove duplicated logic from pyqt/conversion.py
- Update pyqt/conversion.py to use domain.split_pattern.get_split_pattern
- Add tests/test_split_pattern.py with 20 tests covering English, CJK, Spanish, French, and pattern structure
- Extract SubtitleWriter classes (SrtWriter, AssWriter, VttWriter) to infrastructure/subtitle_writer.py
- Add create_subtitle_writer() factory function
- Remove old SubtitleWriter class and _format_timestamp from conversion_runner.py
- Use create_subtitle_writer() factory from infrastructure layer
- Add tests/test_subtitle_writer.py with 28 tests covering SrtWriter, AssWriter, VttWriter
- All tests match old _format_timestamp behavior
- Add .gitattributes with text=auto eol=lf for all text files
- Renormalize all files in index to LF line endings
- Fixes massive whitespace-only diffs between main and feature branch
- Create tests/plugins/ with auto-discovery fixtures and generic tests
- Add conftest.py with plugin_ids, loaded_plugin, host_context fixtures
- Add test_all_plugins.py with 3 test classes:
- TestAllPluginsManifest: validates manifest structure
- TestAllPluginsEngine: validates engine lifecycle contract
- TestAllPluginsCapabilities: validates capability implementation
- Update docs/testing.md with auto-discovery documentation
- Plugin-specific tests remain in tests/test_*_plugin.py for integration
New plugins in plugins/ are now automatically tested without manual test creation.
- Add to PluginManifest
- None = not declared (use VoiceLister fallback)
- () = explicitly no static voices
- Non-empty = static catalog available without Engine instantiation
- Update get_voices() to check manifest first, fall back to Engine
- Declare 54 Kokoro voices and 10 SuperTonic voices in manifests
- Remove hardcoded voice lists from engine.py files
- Engine.listVoices() now returns [] (manifest is source of truth)
- Clean up dead create_pipeline() kwargs (sample_rate, auto_download, total_steps)
- SuperTonic plugin uses internal defaults
- total_steps is per-request parameter via Pipeline.__call__() kwargs
- Add clear_preview_pipelines() for resource cleanup
- Fix test mocks to override listVoices()
- Update Architecture Amendment #1 doc
- Add 96 behavioral regression tests parametrized for both Kokoro and SuperTonic
- Remove legacy TTSBackendRegistry tests (13) from behavioral suite
- Remove mock-only capability tests (Preview, Streaming, Cancellation) not implemented by either plugin
- Fix get_voices() to pass required args to create_engine() + error handling
- All 598 tests pass
- Add tests/contracts/engine_contract.py with shared Engine/Session tests
- TestKokoroEngineContract and TestSuperTonicEngineContract inherit from it
- Eliminates protocol test duplication between plugins
- Any new plugin just inherits EngineContractMixin to verify compliance
- Add PluginManager singleton for plugin discovery and engine caching
- Add CompatBackend adapter wrapping Engine/EngineSession into old create_backend() API
- Update tts_plugin/__init__.py with public exports
- Migrate preview.py and its test to use compat.create_backend
- Add integration and plugin manager contract tests
Implement first TTS plugin using new Plugin Architecture:
- plugins/kokoro/: Plugin package with manifest and entry point
- plugins/kokoro/engine.py: KokoroEngine and KokoroSession adapters
- Wraps existing KokoroBackend without modifying it
- Implements VoiceLister capability
- Satisfies Engine/EngineSession protocol
- Passes all 163 contract tests
Tests:
- Plugin loading through Plugin Loader
- Manifest validation
- Engine creation and lifecycle
- Session synthesis and dispose
- VoiceLister capability
15 new tests for Kokoro plugin.
Implement plugin loading and validation:
- loader.py: discover, import, validate plugins
- validate PLUGIN_MANIFEST, MODEL_REQUIREMENTS, create_engine
- validate api_version compatibility (major must match)
- validate capabilities (reject unknown)
- diagnostic messages for all error cases
- no partial registration after error
Test plugins:
- fake_plugin: minimal valid plugin for testing
- missing_manifest: no PLUGIN_MANIFEST
- invalid_api_version: major version mismatch
- invalid_capabilities: unknown capabilities
- missing_create_engine: no create_engine function
- import_error: raises ImportError during import
- missing_model_requirements: no MODEL_REQUIREMENTS
39 new tests covering all loader functionality.
- Add resolve_backend_for_voice() to TTSBackendRegistry
- Add module-level wrapper resolve_backend_for_voice()
- Simplify _infer_provider_from_spec() to use registry API
- Simplify _supertonic_voice_from_spec() to only normalize
- Add 11 test cases for the new method
Resolution rules:
1. Empty spec -> fallback
2. Kokoro formula (* or +) -> kokoro
3. Exact voice ID match -> backend id
4. Unknown voice -> fallback
Make the Kokoro voice list an internal implementation detail of the
backend instead of a shared constant. The rest of the project already
accesses voices via get_metadata('kokoro').voices.
- Move VOICES_INTERNAL from constants.py to kokoro.py as _VOICES_INTERNAL
- Update tests to use get_metadata('kokoro').voices instead of importing
the constant directly
Add voices field to TTSBackendMetadata so each backend's supported
voice list is part of its metadata rather than external constants.
- Add voices: tuple[str, ...] = () to TTSBackendMetadata
- Create _KOKORO_METADATA / _SUPERTONIC_METADATA as single source
of truth for both metadata property and registry registration
- Update KokoroBackend.get_available_voices() to use self.metadata.voices
- Update SupertonicBackend.get_available_voices() to use self.metadata.voices
- Add tests for voices field, metadata voice content, and unified instance identity
- Create KokoroBackend class implementing TTSBackend protocol
- Move all KPipeline interaction inside KokoroBackend
- Update LoadPipelineThread to create backend via create_backend()
- Update ConversionThread and VoicePreviewThread to accept backend
- Replace np_module/kpipeline_class parameters with single backend
- Add 24 unit tests for KokoroBackend
- KPipeline is now an internal implementation detail of KokoroBackend