From a3d23e9dad9856f1cd6ff2bc66ffd3cafda101af Mon Sep 17 00:00:00 2001 From: faltiska Date: Sat, 7 Jun 2025 11:02:10 +0300 Subject: [PATCH] Dockerfile optimizations: Moved to nvidia cuda base image. Merged all apt-get install commands into one. Removed espeak-ng. Added and uv cache clean command at the end. Removed g++ at the end. Concatenated all ENV commands. Removed api tests folder. --- api/tests/__init__.py | 1 - api/tests/conftest.py | 71 --- api/tests/test_audio_service.py | 256 ----------- api/tests/test_data/generate_test_data.py | 23 - api/tests/test_data/test_audio.npy | Bin 96128 -> 0 bytes api/tests/test_development.py | 34 -- api/tests/test_kokoro_v1.py | 165 ------- api/tests/test_normalizer.py | 317 -------------- api/tests/test_openai_endpoints.py | 499 ---------------------- api/tests/test_paths.py | 138 ------ api/tests/test_text_processor.py | 167 -------- api/tests/test_tts_service.py | 126 ------ docker/gpu/Dockerfile | 84 ++-- pyproject.toml | 3 +- 14 files changed, 39 insertions(+), 1845 deletions(-) delete mode 100644 api/tests/__init__.py delete mode 100644 api/tests/conftest.py delete mode 100644 api/tests/test_audio_service.py delete mode 100644 api/tests/test_data/generate_test_data.py delete mode 100644 api/tests/test_data/test_audio.npy delete mode 100644 api/tests/test_development.py delete mode 100644 api/tests/test_kokoro_v1.py delete mode 100644 api/tests/test_normalizer.py delete mode 100644 api/tests/test_openai_endpoints.py delete mode 100644 api/tests/test_paths.py delete mode 100644 api/tests/test_text_processor.py delete mode 100644 api/tests/test_tts_service.py diff --git a/api/tests/__init__.py b/api/tests/__init__.py deleted file mode 100644 index b9911d8..0000000 --- a/api/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Make tests directory a Python package diff --git a/api/tests/conftest.py b/api/tests/conftest.py deleted file mode 100644 index 2e3bba8..0000000 --- a/api/tests/conftest.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -from pathlib import Path -from unittest.mock import AsyncMock, MagicMock, patch - -import numpy as np -import pytest -import pytest_asyncio -import torch - -from api.src.inference.model_manager import ModelManager -from api.src.inference.voice_manager import VoiceManager -from api.src.services.tts_service import TTSService -from api.src.structures.model_schemas import VoiceConfig - - -@pytest.fixture -def mock_voice_tensor(): - """Load a real voice tensor for testing.""" - voice_path = os.path.join( - os.path.dirname(os.path.dirname(__file__)), "src/voices/af_bella.pt" - ) - return torch.load(voice_path, map_location="cpu", weights_only=False) - - -@pytest.fixture -def mock_audio_output(): - """Load pre-generated test audio for consistent testing.""" - test_audio_path = os.path.join( - os.path.dirname(__file__), "test_data/test_audio.npy" - ) - return np.load(test_audio_path) # Return as numpy array instead of bytes - - -@pytest_asyncio.fixture -async def mock_model_manager(mock_audio_output): - """Mock model manager for testing.""" - manager = AsyncMock(spec=ModelManager) - manager.get_backend = MagicMock() - - async def mock_generate(*args, **kwargs): - # Simulate successful audio generation - return np.random.rand(24000).astype(np.float32) # 1 second of random audio data - - manager.generate = AsyncMock(side_effect=mock_generate) - return manager - - -@pytest_asyncio.fixture -async def mock_voice_manager(mock_voice_tensor): - """Mock voice manager for testing.""" - manager = AsyncMock(spec=VoiceManager) - manager.get_voice_path = MagicMock(return_value="/mock/path/voice.pt") - manager.load_voice = AsyncMock(return_value=mock_voice_tensor) - manager.list_voices = AsyncMock(return_value=["voice1", "voice2"]) - manager.combine_voices = AsyncMock(return_value="voice1_voice2") - return manager - - -@pytest_asyncio.fixture -async def tts_service(mock_model_manager, mock_voice_manager): - """Get mocked TTS service instance.""" - service = TTSService() - service.model_manager = mock_model_manager - service._voice_manager = mock_voice_manager - return service - - -@pytest.fixture -def test_voice(): - """Return a test voice name.""" - return "voice1" diff --git a/api/tests/test_audio_service.py b/api/tests/test_audio_service.py deleted file mode 100644 index 5ba5392..0000000 --- a/api/tests/test_audio_service.py +++ /dev/null @@ -1,256 +0,0 @@ -"""Tests for AudioService""" - -from unittest.mock import patch - -import numpy as np -import pytest - -from api.src.inference.base import AudioChunk -from api.src.services.audio import AudioNormalizer, AudioService -from api.src.services.streaming_audio_writer import StreamingAudioWriter - - -@pytest.fixture(autouse=True) -def mock_settings(): - """Mock settings for all tests""" - with patch("api.src.services.audio.settings") as mock_settings: - mock_settings.gap_trim_ms = 250 - yield mock_settings - - -@pytest.fixture -def sample_audio(): - """Generate a simple sine wave for testing""" - sample_rate = 24000 - duration = 0.1 # 100ms - t = np.linspace(0, duration, int(sample_rate * duration)) - frequency = 440 # A4 note - return np.sin(2 * np.pi * frequency * t).astype(np.float32), sample_rate - - -@pytest.mark.asyncio -async def test_convert_to_wav(sample_audio): - """Test converting to WAV format""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("wav", sample_rate=24000) - # Write and finalize in one step for WAV - audio_chunk = await AudioService.convert_audio( - AudioChunk(audio_data), "wav", writer, is_last_chunk=False - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - # Check WAV header - assert audio_chunk.output.startswith(b"RIFF") - assert b"WAVE" in audio_chunk.output[:12] - - -@pytest.mark.asyncio -async def test_convert_to_mp3(sample_audio): - """Test converting to MP3 format""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("mp3", sample_rate=24000) - - audio_chunk = await AudioService.convert_audio( - AudioChunk(audio_data), "mp3", writer - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - # Check MP3 header (ID3 or MPEG frame sync) - assert audio_chunk.output.startswith(b"ID3") or audio_chunk.output.startswith( - b"\xff\xfb" - ) - - -@pytest.mark.asyncio -async def test_convert_to_opus(sample_audio): - """Test converting to Opus format""" - - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("opus", sample_rate=24000) - - audio_chunk = await AudioService.convert_audio( - AudioChunk(audio_data), "opus", writer - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - # Check OGG header - assert audio_chunk.output.startswith(b"OggS") - - -@pytest.mark.asyncio -async def test_convert_to_flac(sample_audio): - """Test converting to FLAC format""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("flac", sample_rate=24000) - - audio_chunk = await AudioService.convert_audio( - AudioChunk(audio_data), "flac", writer - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - # Check FLAC header - assert audio_chunk.output.startswith(b"fLaC") - - -@pytest.mark.asyncio -async def test_convert_to_aac(sample_audio): - """Test converting to M4A format""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("aac", sample_rate=24000) - - audio_chunk = await AudioService.convert_audio( - AudioChunk(audio_data), "aac", writer - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - # Check ADTS header (AAC) - assert audio_chunk.output.startswith(b"\xff\xf0") or audio_chunk.output.startswith( - b"\xff\xf1" - ) - - -@pytest.mark.asyncio -async def test_convert_to_pcm(sample_audio): - """Test converting to PCM format""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("pcm", sample_rate=24000) - - audio_chunk = await AudioService.convert_audio( - AudioChunk(audio_data), "pcm", writer - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - # PCM is raw bytes, so no header to check - - -@pytest.mark.asyncio -async def test_convert_to_invalid_format_raises_error(sample_audio): - """Test that converting to an invalid format raises an error""" - # audio_data, sample_rate = sample_audio - with pytest.raises(ValueError, match="Unsupported format: invalid"): - writer = StreamingAudioWriter("invalid", sample_rate=24000) - - -@pytest.mark.asyncio -async def test_normalization_wav(sample_audio): - """Test that WAV output is properly normalized to int16 range""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("wav", sample_rate=24000) - - # Create audio data outside int16 range - large_audio = audio_data * 1e5 - # Write and finalize in one step for WAV - audio_chunk = await AudioService.convert_audio( - AudioChunk(large_audio), "wav", writer - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - - -@pytest.mark.asyncio -async def test_normalization_pcm(sample_audio): - """Test that PCM output is properly normalized to int16 range""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("pcm", sample_rate=24000) - - # Create audio data outside int16 range - large_audio = audio_data * 1e5 - audio_chunk = await AudioService.convert_audio( - AudioChunk(large_audio), "pcm", writer - ) - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - - -@pytest.mark.asyncio -async def test_invalid_audio_data(): - """Test handling of invalid audio data""" - invalid_audio = np.array([]) # Empty array - sample_rate = 24000 - - writer = StreamingAudioWriter("wav", sample_rate=24000) - - with pytest.raises(ValueError): - await AudioService.convert_audio(invalid_audio, sample_rate, "wav", writer) - - -@pytest.mark.asyncio -async def test_different_sample_rates(sample_audio): - """Test converting audio with different sample rates""" - audio_data, _ = sample_audio - sample_rates = [8000, 16000, 44100, 48000] - - for rate in sample_rates: - writer = StreamingAudioWriter("wav", sample_rate=rate) - - audio_chunk = await AudioService.convert_audio( - AudioChunk(audio_data), "wav", writer - ) - - writer.close() - - assert isinstance(audio_chunk.output, bytes) - assert isinstance(audio_chunk, AudioChunk) - assert len(audio_chunk.output) > 0 - - -@pytest.mark.asyncio -async def test_buffer_position_after_conversion(sample_audio): - """Test that buffer position is reset after writing""" - audio_data, sample_rate = sample_audio - - writer = StreamingAudioWriter("wav", sample_rate=24000) - - # Write and finalize in one step for first conversion - audio_chunk1 = await AudioService.convert_audio( - AudioChunk(audio_data), "wav", writer, is_last_chunk=True - ) - assert isinstance(audio_chunk1.output, bytes) - assert isinstance(audio_chunk1, AudioChunk) - # Convert again to ensure buffer was properly reset - - writer = StreamingAudioWriter("wav", sample_rate=24000) - - audio_chunk2 = await AudioService.convert_audio( - AudioChunk(audio_data), "wav", writer, is_last_chunk=True - ) - assert isinstance(audio_chunk2.output, bytes) - assert isinstance(audio_chunk2, AudioChunk) - assert len(audio_chunk1.output) == len(audio_chunk2.output) diff --git a/api/tests/test_data/generate_test_data.py b/api/tests/test_data/generate_test_data.py deleted file mode 100644 index 3f6b7cf..0000000 --- a/api/tests/test_data/generate_test_data.py +++ /dev/null @@ -1,23 +0,0 @@ -import os - -import numpy as np - - -def generate_test_audio(): - """Generate test audio data - 1 second of 440Hz tone""" - # Create 1 second of silence at 24kHz - audio = np.zeros(24000, dtype=np.float32) - - # Add a simple sine wave to make it non-zero - t = np.linspace(0, 1, 24000) - audio += 0.5 * np.sin(2 * np.pi * 440 * t) # 440 Hz tone at half amplitude - - # Create test_data directory if it doesn't exist - os.makedirs("api/tests/test_data", exist_ok=True) - - # Save the test audio - np.save("api/tests/test_data/test_audio.npy", audio) - - -if __name__ == "__main__": - generate_test_audio() diff --git a/api/tests/test_data/test_audio.npy b/api/tests/test_data/test_audio.npy deleted file mode 100644 index 2e06aa9fb23da8358abc9ce914785de23c9b1358..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96128 zcmbSy^;cB?_qB@MU2j{lTP&En?fTe(-GSKMg$kmeq=-@~7NB4&F?UNyOGyd>(%lUc z&;9-j�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≪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 AsyncGenerator[AudioChunk, None]: - yield AudioChunk(np.ndarray([], np.int16), output=mock_audio_bytes) - - service.generate_audio_stream = mock_stream - service.list_voices.return_value = ["test_voice", "voice1", "voice2"] - service.combine_voices.return_value = "voice1_voice2" - - mock_get.return_value = service - mock_get.side_effect = None - yield service - - -@patch("api.src.services.audio.AudioService.convert_audio") -def test_openai_speech_endpoint( - mock_convert, mock_tts_service, test_voice, mock_audio_bytes -): - """Test the OpenAI-compatible speech endpoint with basic MP3 generation""" - # Configure mocks - mock_tts_service.generate_audio.return_value = AudioChunk(np.zeros(1000, np.int16)) - mock_convert.return_value = AudioChunk( - np.zeros(1000, np.int16), output=mock_audio_bytes - ) - - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "Hello world", - "voice": test_voice, - "response_format": "mp3", - "stream": False, - }, - ) - assert response.status_code == 200 - assert response.headers["content-type"] == "audio/mpeg" - assert len(response.content) > 0 - assert response.content == mock_audio_bytes + mock_audio_bytes - - mock_tts_service.generate_audio.assert_called_once() - assert mock_convert.call_count == 2 - - -def test_openai_speech_streaming(mock_tts_service, test_voice, mock_audio_bytes): - """Test the OpenAI-compatible speech endpoint with streaming""" - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "Hello world", - "voice": test_voice, - "response_format": "mp3", - "stream": True, - }, - ) - assert response.status_code == 200 - assert response.headers["content-type"] == "audio/mpeg" - assert "Transfer-Encoding" in response.headers - assert response.headers["Transfer-Encoding"] == "chunked" - - content = b"" - for chunk in response.iter_bytes(): - content += chunk - assert content == mock_audio_bytes - - -def test_openai_speech_pcm_streaming(mock_tts_service, test_voice, mock_audio_bytes): - """Test PCM streaming format""" - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "Hello world", - "voice": test_voice, - "response_format": "pcm", - "stream": True, - }, - ) - assert response.status_code == 200 - assert response.headers["content-type"] == "audio/pcm" - - content = b"" - for chunk in response.iter_bytes(): - content += chunk - assert content == mock_audio_bytes - - -def test_openai_speech_invalid_voice(mock_tts_service): - """Test error handling for invalid voice""" - mock_tts_service.generate_audio.side_effect = ValueError( - "Voice 'invalid_voice' not found" - ) - - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "Hello world", - "voice": "invalid_voice", - "response_format": "mp3", - "stream": False, - }, - ) - assert response.status_code == 400 - error_response = response.json() - assert error_response["detail"]["error"] == "validation_error" - assert "Voice 'invalid_voice' not found" in error_response["detail"]["message"] - assert error_response["detail"]["type"] == "invalid_request_error" - - -def test_openai_speech_empty_text(mock_tts_service, test_voice): - """Test error handling for empty text""" - - async def mock_error_stream(*args, **kwargs): - raise ValueError("Text is empty after preprocessing") - - mock_tts_service.generate_audio = mock_error_stream - mock_tts_service.list_voices.return_value = ["test_voice"] - - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "", - "voice": test_voice, - "response_format": "mp3", - "stream": False, - }, - ) - assert response.status_code == 400 - error_response = response.json() - assert error_response["detail"]["error"] == "validation_error" - assert "Text is empty after preprocessing" in error_response["detail"]["message"] - assert error_response["detail"]["type"] == "invalid_request_error" - - -def test_openai_speech_invalid_format(mock_tts_service, test_voice): - """Test error handling for invalid format""" - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "Hello world", - "voice": test_voice, - "response_format": "invalid_format", - "stream": False, - }, - ) - assert response.status_code == 422 # Validation error from Pydantic - - -def test_list_voices(mock_tts_service): - """Test listing available voices""" - # Override the mock for this specific test - mock_tts_service.list_voices.return_value = ["voice1", "voice2"] - - response = client.get("/v1/audio/voices") - assert response.status_code == 200 - data = response.json() - assert "voices" in data - assert len(data["voices"]) == 2 - assert "voice1" in data["voices"] - assert "voice2" in data["voices"] - - -@patch("api.src.routers.openai_compatible.settings") -def test_combine_voices(mock_settings, mock_tts_service): - """Test combining voices endpoint""" - # Enable local voice saving for this test - mock_settings.allow_local_voice_saving = True - - response = client.post("/v1/audio/voices/combine", json="voice1+voice2") - assert response.status_code == 200 - assert response.headers["content-type"] == "application/octet-stream" - assert "voice1+voice2.pt" in response.headers["content-disposition"] - - -def test_server_error(mock_tts_service, test_voice): - """Test handling of server errors""" - - async def mock_error_stream(*args, **kwargs): - raise RuntimeError("Internal server error") - - mock_tts_service.generate_audio = mock_error_stream - mock_tts_service.list_voices.return_value = ["test_voice"] - - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "Hello world", - "voice": test_voice, - "response_format": "mp3", - "stream": False, - }, - ) - assert response.status_code == 500 - error_response = response.json() - assert error_response["detail"]["error"] == "processing_error" - assert error_response["detail"]["type"] == "server_error" - - -def test_streaming_error(mock_tts_service, test_voice): - """Test handling streaming errors""" - # Mock process_voices to raise the error - mock_tts_service.list_voices.side_effect = RuntimeError("Streaming failed") - - response = client.post( - "/v1/audio/speech", - json={ - "model": "kokoro", - "input": "Hello world", - "voice": test_voice, - "response_format": "mp3", - "stream": True, - }, - ) - - assert response.status_code == 500 - error_data = response.json() - assert error_data["detail"]["error"] == "processing_error" - assert error_data["detail"]["type"] == "server_error" - assert "Streaming failed" in error_data["detail"]["message"] - - -@pytest.mark.asyncio -async def test_streaming_initialization_error(): - """Test handling of streaming initialization errors""" - mock_service = AsyncMock() - - async def mock_error_stream(*args, **kwargs): - if False: # This makes it a proper generator - yield b"" - raise RuntimeError("Failed to initialize stream") - - mock_service.generate_audio_stream = mock_error_stream - mock_service.list_voices.return_value = ["test_voice"] - - request = OpenAISpeechRequest( - model="kokoro", - input="Test text", - voice="test_voice", - response_format="mp3", - stream=True, - speed=1.0, - ) - - writer = StreamingAudioWriter("mp3", 24000) - - with pytest.raises(RuntimeError) as exc: - async for _ in stream_audio_chunks(mock_service, request, MagicMock(), writer): - pass - - writer.close() - assert "Failed to initialize stream" in str(exc.value) diff --git a/api/tests/test_paths.py b/api/tests/test_paths.py deleted file mode 100644 index 715934e..0000000 --- a/api/tests/test_paths.py +++ /dev/null @@ -1,138 +0,0 @@ -import os -from unittest.mock import patch - -import pytest - -from api.src.core.paths import ( - _find_file, - _scan_directories, - get_content_type, - get_temp_dir_size, - get_temp_file_path, - list_temp_files, -) - - -@pytest.mark.asyncio -async def test_find_file_exists(): - """Test finding existing file.""" - with patch("aiofiles.os.path.exists") as mock_exists: - mock_exists.return_value = True - path = await _find_file("test.txt", ["/test/path"]) - assert path == "/test/path/test.txt" - - -@pytest.mark.asyncio -async def test_find_file_not_exists(): - """Test finding non-existent file.""" - with patch("aiofiles.os.path.exists") as mock_exists: - mock_exists.return_value = False - with pytest.raises(FileNotFoundError, match="File not found"): - await _find_file("test.txt", ["/test/path"]) - - -@pytest.mark.asyncio -async def test_find_file_with_filter(): - """Test finding file with filter function.""" - with patch("aiofiles.os.path.exists") as mock_exists: - mock_exists.return_value = True - filter_fn = lambda p: p.endswith(".txt") - path = await _find_file("test.txt", ["/test/path"], filter_fn) - assert path == "/test/path/test.txt" - - -@pytest.mark.asyncio -async def test_scan_directories(): - """Test scanning directories.""" - mock_entry = type("MockEntry", (), {"name": "test.txt"})() - - with ( - patch("aiofiles.os.path.exists") as mock_exists, - patch("aiofiles.os.scandir") as mock_scandir, - ): - mock_exists.return_value = True - mock_scandir.return_value = [mock_entry] - - files = await _scan_directories(["/test/path"]) - assert "test.txt" in files - - -@pytest.mark.asyncio -async def test_get_content_type(): - """Test content type detection.""" - test_cases = [ - ("test.html", "text/html"), - ("test.js", "application/javascript"), - ("test.css", "text/css"), - ("test.png", "image/png"), - ("test.unknown", "application/octet-stream"), - ] - - for filename, expected in test_cases: - content_type = await get_content_type(filename) - assert content_type == expected - - -@pytest.mark.asyncio -async def test_get_temp_file_path(): - """Test temp file path generation.""" - with ( - patch("aiofiles.os.path.exists") as mock_exists, - patch("aiofiles.os.makedirs") as mock_makedirs, - ): - mock_exists.return_value = False - - path = await get_temp_file_path("test.wav") - assert "test.wav" in path - mock_makedirs.assert_called_once() - - -@pytest.mark.asyncio -async def test_list_temp_files(): - """Test listing temp files.""" - - class MockEntry: - def __init__(self, name): - self.name = name - - def is_file(self): - return True - - mock_entry = MockEntry("test.wav") - - with ( - patch("aiofiles.os.path.exists") as mock_exists, - patch("aiofiles.os.scandir") as mock_scandir, - ): - mock_exists.return_value = True - mock_scandir.return_value = [mock_entry] - - files = await list_temp_files() - assert "test.wav" in files - - -@pytest.mark.asyncio -async def test_get_temp_dir_size(): - """Test getting temp directory size.""" - - class MockEntry: - def __init__(self, path): - self.path = path - - def is_file(self): - return True - - mock_entry = MockEntry("/tmp/test.wav") - mock_stat = type("MockStat", (), {"st_size": 1024})() - - with ( - patch("aiofiles.os.path.exists") as mock_exists, - patch("aiofiles.os.scandir") as mock_scandir, - patch("aiofiles.os.stat") as mock_stat_fn, - ): - mock_exists.return_value = True - mock_scandir.return_value = [mock_entry] - mock_stat_fn.return_value = mock_stat - - size = await get_temp_dir_size() - assert size == 1024 diff --git a/api/tests/test_text_processor.py b/api/tests/test_text_processor.py deleted file mode 100644 index 6ff8282..0000000 --- a/api/tests/test_text_processor.py +++ /dev/null @@ -1,167 +0,0 @@ -import pytest - -from api.src.services.text_processing.text_processor import ( - get_sentence_info, - process_text_chunk, - smart_split, -) - - -def test_process_text_chunk_basic(): - """Test basic text chunk processing.""" - text = "Hello world" - tokens = process_text_chunk(text) - assert isinstance(tokens, list) - assert len(tokens) > 0 - - -def test_process_text_chunk_empty(): - """Test processing empty text.""" - text = "" - tokens = process_text_chunk(text) - assert isinstance(tokens, list) - assert len(tokens) == 0 - - -def test_process_text_chunk_phonemes(): - """Test processing with skip_phonemize.""" - phonemes = "h @ l @U" # Example phoneme sequence - tokens = process_text_chunk(phonemes, skip_phonemize=True) - assert isinstance(tokens, list) - assert len(tokens) > 0 - - -def test_get_sentence_info(): - """Test sentence splitting and info extraction.""" - text = "This is sentence one. This is sentence two! What about three?" - results = get_sentence_info(text, {}) - - assert len(results) == 3 - for sentence, tokens, count in results: - assert isinstance(sentence, str) - assert isinstance(tokens, list) - assert isinstance(count, int) - assert count == len(tokens) - assert count > 0 - - -def test_get_sentence_info_phenomoes(): - """Test sentence splitting and info extraction.""" - text = ( - "This is sentence one. This is two! What about three?" - ) - results = get_sentence_info(text, {"": r"sˈɛntᵊns"}) - - assert len(results) == 3 - assert "sˈɛntᵊns" in results[1][0] - for sentence, tokens, count in results: - assert isinstance(sentence, str) - assert isinstance(tokens, list) - assert isinstance(count, int) - assert count == len(tokens) - assert count > 0 - - -@pytest.mark.asyncio -async def test_smart_split_short_text(): - """Test smart splitting with text under max tokens.""" - text = "This is a short test sentence." - chunks = [] - async for chunk_text, chunk_tokens in smart_split(text): - chunks.append((chunk_text, chunk_tokens)) - - assert len(chunks) == 1 - assert isinstance(chunks[0][0], str) - assert isinstance(chunks[0][1], list) - - -@pytest.mark.asyncio -async def test_smart_split_long_text(): - """Test smart splitting with longer text.""" - # Create text that should split into multiple chunks - text = ". ".join(["This is test sentence number " + str(i) for i in range(20)]) - - chunks = [] - async for chunk_text, chunk_tokens in smart_split(text): - chunks.append((chunk_text, chunk_tokens)) - - assert len(chunks) > 1 - for chunk_text, chunk_tokens in chunks: - assert isinstance(chunk_text, str) - assert isinstance(chunk_tokens, list) - assert len(chunk_tokens) > 0 - - -@pytest.mark.asyncio -async def test_smart_split_with_punctuation(): - """Test smart splitting handles punctuation correctly.""" - text = "First sentence! Second sentence? Third sentence; Fourth sentence: Fifth sentence." - - chunks = [] - async for chunk_text, chunk_tokens in smart_split(text): - chunks.append(chunk_text) - - # Verify punctuation is preserved - assert all(any(p in chunk for p in "!?;:.") for chunk in chunks) - -def test_process_text_chunk_chinese_phonemes(): - """Test processing with Chinese pinyin phonemes.""" - pinyin = "nǐ hǎo lì" # Example pinyin sequence with tones - tokens = process_text_chunk(pinyin, skip_phonemize=True, language="z") - assert isinstance(tokens, list) - assert len(tokens) > 0 - - -def test_get_sentence_info_chinese(): - """Test Chinese sentence splitting and info extraction.""" - text = "这是一个句子。这是第二个句子!第三个问题?" - results = get_sentence_info(text, {}, lang_code="z") - - assert len(results) == 3 - for sentence, tokens, count in results: - assert isinstance(sentence, str) - assert isinstance(tokens, list) - assert isinstance(count, int) - assert count == len(tokens) - assert count > 0 - -@pytest.mark.asyncio -async def test_smart_split_chinese_short(): - """Test Chinese smart splitting with short text.""" - text = "这是一句话。" - chunks = [] - async for chunk_text, chunk_tokens in smart_split(text, lang_code="z"): - chunks.append((chunk_text, chunk_tokens)) - - assert len(chunks) == 1 - assert isinstance(chunks[0][0], str) - assert isinstance(chunks[0][1], list) - - -@pytest.mark.asyncio -async def test_smart_split_chinese_long(): - """Test Chinese smart splitting with longer text.""" - text = "。".join([f"测试句子 {i}" for i in range(20)]) - - chunks = [] - async for chunk_text, chunk_tokens in smart_split(text, lang_code="z"): - chunks.append((chunk_text, chunk_tokens)) - - assert len(chunks) > 1 - for chunk_text, chunk_tokens in chunks: - assert isinstance(chunk_text, str) - assert isinstance(chunk_tokens, list) - assert len(chunk_tokens) > 0 - - -@pytest.mark.asyncio -async def test_smart_split_chinese_punctuation(): - """Test Chinese smart splitting with punctuation preservation.""" - text = "第一句!第二问?第三句;第四句:第五句。" - - chunks = [] - async for chunk_text, _ in smart_split(text, lang_code="z"): - chunks.append(chunk_text) - - # Verify Chinese punctuation is preserved - assert all(any(p in chunk for p in "!?;:。") for chunk in chunks) \ No newline at end of file diff --git a/api/tests/test_tts_service.py b/api/tests/test_tts_service.py deleted file mode 100644 index ae8447a..0000000 --- a/api/tests/test_tts_service.py +++ /dev/null @@ -1,126 +0,0 @@ -from unittest.mock import AsyncMock, MagicMock, patch - -import numpy as np -import pytest -import torch - -from api.src.services.tts_service import TTSService - - -@pytest.fixture -def mock_managers(): - """Mock model and voice managers.""" - - async def _mock_managers(): - model_manager = AsyncMock() - model_manager.get_backend.return_value = MagicMock() - - voice_manager = AsyncMock() - voice_manager.get_voice_path.return_value = "/path/to/voice.pt" - voice_manager.list_voices.return_value = ["voice1", "voice2"] - - with ( - patch("api.src.services.tts_service.get_model_manager") as mock_get_model, - patch("api.src.services.tts_service.get_voice_manager") as mock_get_voice, - ): - mock_get_model.return_value = model_manager - mock_get_voice.return_value = voice_manager - return model_manager, voice_manager - - return _mock_managers() - - -@pytest.fixture -def tts_service(mock_managers): - """Create TTSService instance with mocked dependencies.""" - - async def _create_service(): - return await TTSService.create("test_output") - - return _create_service() - - -@pytest.mark.asyncio -async def test_service_creation(): - """Test service creation and initialization.""" - model_manager = AsyncMock() - voice_manager = AsyncMock() - - with ( - patch("api.src.services.tts_service.get_model_manager") as mock_get_model, - patch("api.src.services.tts_service.get_voice_manager") as mock_get_voice, - ): - mock_get_model.return_value = model_manager - mock_get_voice.return_value = voice_manager - - service = await TTSService.create("test_output") - assert service.output_dir == "test_output" - assert service.model_manager is model_manager - assert service._voice_manager is voice_manager - - -@pytest.mark.asyncio -async def test_get_voice_path_single(): - """Test getting path for single voice.""" - model_manager = AsyncMock() - voice_manager = AsyncMock() - voice_manager.get_voice_path.return_value = "/path/to/voice1.pt" - - with ( - patch("api.src.services.tts_service.get_model_manager") as mock_get_model, - patch("api.src.services.tts_service.get_voice_manager") as mock_get_voice, - ): - mock_get_model.return_value = model_manager - mock_get_voice.return_value = voice_manager - - service = await TTSService.create("test_output") - name, path = await service._get_voices_path("voice1") - assert name == "voice1" - assert path == "/path/to/voice1.pt" - voice_manager.get_voice_path.assert_called_once_with("voice1") - - -@pytest.mark.asyncio -async def test_get_voice_path_combined(): - """Test getting path for combined voices.""" - model_manager = AsyncMock() - voice_manager = AsyncMock() - voice_manager.get_voice_path.return_value = "/path/to/voice.pt" - - with ( - patch("api.src.services.tts_service.get_model_manager") as mock_get_model, - patch("api.src.services.tts_service.get_voice_manager") as mock_get_voice, - patch("torch.load") as mock_load, - patch("torch.save") as mock_save, - patch("tempfile.gettempdir") as mock_temp, - ): - mock_get_model.return_value = model_manager - mock_get_voice.return_value = voice_manager - mock_temp.return_value = "/tmp" - mock_load.return_value = torch.ones(10) - - service = await TTSService.create("test_output") - name, path = await service._get_voices_path("voice1+voice2") - assert name == "voice1+voice2" - assert path.endswith("voice1+voice2.pt") - mock_save.assert_called_once() - - -@pytest.mark.asyncio -async def test_list_voices(): - """Test listing available voices.""" - model_manager = AsyncMock() - voice_manager = AsyncMock() - voice_manager.list_voices.return_value = ["voice1", "voice2"] - - with ( - patch("api.src.services.tts_service.get_model_manager") as mock_get_model, - patch("api.src.services.tts_service.get_voice_manager") as mock_get_voice, - ): - mock_get_model.return_value = model_manager - mock_get_voice.return_value = voice_manager - - service = await TTSService.create("test_output") - voices = await service.list_voices() - assert voices == ["voice1", "voice2"] - voice_manager.list_voices.assert_called_once() diff --git a/docker/gpu/Dockerfile b/docker/gpu/Dockerfile index 44c1ba7..1f9d58e 100644 --- a/docker/gpu/Dockerfile +++ b/docker/gpu/Dockerfile @@ -1,67 +1,59 @@ -FROM --platform=$BUILDPLATFORM nvidia/cuda:12.8.0-cudnn-runtime-ubuntu24.04 +FROM --platform=$BUILDPLATFORM nvidia/cuda:12.9.0-base-ubuntu24.04 # Set non-interactive frontend ENV DEBIAN_FRONTEND=noninteractive # Install Python and other dependencies -RUN apt-get update && apt-get install -y \ - python3.10 \ - python3-venv \ - espeak-ng \ - espeak-ng-data \ - git \ - libsndfile1 \ - curl \ - ffmpeg \ - g++ \ - && apt-get clean && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /usr/share/espeak-ng-data \ - && ln -s /usr/lib/*/espeak-ng-data/* /usr/share/espeak-ng-data/ - -# Install UV using the installer script -RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ +RUN apt-get update -y && \ + apt-get install -y python3 python3-venv libsndfile1 curl ffmpeg g++ && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + curl -LsSf https://astral.sh/uv/install.sh | sh && \ mv /root/.local/bin/uv /usr/local/bin/ && \ - mv /root/.local/bin/uvx /usr/local/bin/ - -# Create non-root user and set up directories and permissions -RUN useradd -m -u 1001 appuser && \ + mv /root/.local/bin/uvx /usr/local/bin/ && \ + useradd -m -u 1001 appuser && \ mkdir -p /app/api/src/models/v1_0 && \ chown -R appuser:appuser /app USER appuser WORKDIR /app -# Copy dependency files -COPY --chown=appuser:appuser pyproject.toml ./pyproject.toml - ENV PHONEMIZER_ESPEAK_PATH=/usr/bin \ PHONEMIZER_ESPEAK_DATA=/usr/share/espeak-ng-data \ - ESPEAK_DATA_PATH=/usr/share/espeak-ng-data + PYTHONUNBUFFERED=1 \ + PYTHONPATH=/app:/app/api \ + PATH="/app/.venv/bin:$PATH" \ + UV_LINK_MODE=copy \ + USE_GPU=true \ + DEVICE="gpu" -# Install dependencies with GPU extras (using cache mounts) -RUN --mount=type=cache,target=/root/.cache/uv \ - uv venv --python 3.10 && \ - uv sync --extra gpu - -# Copy project files including models +COPY --chown=appuser:appuser pyproject.toml ./pyproject.toml COPY --chown=appuser:appuser api ./api COPY --chown=appuser:appuser web ./web COPY --chown=appuser:appuser docker/scripts/ ./ RUN chmod +x ./entrypoint.sh + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv venv --python 3.10 && \ + uv sync --extra gpu && \ + uv cache clean && \ + python download_model.py --output api/src/models/v1_0 - -# Set all environment variables in one go -ENV PYTHONUNBUFFERED=1 \ - PYTHONPATH=/app:/app/api \ - PATH="/app/.venv/bin:$PATH" \ - UV_LINK_MODE=copy \ - USE_GPU=true - -ENV DOWNLOAD_MODEL=true -# Download model if enabled -RUN if [ "$DOWNLOAD_MODEL" = "true" ]; then \ - python download_model.py --output api/src/models/v1_0; \ - fi - -ENV DEVICE="gpu" # Run FastAPI server through entrypoint.sh CMD ["./entrypoint.sh"] + + + +# If you want to test the docker image locally, run this from the project root: +# docker build -f docker\gpu\Dockerfile -t kokoro . +# Run it with +# docker run -p 8880:8880 --name kokoro kokoro --gpus all +# +# You can log into the container with +# docker exec -it kokoro /bin/bash +# +# Other commands: +# 1. Stop and remove container +# docker stop kokoro +# docker container remove kokoro +# 2. List and remove images +# docker images +# docker image remove kokoro \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 5d082f7..e983575 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,10 +31,9 @@ dependencies = [ "matplotlib>=3.10.0", "mutagen>=1.47.0", "psutil>=6.1.1", - "espeakng-loader==0.2.4", "kokoro==0.9.2", "misaki[en,ja,ko,zh]==0.9.3", - "spacy==3.8.5", + "spacy==3.8.7", "en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl", "inflect>=7.5.0", "phonemizer-fork>=3.3.2",