- Modified TTSRequest to accept optional segments array - Updated generate_audiobook endpoint to use segments if provided - Added generate_audiobook_from_segments method to TTSService - Ensured backward compatibility with text-based generation - Added comprehensive tests for segment-based generation - All tests passing (7/7) Requirements: 15.7
165 lines
5.0 KiB
Python
165 lines
5.0 KiB
Python
"""
|
|
Test for segment-based audiobook generation (Task 19).
|
|
|
|
Tests that the TTS endpoint can accept edited script segments
|
|
and generate audio from them, while maintaining backward compatibility
|
|
with text-based generation.
|
|
"""
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from main import app
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
def test_generate_audiobook_with_segments():
|
|
"""Test that audiobook can be generated from edited script segments."""
|
|
# Prepare request with segments
|
|
request_data = {
|
|
"segments": [
|
|
{"id": "seg_0", "speaker": "Alice", "text": "Hello, how are you?"},
|
|
{"id": "seg_1", "speaker": "Bob", "text": "I'm doing great, thanks!"},
|
|
{"id": "seg_2", "speaker": "Narrator", "text": "They continued their conversation."}
|
|
],
|
|
"lang": "a",
|
|
"roles": [
|
|
{"id": "Alice", "voice": "af_bella"},
|
|
{"id": "Bob", "voice": "am_adam"},
|
|
{"id": "Narrator", "voice": "af_sarah"}
|
|
],
|
|
"speed": 1.0
|
|
}
|
|
|
|
response = client.post("/api/tts", json=request_data)
|
|
|
|
# Should succeed
|
|
assert response.status_code == 200
|
|
assert response.headers["content-type"] == "audio/wav"
|
|
assert "audiobook.wav" in response.headers.get("content-disposition", "")
|
|
assert len(response.content) > 0
|
|
|
|
|
|
def test_generate_audiobook_with_text_backward_compatibility():
|
|
"""Test that audiobook generation still works with text (backward compatibility)."""
|
|
# Prepare request with text (old format)
|
|
request_data = {
|
|
"text": "Alice: Hello there!\nBob: Hi Alice!",
|
|
"lang": "a",
|
|
"roles": [
|
|
{"id": "Alice", "voice": "af_bella"},
|
|
{"id": "Bob", "voice": "am_adam"}
|
|
],
|
|
"speed": 1.0
|
|
}
|
|
|
|
response = client.post("/api/tts", json=request_data)
|
|
|
|
# Should succeed
|
|
assert response.status_code == 200
|
|
assert response.headers["content-type"] == "audio/wav"
|
|
assert len(response.content) > 0
|
|
|
|
|
|
def test_generate_audiobook_segments_take_precedence():
|
|
"""Test that segments are used when both segments and text are provided."""
|
|
# Prepare request with both segments and text
|
|
request_data = {
|
|
"segments": [
|
|
{"id": "seg_0", "speaker": "Alice", "text": "From segments"}
|
|
],
|
|
"text": "Alice: From text",
|
|
"lang": "a",
|
|
"roles": [
|
|
{"id": "Alice", "voice": "af_bella"}
|
|
],
|
|
"speed": 1.0
|
|
}
|
|
|
|
response = client.post("/api/tts", json=request_data)
|
|
|
|
# Should succeed and use segments
|
|
assert response.status_code == 200
|
|
assert response.headers["content-type"] == "audio/wav"
|
|
|
|
|
|
def test_generate_audiobook_missing_both_segments_and_text():
|
|
"""Test that request fails when neither segments nor text is provided."""
|
|
request_data = {
|
|
"lang": "a",
|
|
"roles": [
|
|
{"id": "Alice", "voice": "af_bella"}
|
|
],
|
|
"speed": 1.0
|
|
}
|
|
|
|
response = client.post("/api/tts", json=request_data)
|
|
|
|
# Should fail with validation error
|
|
assert response.status_code == 400
|
|
assert "ValidationError" in response.json()["error"]
|
|
|
|
|
|
def test_generate_audiobook_empty_segments():
|
|
"""Test that request fails when segments array is empty."""
|
|
request_data = {
|
|
"segments": [],
|
|
"lang": "a",
|
|
"roles": [
|
|
{"id": "Alice", "voice": "af_bella"}
|
|
],
|
|
"speed": 1.0
|
|
}
|
|
|
|
response = client.post("/api/tts", json=request_data)
|
|
|
|
# Should fail - empty segments should be treated as not provided
|
|
assert response.status_code == 400
|
|
|
|
|
|
def test_edited_script_preserves_speaker_assignments():
|
|
"""Test that edited speaker assignments in segments are preserved."""
|
|
# Segments where user has changed speaker assignments
|
|
request_data = {
|
|
"segments": [
|
|
{"id": "seg_0", "speaker": "Bob", "text": "This was originally Alice's line"},
|
|
{"id": "seg_1", "speaker": "Alice", "text": "This was originally Bob's line"}
|
|
],
|
|
"lang": "a",
|
|
"roles": [
|
|
{"id": "Alice", "voice": "af_bella"},
|
|
{"id": "Bob", "voice": "am_adam"}
|
|
],
|
|
"speed": 1.0
|
|
}
|
|
|
|
response = client.post("/api/tts", json=request_data)
|
|
|
|
# Should succeed with edited assignments
|
|
assert response.status_code == 200
|
|
assert response.headers["content-type"] == "audio/wav"
|
|
assert len(response.content) > 0
|
|
|
|
|
|
def test_segment_with_unmapped_speaker_uses_default():
|
|
"""Test that segments with unmapped speakers fall back to default voice."""
|
|
request_data = {
|
|
"segments": [
|
|
{"id": "seg_0", "speaker": "UnknownSpeaker", "text": "Hello from unknown"}
|
|
],
|
|
"lang": "a",
|
|
"roles": [
|
|
{"id": "Alice", "voice": "af_bella"}
|
|
],
|
|
"speed": 1.0
|
|
}
|
|
|
|
response = client.post("/api/tts", json=request_data)
|
|
|
|
# Should succeed using default voice
|
|
assert response.status_code == 200
|
|
assert response.headers["content-type"] == "audio/wav"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|