dengjen-tts-python 2.1.0

Python bindings for the dengjen-tts speech synthesis engine
import inspect
import os
import subprocess
import sys
import unittest
from pathlib import Path

import pydengjen

DATA_ENV = "DENGJEN_ESPEAKNG_DATA_DIRECTORY"
PACKAGE_DIR = Path(pydengjen.__file__).resolve().parent
PROBE = (
    "import ctypes, pydengjen\n"
    "libc = ctypes.CDLL(None)\n"
    "libc.getenv.restype = ctypes.c_char_p\n"
    f"print((libc.getenv({DATA_ENV.encode()!r}) or b'').decode())\n"
)


def defaults(callable_):
    parameters = inspect.signature(callable_).parameters.values()
    return {p.name: p.default for p in parameters if p.default is not inspect.Parameter.empty}


def data_directory_seen_by_a_fresh_interpreter(value):
    env = {k: v for k, v in os.environ.items() if k != DATA_ENV}
    if value is not None:
        env[DATA_ENV] = value
    result = subprocess.run(
        [sys.executable, "-c", PROBE], env=env, capture_output=True, text=True, check=True
    )
    return result.stdout.strip()


class OptionalArgumentTests(unittest.TestCase):
    def test_audio_output_config_needs_no_arguments(self):
        pydengjen.AudioOutputConfig()
        pydengjen.AudioOutputConfig(rate=60)

    def test_synthesis_methods_default_their_optional_arguments_to_none(self):
        expected = {
            "synthesize": ["audio_output_config"],
            "synthesize_lazy": ["audio_output_config"],
            "synthesize_parallel": ["audio_output_config"],
            "synthesize_batched": ["audio_output_config", "batch_size"],
            "synthesize_streamed": ["audio_output_config", "chunk_size", "chunk_padding"],
            "synthesize_to_file": ["audio_output_config"],
        }
        for name, optional in expected.items():
            with self.subTest(method=name):
                found = defaults(getattr(pydengjen.Dengjen, name))
                self.assertEqual(found, {key: None for key in optional})

    def test_phonemize_text_defaults_everything_after_the_language_to_none(self):
        names = ["phoneme_separator", "remove_lang_switch_flags", "remove_stress", "use_tashkeel"]
        self.assertEqual(defaults(pydengjen.phonemize_text), {name: None for name in names})


class PiperScalesTests(unittest.TestCase):
    def test_values_are_readable(self):
        scales = pydengjen.PiperScales(1.0, 0.5, 0.25)
        self.assertEqual((scales.length_scale, scales.noise_scale, scales.noise_w), (1.0, 0.5, 0.25))


@unittest.skipIf(sys.platform == "win32", "the probe reads the C environment through libc")
class EspeakDataTests(unittest.TestCase):
    def test_the_package_ships_the_data(self):
        self.assertTrue((PACKAGE_DIR / "espeak-ng-data" / "phontab").is_file())

    def test_directory_defaults_to_the_package(self):
        self.assertEqual(data_directory_seen_by_a_fresh_interpreter(None), str(PACKAGE_DIR))

    def test_a_directory_the_user_set_is_kept(self):
        self.assertEqual(data_directory_seen_by_a_fresh_interpreter("/somewhere/else"), "/somewhere/else")


if __name__ == "__main__":
    unittest.main()