mirror of
https://github.com/denizsafak/abogen.git
synced 2026-09-20 11:40:57 +02:00
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
This commit is contained in:
@@ -14,6 +14,8 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import numpy as np
|
||||
|
||||
from abogen.domain.enums import Language
|
||||
|
||||
from abogen.tts_plugin.engine import Engine, EngineSession
|
||||
from abogen.tts_plugin.errors import EngineError
|
||||
from abogen.tts_plugin.plugin_manager import PluginManager, get_plugin_manager, reset_plugin_manager
|
||||
@@ -328,7 +330,7 @@ class TestRegression:
|
||||
manager._loaded = True
|
||||
|
||||
with patch("abogen.tts_plugin.utils.get_plugin_manager", return_value=manager):
|
||||
backend = create_pipeline("mock_tts", lang_code="a", device="cpu")
|
||||
backend = create_pipeline("mock_tts", language=Language.EN_US, device="cpu")
|
||||
|
||||
# Old interface: pipeline(text, voice=..., speed=..., split_pattern=...)
|
||||
segments = list(backend(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.tts_plugin.plugin_manager import PluginManager, get_plugin_manager, reset_plugin_manager
|
||||
from abogen.tts_plugin.utils import Pipeline, create_pipeline
|
||||
from abogen.tts_plugin.engine import Engine, EngineSession
|
||||
@@ -175,7 +176,7 @@ class TestCreatePipelineCompat:
|
||||
mock_engine = FakeEngine()
|
||||
mock_manager.create_engine.return_value = mock_engine
|
||||
|
||||
backend = create_pipeline("kokoro", lang_code="a", device="cpu")
|
||||
backend = create_pipeline("kokoro", language=Language.EN_US, device="cpu")
|
||||
|
||||
assert callable(backend)
|
||||
mock_manager.create_engine.assert_called_once()
|
||||
@@ -185,7 +186,7 @@ class TestCreatePipelineCompat:
|
||||
assert call_args.kwargs["model_path"] is None
|
||||
assert isinstance(call_args.kwargs["config"], EngineConfig)
|
||||
assert call_args.kwargs["config"].device == "cpu"
|
||||
assert call_args.kwargs["config"].lang_code == "a"
|
||||
assert call_args.kwargs["config"].language == Language.EN_US
|
||||
|
||||
def test_create_pipeline_raises_for_unknown_plugin(self):
|
||||
"""create_pipeline raises KeyError for unknown plugins."""
|
||||
|
||||
@@ -8,6 +8,7 @@ These tests verify that value objects satisfy the architectural requirements:
|
||||
|
||||
import pytest
|
||||
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.tts_plugin.types import (
|
||||
AudioFormat,
|
||||
Duration,
|
||||
@@ -192,23 +193,23 @@ class TestEngineConfigContract:
|
||||
config = EngineConfig(device="cuda:0")
|
||||
assert config.device == "cuda:0"
|
||||
|
||||
def test_default_lang_code(self) -> None:
|
||||
def test_default_language(self) -> None:
|
||||
config = EngineConfig()
|
||||
assert config.lang_code == "a"
|
||||
assert config.language == Language.EN_US
|
||||
|
||||
def test_custom_lang_code(self) -> None:
|
||||
config = EngineConfig(lang_code="j")
|
||||
assert config.lang_code == "j"
|
||||
def test_custom_language(self) -> None:
|
||||
config = EngineConfig(language=Language.JA)
|
||||
assert config.language == Language.JA
|
||||
|
||||
def test_immutability(self) -> None:
|
||||
config = EngineConfig()
|
||||
with pytest.raises(AttributeError):
|
||||
config.device = "cuda:0" # type: ignore[misc]
|
||||
|
||||
def test_immutability_lang_code(self) -> None:
|
||||
def test_immutability_language(self) -> None:
|
||||
config = EngineConfig()
|
||||
with pytest.raises(AttributeError):
|
||||
config.lang_code = "j" # type: ignore[misc]
|
||||
config.language = Language.JA # type: ignore[misc]
|
||||
|
||||
def test_unknown_keys_ignored_per_spec(self) -> None:
|
||||
"""Architecture spec: Unknown keys are ignored (no error).
|
||||
@@ -225,11 +226,11 @@ class TestEngineConfigContract:
|
||||
EngineConfig may contain fields that are not relevant to every plugin.
|
||||
Plugins MUST ignore fields they do not need, not raise on them.
|
||||
"""
|
||||
config = EngineConfig(device="cuda:0", lang_code="j")
|
||||
config = EngineConfig(device="cuda:0", language=Language.JA)
|
||||
assert config.device == "cuda:0"
|
||||
assert config.lang_code == "j"
|
||||
assert config.language == Language.JA
|
||||
# A plugin that only needs device simply reads config.device
|
||||
# and ignores config.lang_code — this must not raise.
|
||||
# and ignores config.language — this must not raise.
|
||||
|
||||
def test_engine_config_contains_engine_instance_configuration(self) -> None:
|
||||
"""Architecture Amendment #1: EngineConfig definition.
|
||||
@@ -238,7 +239,7 @@ class TestEngineConfigContract:
|
||||
Engine instance is created and that remain constant throughout
|
||||
the lifetime of that Engine.
|
||||
"""
|
||||
config = EngineConfig(device="cpu", lang_code="a")
|
||||
config = EngineConfig(device="cpu", language=Language.EN_US)
|
||||
# Both fields are init-time, immutable, engine-scoped.
|
||||
assert config.device == "cpu"
|
||||
assert config.lang_code == "a"
|
||||
assert config.language == Language.EN_US
|
||||
|
||||
@@ -928,10 +928,11 @@ class TestValueObjectsBehavioral:
|
||||
|
||||
def test_engine_config_defaults(self) -> None:
|
||||
from abogen.tts_plugin.types import EngineConfig
|
||||
from abogen.domain.enums import Language
|
||||
|
||||
config = EngineConfig()
|
||||
assert config.device == "cpu"
|
||||
assert config.lang_code == "a"
|
||||
assert config.language == Language.EN_US
|
||||
|
||||
def test_parameter_values_defaults(self) -> None:
|
||||
pv = ParameterValues()
|
||||
|
||||
@@ -14,6 +14,7 @@ from unittest.mock import MagicMock, patch
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.domain.conversion_engine import (
|
||||
synthesize_text,
|
||||
SynthParams,
|
||||
@@ -254,7 +255,7 @@ class TestProcessAndWriteSubtitles:
|
||||
writer,
|
||||
subtitle_mode="Sentence",
|
||||
max_subtitle_words=5,
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
use_spacy_segmentation=False,
|
||||
fallback_end_time=10.0,
|
||||
)
|
||||
@@ -271,7 +272,7 @@ class TestProcessAndWriteSubtitles:
|
||||
writer,
|
||||
subtitle_mode="Sentence",
|
||||
max_subtitle_words=5,
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
use_spacy_segmentation=False,
|
||||
fallback_end_time=2.0,
|
||||
)
|
||||
@@ -293,7 +294,7 @@ class TestProcessAndWriteSubtitles:
|
||||
writer,
|
||||
subtitle_mode="Line",
|
||||
max_subtitle_words=5,
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
use_spacy_segmentation=False,
|
||||
fallback_end_time=3.0,
|
||||
)
|
||||
@@ -312,7 +313,7 @@ class TestProcessAndWriteSubtitles:
|
||||
writer,
|
||||
subtitle_mode="Disabled",
|
||||
max_subtitle_words=5,
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
use_spacy_segmentation=False,
|
||||
fallback_end_time=2.0,
|
||||
)
|
||||
@@ -348,7 +349,7 @@ class TestFullPipeline:
|
||||
audio_sink=merged_sink,
|
||||
subtitle_mode="Sentence",
|
||||
max_subtitle_words=5,
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
use_spacy_segmentation=False,
|
||||
)
|
||||
|
||||
@@ -367,7 +368,7 @@ class TestFullPipeline:
|
||||
subtitle_writer,
|
||||
subtitle_mode="Sentence",
|
||||
max_subtitle_words=5,
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
use_spacy_segmentation=False,
|
||||
fallback_end_time=stats.current_time,
|
||||
)
|
||||
|
||||
@@ -37,13 +37,13 @@ class TestConversionRequestBasics:
|
||||
assert "merge_chapters_at_end" in defaults
|
||||
|
||||
def test_split_pattern_computation(self):
|
||||
pattern = get_split_pattern("a", "Disabled")
|
||||
pattern = get_split_pattern(Language.EN_US, "Disabled")
|
||||
assert isinstance(pattern, str)
|
||||
assert len(pattern) > 0
|
||||
|
||||
def test_split_pattern_varies_by_subtitle_mode(self):
|
||||
pattern_disabled = get_split_pattern("a", "Disabled")
|
||||
pattern_sentence = get_split_pattern("a", "Sentence")
|
||||
pattern_disabled = get_split_pattern(Language.EN_US, "Disabled")
|
||||
pattern_sentence = get_split_pattern(Language.EN_US, "Sentence")
|
||||
# Different modes should produce different patterns
|
||||
assert isinstance(pattern_disabled, str)
|
||||
assert isinstance(pattern_sentence, str)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.domain.normalization import prepare_text_for_tts, normalize_text_for_pipeline, build_tts_context, TTSContext
|
||||
|
||||
|
||||
@@ -151,21 +152,21 @@ class TestBuildTtsContext:
|
||||
"""Tests for the build_tts_context factory."""
|
||||
|
||||
def test_returns_tts_context(self):
|
||||
ctx = build_tts_context()
|
||||
ctx = build_tts_context(language=Language.EN_US)
|
||||
assert isinstance(ctx, TTSContext)
|
||||
|
||||
def test_default_split_pattern(self):
|
||||
ctx = build_tts_context(language="a", subtitle_mode="Disabled")
|
||||
ctx = build_tts_context(language=Language.EN_US, subtitle_mode="Disabled")
|
||||
assert isinstance(ctx.split_pattern, str)
|
||||
assert len(ctx.split_pattern) > 0
|
||||
|
||||
def test_english_uses_newline_split(self):
|
||||
ctx = build_tts_context(language="a", subtitle_mode="Disabled")
|
||||
ctx = build_tts_context(language=Language.EN_US, subtitle_mode="Disabled")
|
||||
assert ctx.split_pattern == "\n"
|
||||
|
||||
def test_cjk_uses_punctuation_split(self):
|
||||
ctx = build_tts_context(language="j", subtitle_mode="Disabled")
|
||||
assert "[.??.?!]" in ctx.split_pattern or "\\n" not in ctx.split_pattern
|
||||
ctx = build_tts_context(language=Language.JA, subtitle_mode="Disabled")
|
||||
assert r"\n" in ctx.split_pattern
|
||||
|
||||
def test_pronunciation_overrides_compiled(self):
|
||||
overrides = [
|
||||
@@ -176,6 +177,7 @@ class TestBuildTtsContext:
|
||||
}
|
||||
]
|
||||
ctx = build_tts_context(
|
||||
language=Language.EN_US,
|
||||
pronunciation_overrides=overrides,
|
||||
)
|
||||
assert ctx.pronunciation_rules is not None
|
||||
@@ -190,6 +192,7 @@ class TestBuildTtsContext:
|
||||
}
|
||||
]
|
||||
ctx = build_tts_context(
|
||||
language=Language.EN_US,
|
||||
manual_overrides=overrides,
|
||||
)
|
||||
assert ctx.pronunciation_rules is not None
|
||||
@@ -203,6 +206,7 @@ class TestBuildTtsContext:
|
||||
{"token": "x", "pronunciation": "RIGHT", "normalized": "x"}
|
||||
]
|
||||
ctx = build_tts_context(
|
||||
language=Language.EN_US,
|
||||
pronunciation_overrides=pronunciation,
|
||||
manual_overrides=manual,
|
||||
)
|
||||
@@ -224,22 +228,23 @@ class TestBuildTtsContext:
|
||||
}
|
||||
]
|
||||
ctx = build_tts_context(
|
||||
language=Language.EN_US,
|
||||
heteronym_overrides=overrides,
|
||||
)
|
||||
assert ctx.heteronym_rules is not None
|
||||
|
||||
def test_usage_counter_passed_through(self):
|
||||
counter = {}
|
||||
ctx = build_tts_context(usage_counter=counter)
|
||||
ctx = build_tts_context(language=Language.EN_US, usage_counter=counter)
|
||||
assert ctx.usage_counter is counter
|
||||
|
||||
def test_usage_counter_default_empty(self):
|
||||
ctx = build_tts_context()
|
||||
ctx = build_tts_context(language=Language.EN_US)
|
||||
assert ctx.usage_counter == {}
|
||||
|
||||
def test_normalization_overrides_stored(self):
|
||||
overrides = {"normalization_numbers": False}
|
||||
ctx = build_tts_context(normalization_overrides=overrides)
|
||||
ctx = build_tts_context(language=Language.EN_US, normalization_overrides=overrides)
|
||||
assert ctx.normalization_overrides is overrides
|
||||
|
||||
def test_speakers_used_for_pronunciation(self):
|
||||
@@ -250,7 +255,7 @@ class TestBuildTtsContext:
|
||||
"resolved_voice": "M1",
|
||||
}
|
||||
}
|
||||
ctx = build_tts_context(speakers=speakers)
|
||||
ctx = build_tts_context(language=Language.EN_US, speakers=speakers)
|
||||
assert ctx.pronunciation_rules is not None
|
||||
assert len(ctx.pronunciation_rules) >= 1
|
||||
|
||||
@@ -265,7 +270,7 @@ class TestBuildTtsContext:
|
||||
mock_cfg.return_value = MagicMock(convert_numbers=True)
|
||||
with patch("builtins.__import__", side_effect=ImportError):
|
||||
try:
|
||||
build_tts_context(log_callback=lambda lvl, msg: logs.append((lvl, msg)))
|
||||
build_tts_context(language=Language.EN_US, log_callback=lambda lvl, msg: logs.append((lvl, msg)))
|
||||
except ImportError:
|
||||
pass
|
||||
# If num2words is missing and convert_numbers is True, a warning should be logged
|
||||
@@ -276,7 +281,7 @@ class TestBuildTtsContext:
|
||||
"normalization_apostrophe_mode": "llm",
|
||||
}):
|
||||
with pytest.raises(RuntimeError, match="LLM"):
|
||||
build_tts_context()
|
||||
build_tts_context(language=Language.EN_US)
|
||||
|
||||
def test_dict_source_accepted(self):
|
||||
"""merge_pronunciation_overrides should accept a dict."""
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.domain.pipeline_factory import (
|
||||
PipelinePool,
|
||||
create_pipeline_for_job,
|
||||
@@ -31,7 +32,7 @@ class TestCreatePipelineForJob:
|
||||
@patch("abogen.domain.pipeline_factory.is_plugin_registered", return_value=True)
|
||||
def test_supertonic_provider(self, _reg, mock_create):
|
||||
mock_create.return_value = MagicMock()
|
||||
result = create_pipeline_for_job("supertonic", "en", use_gpu=True)
|
||||
result = create_pipeline_for_job("supertonic", Language.EN_US, use_gpu=True)
|
||||
mock_create.assert_called_once_with("supertonic")
|
||||
assert result is mock_create.return_value
|
||||
|
||||
@@ -40,43 +41,41 @@ class TestCreatePipelineForJob:
|
||||
@patch("abogen.domain.pipeline_factory.resolve_device", return_value="cpu")
|
||||
def test_kokoro_provider(self, _dev, _reg, mock_create):
|
||||
mock_create.return_value = MagicMock()
|
||||
result = create_pipeline_for_job("kokoro", "en", use_gpu=False)
|
||||
# "en" → fallback to EN_US → kokoro code "a"
|
||||
mock_create.assert_called_once_with("kokoro", lang_code="a", device="cpu")
|
||||
result = create_pipeline_for_job("kokoro", Language.EN_US, use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", language=Language.EN_US, device="cpu")
|
||||
assert result is mock_create.return_value
|
||||
|
||||
@patch("abogen.domain.pipeline_factory.create_pipeline")
|
||||
@patch("abogen.domain.pipeline_factory.is_plugin_registered", return_value=True)
|
||||
@patch("abogen.domain.pipeline_factory.resolve_device", return_value="cpu")
|
||||
def test_kokoro_provider_iso_code(self, _dev, _reg, mock_create):
|
||||
def test_kokoro_provider_en_gb(self, _dev, _reg, mock_create):
|
||||
mock_create.return_value = MagicMock()
|
||||
result = create_pipeline_for_job("kokoro", "en-GB", use_gpu=False)
|
||||
# "en-GB" → EN_GB → kokoro code "b"
|
||||
mock_create.assert_called_once_with("kokoro", lang_code="b", device="cpu")
|
||||
result = create_pipeline_for_job("kokoro", Language.EN_GB, use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", language=Language.EN_GB, device="cpu")
|
||||
|
||||
@patch("abogen.domain.pipeline_factory.create_pipeline")
|
||||
@patch("abogen.domain.pipeline_factory.is_plugin_registered", return_value=False)
|
||||
@patch("abogen.domain.pipeline_factory.resolve_device", return_value="cpu")
|
||||
def test_unknown_provider_falls_back_to_kokoro(self, _dev, _reg, mock_create):
|
||||
mock_create.return_value = MagicMock()
|
||||
result = create_pipeline_for_job("unknown_provider", "en", use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", lang_code="a", device="cpu")
|
||||
result = create_pipeline_for_job("unknown_provider", Language.EN_US, use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", language=Language.EN_US, device="cpu")
|
||||
|
||||
@patch("abogen.domain.pipeline_factory.create_pipeline")
|
||||
@patch("abogen.domain.pipeline_factory.is_plugin_registered", return_value=True)
|
||||
@patch("abogen.domain.pipeline_factory.resolve_device", return_value="cpu")
|
||||
def test_empty_provider_defaults_to_kokoro(self, _dev, _reg, mock_create):
|
||||
mock_create.return_value = MagicMock()
|
||||
result = create_pipeline_for_job("", "en", use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", lang_code="a", device="cpu")
|
||||
result = create_pipeline_for_job("", Language.EN_US, use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", language=Language.EN_US, device="cpu")
|
||||
|
||||
@patch("abogen.domain.pipeline_factory.create_pipeline")
|
||||
@patch("abogen.domain.pipeline_factory.is_plugin_registered", return_value=True)
|
||||
@patch("abogen.domain.pipeline_factory.resolve_device", return_value="cpu")
|
||||
def test_none_provider_defaults_to_kokoro(self, _dev, _reg, mock_create):
|
||||
mock_create.return_value = MagicMock()
|
||||
result = create_pipeline_for_job(None, "en", use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", lang_code="a", device="cpu")
|
||||
result = create_pipeline_for_job(None, Language.EN_US, use_gpu=False)
|
||||
mock_create.assert_called_once_with("kokoro", language=Language.EN_US, device="cpu")
|
||||
|
||||
|
||||
class TestDisposePipelines:
|
||||
@@ -110,11 +109,11 @@ class TestPipelinePool:
|
||||
mock_create.return_value = mock_pipeline
|
||||
pool = PipelinePool()
|
||||
|
||||
result = pool.get("kokoro", "en", use_gpu=True)
|
||||
result = pool.get("kokoro", Language.EN_US, use_gpu=True)
|
||||
assert result is mock_pipeline
|
||||
mock_create.assert_called_once()
|
||||
|
||||
result2 = pool.get("kokoro", "en", use_gpu=True)
|
||||
result2 = pool.get("kokoro", Language.EN_US, use_gpu=True)
|
||||
assert result2 is mock_pipeline
|
||||
assert mock_create.call_count == 1
|
||||
|
||||
@@ -125,10 +124,10 @@ class TestPipelinePool:
|
||||
pool = PipelinePool()
|
||||
|
||||
request = MagicMock()
|
||||
pool.get("kokoro", "en", use_gpu=True, request=request)
|
||||
pool.get("kokoro", Language.EN_US, use_gpu=True, request=request)
|
||||
assert mock_cache.call_count == 1
|
||||
|
||||
pool.get("kokoro", "en", use_gpu=True, request=request)
|
||||
pool.get("kokoro", Language.EN_US, use_gpu=True, request=request)
|
||||
assert mock_cache.call_count == 1
|
||||
|
||||
@patch("abogen.domain.pipeline_factory.initialize_voice_cache")
|
||||
@@ -136,7 +135,7 @@ class TestPipelinePool:
|
||||
def test_get_no_job_skips_voice_cache(self, mock_create, mock_cache):
|
||||
mock_create.return_value = MagicMock()
|
||||
pool = PipelinePool()
|
||||
pool.get("kokoro", "en", use_gpu=True)
|
||||
pool.get("kokoro", Language.EN_US, use_gpu=True)
|
||||
mock_cache.assert_not_called()
|
||||
|
||||
@patch("abogen.domain.pipeline_factory.create_pipeline_for_job")
|
||||
@@ -146,8 +145,8 @@ class TestPipelinePool:
|
||||
mock_create.side_effect = [p1, p2]
|
||||
pool = PipelinePool()
|
||||
|
||||
r1 = pool.get("kokoro", "en", use_gpu=True)
|
||||
r2 = pool.get("supertonic", "en", use_gpu=True)
|
||||
r1 = pool.get("kokoro", Language.EN_US, use_gpu=True)
|
||||
r2 = pool.get("supertonic", Language.EN_US, use_gpu=True)
|
||||
assert r1 is p1
|
||||
assert r2 is p2
|
||||
assert mock_create.call_count == 2
|
||||
@@ -160,8 +159,8 @@ class TestPipelinePool:
|
||||
mock_create.side_effect = [p1, p2]
|
||||
pool = PipelinePool()
|
||||
|
||||
pool.get("kokoro", "en", use_gpu=True)
|
||||
pool.get("supertonic", "en", use_gpu=True)
|
||||
pool.get("kokoro", Language.EN_US, use_gpu=True)
|
||||
pool.get("supertonic", Language.EN_US, use_gpu=True)
|
||||
pool.dispose_all()
|
||||
|
||||
p1.dispose.assert_called_once()
|
||||
@@ -181,5 +180,5 @@ class TestPipelinePool:
|
||||
def test_unknown_provider_falls_back(self, _reg, _cache, mock_create):
|
||||
mock_create.return_value = MagicMock()
|
||||
pool = PipelinePool()
|
||||
pool.get("bogus_provider", "en", use_gpu=True)
|
||||
mock_create.assert_called_once_with("kokoro", "en", True)
|
||||
pool.get("bogus_provider", Language.EN_US, use_gpu=True)
|
||||
mock_create.assert_called_once_with("kokoro", Language.EN_US, True)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.webui.routes.utils import synthesize
|
||||
|
||||
|
||||
@@ -45,7 +46,7 @@ def test_preview_applies_manual_override_before_normalization(monkeypatch):
|
||||
synthesize.generate_preview_audio(
|
||||
text="He said Unfu*k loudly.",
|
||||
voice_spec="M1",
|
||||
language="en",
|
||||
language=Language.EN_US,
|
||||
speed=1.0,
|
||||
use_gpu=False,
|
||||
tts_provider="supertonic",
|
||||
|
||||
+21
-25
@@ -1,10 +1,11 @@
|
||||
"""Tests for split pattern logic (3 identical copies in codebase)."""
|
||||
"""Tests for split pattern logic."""
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
import pytest
|
||||
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.domain.split_pattern import get_split_pattern
|
||||
|
||||
|
||||
@@ -12,49 +13,49 @@ from abogen.domain.split_pattern import get_split_pattern
|
||||
|
||||
class TestEnglish:
|
||||
def test_english_sentence(self):
|
||||
assert get_split_pattern("en-US", "Sentence") == "\n"
|
||||
assert get_split_pattern(Language.EN_US, "Sentence") == "\n"
|
||||
|
||||
def test_english_sentence_comma(self):
|
||||
assert get_split_pattern("en-US", "Sentence + Comma") == "\n"
|
||||
assert get_split_pattern(Language.EN_US, "Sentence + Comma") == "\n"
|
||||
|
||||
def test_english_line(self):
|
||||
assert get_split_pattern("en-US", "Line") == "\n"
|
||||
assert get_split_pattern(Language.EN_US, "Line") == "\n"
|
||||
|
||||
def test_english_disabled(self):
|
||||
assert get_split_pattern("en-US", "Disabled") == "\n"
|
||||
assert get_split_pattern(Language.EN_US, "Disabled") == "\n"
|
||||
|
||||
def test_english_gb(self):
|
||||
assert get_split_pattern("en-GB", "Sentence") == "\n"
|
||||
assert get_split_pattern(Language.EN_GB, "Sentence") == "\n"
|
||||
|
||||
|
||||
# --- CJK languages ---
|
||||
|
||||
class TestCJK:
|
||||
def test_chinese_disabled(self):
|
||||
pattern = get_split_pattern("zh", "Disabled")
|
||||
pattern = get_split_pattern(Language.ZH, "Disabled")
|
||||
assert pattern != "\n"
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_chinese_line(self):
|
||||
pattern = get_split_pattern("zh", "Line")
|
||||
pattern = get_split_pattern(Language.ZH, "Line")
|
||||
assert pattern != "\n"
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_chinese_sentence(self):
|
||||
pattern = get_split_pattern("zh", "Sentence")
|
||||
pattern = get_split_pattern(Language.ZH, "Sentence")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_chinese_sentence_comma(self):
|
||||
pattern = get_split_pattern("zh", "Sentence + Comma")
|
||||
pattern = get_split_pattern(Language.ZH, "Sentence + Comma")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_japanese_disabled(self):
|
||||
pattern = get_split_pattern("ja", "Disabled")
|
||||
pattern = get_split_pattern(Language.JA, "Disabled")
|
||||
assert pattern != "\n"
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_japanese_sentence(self):
|
||||
pattern = get_split_pattern("ja", "Sentence")
|
||||
pattern = get_split_pattern(Language.JA, "Sentence")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
|
||||
@@ -62,22 +63,17 @@ class TestCJK:
|
||||
|
||||
class TestOtherLanguages:
|
||||
def test_spanish_sentence(self):
|
||||
pattern = get_split_pattern("es", "Sentence")
|
||||
pattern = get_split_pattern(Language.ES, "Sentence")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_spanish_line(self):
|
||||
assert get_split_pattern("es", "Line") == "\n"
|
||||
assert get_split_pattern(Language.ES, "Line") == "\n"
|
||||
|
||||
def test_spanish_disabled(self):
|
||||
# canonical: \n+ for non-CJK Disabled
|
||||
assert get_split_pattern("es", "Disabled") == r"\n+"
|
||||
assert get_split_pattern(Language.ES, "Disabled") == r"\n+"
|
||||
|
||||
def test_french_sentence_comma(self):
|
||||
pattern = get_split_pattern("fr", "Sentence + Comma")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_unknown_lang(self):
|
||||
pattern = get_split_pattern("x", "Sentence")
|
||||
pattern = get_split_pattern(Language.FR, "Sentence + Comma")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
|
||||
@@ -85,17 +81,17 @@ class TestOtherLanguages:
|
||||
|
||||
class TestPatternStructure:
|
||||
def test_sentence_has_lookbehind(self):
|
||||
pattern = get_split_pattern("es", "Sentence")
|
||||
pattern = get_split_pattern(Language.ES, "Sentence")
|
||||
assert r"(?<=" in pattern
|
||||
|
||||
def test_sentence_comma_has_comma_chars(self):
|
||||
pattern = get_split_pattern("es", "Sentence + Comma")
|
||||
pattern = get_split_pattern(Language.ES, "Sentence + Comma")
|
||||
assert "," in pattern
|
||||
|
||||
def test_cjk_spacing_uses_star(self):
|
||||
pattern = get_split_pattern("zh", "Sentence")
|
||||
pattern = get_split_pattern(Language.ZH, "Sentence")
|
||||
assert r"\s*" in pattern
|
||||
|
||||
def test_non_cjk_spacing_uses_plus(self):
|
||||
pattern = get_split_pattern("es", "Sentence")
|
||||
pattern = get_split_pattern(Language.ES, "Sentence")
|
||||
assert r"\s+" in pattern
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from abogen.domain.enums import Language
|
||||
from abogen.domain.subtitle_generation import (
|
||||
process_subtitle_tokens,
|
||||
PUNCTUATION_SENTENCE,
|
||||
@@ -20,7 +21,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="Sentence",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
assert entries == []
|
||||
|
||||
@@ -41,7 +42,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="Disabled",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
# Disabled mode doesn't have special handling in current implementation
|
||||
# It processes tokens normally
|
||||
@@ -59,7 +60,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="Line",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
# Line mode processes all tokens into entries
|
||||
assert len(entries) >= 1
|
||||
@@ -82,7 +83,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="Sentence",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
assert len(entries) >= 1
|
||||
# Should have at least one entry with both sentences or split
|
||||
@@ -106,7 +107,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="2", # 2 words per entry
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
assert len(entries) >= 2
|
||||
# Check that entries are split roughly by word count
|
||||
@@ -125,7 +126,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="Line",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
fallback_end_time=10.0,
|
||||
)
|
||||
assert len(entries) == 1
|
||||
@@ -143,7 +144,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="Sentence + Highlighting",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
assert len(entries) >= 1
|
||||
# Should contain karaoke tags
|
||||
@@ -162,7 +163,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=3,
|
||||
subtitle_mode="Line",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
# Should have more than 1 entry due to word limit
|
||||
assert len(entries) > 1
|
||||
@@ -179,7 +180,7 @@ class TestProcessSubtitleTokens:
|
||||
subtitle_entries=entries,
|
||||
max_subtitle_words=50,
|
||||
subtitle_mode="Sentence",
|
||||
lang_code="a",
|
||||
language=Language.EN_US,
|
||||
)
|
||||
assert len(entries) >= 1
|
||||
# Check that timing is preserved
|
||||
|
||||
Reference in New Issue
Block a user