AI-video-lifeplan-demo/backend/audio_generator.py

148 lines
4.9 KiB
Python

"""
Audio generation module using OpenAI TTS
This module converts script text to audio using OpenAI's TTS API
"""
import os
from pathlib import Path
from typing import Dict, Any, List, Optional
try:
from openai import OpenAI
except ImportError:
OpenAI = None
from backend.video_processor.utils import get_video_duration
class AudioGenerator:
"""Generate narration audio using OpenAI TTS"""
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
if not self.api_key:
raise ValueError("OPENAI_API_KEY not found in environment")
if OpenAI is None:
raise ImportError(
"openai package not installed. "
"Run: pip install openai"
)
self.client = OpenAI(api_key=self.api_key)
# CRITICAL: Output directly to public/ for Remotion to find them via staticFile
self.output_dir = Path("public")
self.output_dir.mkdir(parents=True, exist_ok=True)
def generate_audio(
self,
script_segments: List[Dict[str, Any]],
voice: str = "alloy",
model: str = "tts-1"
) -> List[Dict[str, Any]]:
"""
Generate audio files for each script segment
Args:
script_segments: List of script segments with text
voice: Voice to use (alloy, echo, fable, onyx, nova, shimmer)
model: TTS model (tts-1 or tts-1-hd)
Returns:
Updated script segments with audioPath field
"""
print(f" Generating audio with OpenAI TTS (voice: {voice})...")
updated_segments = []
for segment in script_segments:
segment_id = segment["id"]
text = segment["text"]
is_video_clip = segment.get("type") == "video_clip" or segment.get("visualType") == "video"
# Skip TTS for video clips (keep original audio)
if is_video_clip:
print(f" ⏭️ Keeping original audio for video segment {segment_id}")
updated_segments.append(segment)
continue
try:
# Generate audio
audio_filename = f"segment_{segment_id:03d}.mp3"
audio_path = self.output_dir / audio_filename
response = self.client.audio.speech.create(
model=model,
voice=voice,
input=text
)
# Save audio file
response.stream_to_file(str(audio_path))
# CRITICAL: Get actual duration of the generated audio file
try:
actual_duration = get_video_duration(str(audio_path))
segment["duration"] = actual_duration
print(f" ✓ Generated {audio_filename} (Actual duration: {actual_duration:.2f}s)")
except Exception as de:
print(f" ⚠️ Could not get actual duration for {audio_filename}: {de}")
# Keep existing duration as fallback
# Update segment with audio path
segment["audioPath"] = str(audio_path)
updated_segments.append(segment)
except Exception as e:
print(f" ⚠️ Failed to generate audio for segment {segment_id}: {e}")
# Keep segment without audio
updated_segments.append(segment)
print(f" ✓ Generated {len(updated_segments)} audio files")
return updated_segments
def estimate_audio_duration(self, text: str, rate: float = 150) -> float:
"""
Estimate audio duration based on text length
Args:
text: Text to estimate
rate: Characters per minute (Japanese: ~150-200)
Returns:
Estimated duration in seconds
"""
char_count = len(text)
duration = (char_count / rate) * 60
return round(duration, 1)
def generate_audio_for_script(
script_data: Dict[str, Any],
voice: str = "alloy"
) -> Dict[str, Any]:
"""
Convenience function to generate audio for a script
Args:
script_data: Script data with segments
voice: Voice to use for TTS
Returns:
Updated script data with audio paths
"""
try:
generator = AudioGenerator()
segments = script_data["segments"]
updated_segments = generator.generate_audio(segments, voice=voice)
return {
**script_data,
"segments": updated_segments,
}
except (ValueError, ImportError) as e:
print(f" ⚠️ Cannot generate audio: {e}")
print(" Skipping audio generation...")
return script_data