elevenlabs stuff
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
fastapi==0.115.2
|
fastapi==0.115.2
|
||||||
uvicorn==0.31.1
|
uvicorn==0.31.1
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
|
elevenlabs==2.18.0
|
||||||
|
python-dotenv==1.1.1
|
||||||
|
fastf1==3.6.1
|
||||||
|
pandas==2.3.3
|
||||||
|
requests==2.32.5
|
||||||
BIN
scripts/data/audio/pit_call.mp3
Normal file
BIN
scripts/data/audio/pit_call.mp3
Normal file
Binary file not shown.
2
scripts/fetch_race_data.py
Normal file → Executable file
2
scripts/fetch_race_data.py
Normal file → Executable file
@@ -175,7 +175,7 @@ def prepare_telemetry_stream(telemetry: pd.DataFrame, sample_rate_hz: float = 10
|
|||||||
# Resample to target rate if needed
|
# Resample to target rate if needed
|
||||||
telemetry = telemetry.copy()
|
telemetry = telemetry.copy()
|
||||||
telemetry['Time'] = pd.to_timedelta(telemetry['Time'])
|
telemetry['Time'] = pd.to_timedelta(telemetry['Time'])
|
||||||
telemetry = telemetry.sort_values('Time')
|
telemetry = telemetry.sort_values(['LapNumber', 'Time'])
|
||||||
|
|
||||||
# Convert to milliseconds for easier time tracking
|
# Convert to milliseconds for easier time tracking
|
||||||
telemetry['TimeMs'] = (telemetry['Time'].dt.total_seconds() * 1000).astype(int)
|
telemetry['TimeMs'] = (telemetry['Time'].dt.total_seconds() * 1000).astype(int)
|
||||||
|
|||||||
31
scripts/strategy_voice_integration.py
Normal file
31
scripts/strategy_voice_integration.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"""
|
||||||
|
Example: Integrate voice feedback with AI strategy decisions
|
||||||
|
"""
|
||||||
|
|
||||||
|
from voice_service import RaceEngineerVoice
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def announce_strategy_decision(decision: dict):
|
||||||
|
"""
|
||||||
|
Convert AI strategy decision to voice announcement.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
decision: Dict with keys like 'action', 'tire_compound', 'lap'
|
||||||
|
"""
|
||||||
|
engineer = RaceEngineerVoice()
|
||||||
|
|
||||||
|
# Generate appropriate message
|
||||||
|
if decision['action'] == 'pit':
|
||||||
|
text = f"Box this lap for {decision['tire_compound']}. In, in, in!"
|
||||||
|
elif decision['action'] == 'stay_out':
|
||||||
|
text = "Stay out. These tires are still competitive"
|
||||||
|
elif decision['action'] == 'push':
|
||||||
|
text = f"Push mode. We need {decision.get('gap_target', 3)} seconds"
|
||||||
|
else:
|
||||||
|
text = decision.get('message', 'Copy that')
|
||||||
|
|
||||||
|
# Synthesize and save
|
||||||
|
audio_path = Path(f"data/audio/lap_{decision.get('lap', 0)}_command.mp3")
|
||||||
|
engineer.synthesize_strategy_message(text, audio_path)
|
||||||
|
|
||||||
|
return audio_path
|
||||||
90
scripts/voice_service.py
Normal file
90
scripts/voice_service.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"""
|
||||||
|
ElevenLabs Voice Service for F1 Race Engineer
|
||||||
|
|
||||||
|
Converts AI strategy recommendations to natural speech.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from elevenlabs.client import ElevenLabs
|
||||||
|
from elevenlabs import save
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
class RaceEngineerVoice:
|
||||||
|
def __init__(self, voice_id: str = "mbBupyLcEivjpxh8Brkf"): # Default: Rachel
|
||||||
|
"""
|
||||||
|
Initialize ElevenLabs voice service.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
voice_id: ElevenLabs voice ID (Rachel is default, professional female voice)
|
||||||
|
"""
|
||||||
|
self.client = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
|
||||||
|
self.voice_id = voice_id
|
||||||
|
|
||||||
|
def synthesize_strategy_message(
|
||||||
|
self,
|
||||||
|
text: str,
|
||||||
|
output_path: Path,
|
||||||
|
stability: float = 0.4,
|
||||||
|
similarity_boost: float = 0.95
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
Convert strategy text to speech.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Message to synthesize (e.g., "Box this lap, box this lap")
|
||||||
|
output_path: Where to save audio file
|
||||||
|
stability: Voice stability (0-1)
|
||||||
|
similarity_boost: Voice similarity (0-1)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to generated audio file
|
||||||
|
"""
|
||||||
|
audio = self.client.text_to_speech.convert(
|
||||||
|
voice_id=self.voice_id,
|
||||||
|
text=text,
|
||||||
|
model_id="eleven_multilingual_v2", # Fast, low-latency model
|
||||||
|
voice_settings={
|
||||||
|
"stability": stability,
|
||||||
|
"similarity_boost": similarity_boost,
|
||||||
|
"style": 0.7,
|
||||||
|
"use_speaker_boost": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save audio
|
||||||
|
output_path = Path(output_path)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Write the audio generator to file
|
||||||
|
save(audio, str(output_path))
|
||||||
|
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
def race_engineer_commands(self) -> dict:
|
||||||
|
"""Common F1 race engineer commands"""
|
||||||
|
return {
|
||||||
|
"box_now": "Box this lap, box this lap",
|
||||||
|
"stay_out": "Stay out, stay out. We're looking good on these tires",
|
||||||
|
"push": "Push now, push. We need to build a gap",
|
||||||
|
"save_tires": "Manage those tires. Lift and coast into the corners",
|
||||||
|
"traffic_ahead": "Traffic ahead. Blue flags expected",
|
||||||
|
"safety_car": "Safety car, safety car. We're checking strategy",
|
||||||
|
"undercut_threat": "Undercut threat from behind. We may need to respond",
|
||||||
|
"fastest_lap": "You're on for fastest lap. Push this lap",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
engineer = RaceEngineerVoice()
|
||||||
|
|
||||||
|
# Generate pit call
|
||||||
|
audio_file = engineer.synthesize_strategy_message(
|
||||||
|
text="Mama Mia! That was a close one! Let's keep going and finish this out strong, like a cheetah on espresso!",
|
||||||
|
output_path=Path("data/audio/pit_call.mp3")
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"✓ Generated: {audio_file}")
|
||||||
Reference in New Issue
Block a user