146 lines
6.6 KiB
Python
146 lines
6.6 KiB
Python
import logging
|
|
from typing import Dict, Any
|
|
from google.cloud import texttospeech
|
|
|
|
from core.speech_service import TextToSpeechService, BaseAIConversationService, BaseConversationFlowService
|
|
from .models import SCENARIO_PERSONALITIES, GermanPersonality
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class GermanTextToSpeechService(TextToSpeechService):
|
|
def __init__(self):
|
|
super().__init__(language_code="de-DE")
|
|
|
|
def _get_voice_config(self, gender: str, character_name: str = None) -> Dict[str, Any]:
|
|
"""Get German-specific voice configuration."""
|
|
tts_gender = self.gender_map.get(gender, texttospeech.SsmlVoiceGender.FEMALE)
|
|
|
|
# Character-specific German voices using Chirp3-HD models
|
|
character_voice_map = {
|
|
"Mehmet": {
|
|
"name": "de-DE-Chirp3-HD-Charon", # Male voice with slight accent
|
|
"speaking_rate": 0.95,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.MALE,
|
|
},
|
|
"Lisa": {
|
|
"name": "de-DE-Chirp3-HD-Kore", # Young female voice
|
|
"speaking_rate": 1.05,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.FEMALE,
|
|
},
|
|
"Frau Schmidt": {
|
|
"name": "de-DE-Chirp3-HD-Zephyr", # Formal female voice
|
|
"speaking_rate": 0.9,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.FEMALE,
|
|
},
|
|
"Klaus": {
|
|
"name": "de-DE-Chirp3-HD-Puck", # Cheerful male voice
|
|
"speaking_rate": 1.0,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.MALE,
|
|
},
|
|
"Flughafen-Mitarbeiter": {
|
|
"name": "de-DE-Chirp3-HD-Leda", # Professional female voice
|
|
"speaking_rate": 0.95,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.FEMALE,
|
|
},
|
|
"Dr. Müller": {
|
|
"name": "de-DE-Chirp3-HD-Fenrir", # Professional male voice for doctor
|
|
"speaking_rate": 0.9,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.MALE,
|
|
}
|
|
}
|
|
|
|
# Generic German voices by gender using Chirp3-HD models
|
|
gender_voice_fallback = {
|
|
texttospeech.SsmlVoiceGender.MALE: {
|
|
"name": "de-DE-Chirp3-HD-Charon",
|
|
"speaking_rate": 1.0,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.MALE,
|
|
},
|
|
texttospeech.SsmlVoiceGender.FEMALE: {
|
|
"name": "de-DE-Chirp3-HD-Kore",
|
|
"speaking_rate": 1.0,
|
|
"pitch": None,
|
|
"ssml_gender": texttospeech.SsmlVoiceGender.FEMALE,
|
|
}
|
|
}
|
|
|
|
if character_name and character_name in character_voice_map:
|
|
config_set = character_voice_map[character_name]
|
|
logger.info(f"Using character-specific German voice for '{character_name}': {config_set['name']}")
|
|
return config_set
|
|
|
|
config_set = gender_voice_fallback.get(tts_gender, gender_voice_fallback[texttospeech.SsmlVoiceGender.FEMALE])
|
|
logger.info(f"Using German gender fallback voice for {tts_gender}: {config_set['name']}")
|
|
return config_set
|
|
|
|
class GermanAIConversationService(BaseAIConversationService):
|
|
def __init__(self):
|
|
super().__init__(language_code="de")
|
|
|
|
def get_personality_for_scenario(self, scenario: str, character_name: str = None) -> GermanPersonality:
|
|
"""Get German personality based on scenario and character name."""
|
|
if scenario in SCENARIO_PERSONALITIES:
|
|
personalities = SCENARIO_PERSONALITIES[scenario]
|
|
if character_name and character_name in personalities:
|
|
return personalities[character_name]
|
|
else:
|
|
return list(personalities.values())[0]
|
|
|
|
# Return default personality if scenario not found
|
|
from .models import GermanPersonality, CharacterType, Gender, PersonalityTone, GoalItem, HelpfulPhrase
|
|
return GermanPersonality(
|
|
character_type=CharacterType.GENERIC,
|
|
name="Herr/Frau Müller",
|
|
gender=Gender.FEMALE,
|
|
tone=PersonalityTone.FRIENDLY,
|
|
age_range="middle-aged",
|
|
background="Helpful Berlin resident",
|
|
typical_phrases=["Hallo!", "Wie geht's?", "Kann ich helfen?"],
|
|
response_style="Friendly and helpful",
|
|
location_context="Berlin",
|
|
scenario_title="General Conversation",
|
|
scenario_description="General German conversation practice",
|
|
scenario_challenge="Practice basic German conversation",
|
|
scenario_goal="Have a natural conversation in German",
|
|
goal_items=[],
|
|
helpful_phrases=[],
|
|
is_helpful=True,
|
|
is_talkative=True
|
|
)
|
|
|
|
class GermanConversationFlowService(BaseConversationFlowService):
|
|
def __init__(self):
|
|
super().__init__(language_code="de-DE")
|
|
self.tts_service = GermanTextToSpeechService()
|
|
self.ai_service = GermanAIConversationService()
|
|
|
|
def extract_scenario_from_context(self, context: str) -> str:
|
|
"""Extract scenario type from context string."""
|
|
logger.info(f"Extracting German scenario from context: '{context}'")
|
|
context_lower = context.lower()
|
|
|
|
detected_scenario = None
|
|
if "spati" in context_lower or "späti" in context_lower or "convenience" in context_lower:
|
|
detected_scenario = "spati"
|
|
elif "wg" in context_lower or "room" in context_lower or "apartment" in context_lower:
|
|
detected_scenario = "wg_viewing"
|
|
elif "bürgeramt" in context_lower or "burgeramt" in context_lower or "registration" in context_lower:
|
|
detected_scenario = "burgeramt"
|
|
elif "biergarten" in context_lower or "beer" in context_lower or "restaurant" in context_lower:
|
|
detected_scenario = "biergarten"
|
|
elif "ber" in context_lower or "airport" in context_lower or "flughafen" in context_lower or "potsdamer" in context_lower:
|
|
detected_scenario = "ber_airport"
|
|
elif "arzt" in context_lower or "doctor" in context_lower or "medical" in context_lower:
|
|
detected_scenario = "arzt"
|
|
else:
|
|
detected_scenario = "spati" # Default to späti
|
|
|
|
logger.info(f"Detected German scenario: '{detected_scenario}'")
|
|
return detected_scenario |