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

200 lines
8.1 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Generate video for PPT slides 2 & 3 using existing data (no API calls needed)
Only display PPT slides, narration includes PDF info
"""
import os
import json
import sys
from pathlib import Path
from typing import Dict, List
class SimplePPTVideoGenerator:
def __init__(self):
self.project_root = Path(__file__).parent.parent
def create_script_segments(self) -> List[Dict]:
"""
Create narration script for slides 2 and 3
Based on existing PDF/PPT data
Using existing audio files from family_intro_audio
"""
# Use existing audio files
segments = [
{
"id": "slide2_intro",
"text": "それでは、テスト太郎様のご家族についてご紹介いたします。こちらのスライドをご覧ください。",
"duration": 8.976,
"visualType": "ppt",
"visualAssetIndex": 1, # slide_002.png
"highlightArea": "full",
"audioPath": "public/family_intro_audio/narration_intro01.mp3"
},
{
"id": "slide2_family_overview",
"text": "今回ご紹介するのは、テスト太郎さん、花子さん、そして7歳になる息子さんの3人家族です。太郎さんは情報通信業にお勤めで、年収は703万円。花子さんは卸売・小売業で、年収は368万円です。素敵なご家族ですね。",
"duration": 19.248,
"visualType": "ppt",
"visualAssetIndex": 1,
"highlightArea": "full",
"audioPath": "public/family_intro_audio/narration_family01.mp3"
},
{
"id": "slide2_child_info",
"text": "それでは、ご家族の詳細を見ていきましょう。まず、お父様のテスト太郎さんは、1980年1月1日生まれの47歳。情報通信業でご活躍されています。",
"duration": 30.264,
"visualType": "ppt",
"visualAssetIndex": 1,
"highlightArea": "full",
"audioPath": "public/family_intro_audio/narration_details01.mp3"
},
{
"id": "slide3_transition",
"text": "テストさんご一家の世帯年収は1071万円。将来の夢に向かって、素晴らしいライフプランを描いていきましょう。",
"duration": 9.528,
"visualType": "ppt",
"visualAssetIndex": 2, # slide_003.png
"highlightArea": "full",
"audioPath": "public/family_intro_audio/narration_summary01.mp3"
}
]
print(f"✓ Created {len(segments)} script segments (using existing audio)")
print("\n📝 Script Preview:")
for i, seg in enumerate(segments, 1):
print(f"\n{i}. [{seg['id']}] - Slide {seg['visualAssetIndex'] + 1}")
print(f" Duration: {seg['duration']}s")
print(f" Audio: {seg['audioPath']}")
print(f" Text: {seg['text'][:60]}...")
return segments
def generate_audio(self, segments: List[Dict]) -> List[Dict]:
"""Generate TTS audio for each segment"""
sys.path.insert(0, str(self.project_root))
print("\n🎙️ Checking audio generation capability...")
# Check if API key is available
if not os.getenv("OPENAI_API_KEY"):
print(" ⚠️ OPENAI_API_KEY not found - skipping audio generation")
print(" 📝 Using preset durations for video segments")
return segments
try:
from backend.audio_generator import AudioGenerator
from backend.video_processor.utils import get_video_duration
except ImportError as e:
print(f" ⚠️ Import error: {e}")
print(" 📝 Using preset durations without audio generation")
return segments
print("🎙️ Generating audio...")
try:
audio_gen = AudioGenerator()
except ValueError as e:
print(f" ⚠️ {e}")
print(" 📝 Using preset durations for video segments")
return segments
output_dir = self.project_root / "public" / "ppt_slides_audio"
output_dir.mkdir(parents=True, exist_ok=True)
for i, seg in enumerate(segments):
text = seg["text"]
audio_filename = f"narration_{seg['id']}.mp3"
audio_path = output_dir / audio_filename
print(f" [{i+1}/{len(segments)}] {seg['id']}: Generating audio...")
try:
response = audio_gen.client.audio.speech.create(
model="tts-1",
voice="nova",
input=text
)
response.stream_to_file(str(audio_path))
actual_duration = get_video_duration(str(audio_path))
seg["duration"] = actual_duration
seg["audioPath"] = f"public/ppt_slides_audio/{audio_filename}"
print(f" ✓ Duration: {actual_duration:.2f}s")
except Exception as e:
print(f" ⚠️ Audio generation failed: {e}")
print(f" Using preset duration: {seg['duration']}s")
continue
total_duration = sum(s.get("duration", 0) for s in segments)
print(f"\n✓ Audio processing complete (Total: {total_duration:.1f}s)")
return segments
def create_render_props(self, segments: List[Dict]) -> Dict:
"""Create render_props with only PPT slides"""
# Load existing render_props to get charts and assets
props_path = self.project_root / "assets/1_processed/render_props.json"
with open(props_path, encoding="utf-8") as f:
existing_props = json.load(f)
new_props = {
"meta": {
"title": "テスト太郎様 ライフプラン紹介",
"currencyUnit": "万円",
"duration": sum(s.get("duration", 0) for s in segments),
"clientName": "テスト太郎"
},
"charts": existing_props.get("charts", {}),
"script": segments,
"assets": {
"slides": existing_props["assets"]["slides"],
"videoClips": [],
"broll": existing_props["assets"].get("broll", {})
}
}
output_path = self.project_root / "assets/1_processed/render_props_ppt_slides.json"
with open(output_path, "w", encoding="utf-8") as f:
json.dump(new_props, f, ensure_ascii=False, indent=2)
print(f"\n✓ Saved render props to {output_path.name}")
return new_props
def run(self) -> str:
"""Execute full pipeline"""
print("🚀 Generating video for PPT slides 2 & 3...\n")
# Create script segments
segments = self.create_script_segments()
print()
# Generate audio
segments = self.generate_audio(segments)
print()
# Create render props
props = self.create_render_props(segments)
print()
output_file = "assets/2_output/ppt_slides_video.mp4"
print(f"✅ Pipeline complete!")
print(f"\n📹 Render command:")
print(f" npx remotion render MainVideo {output_file} --props=\"assets/1_processed/render_props_ppt_slides.json\" --concurrency=1")
print()
print(f"📊 Video Summary:")
print(f" - Target slides: slide_002.png, slide_003.png")
print(f" - Total segments: {len(segments)}")
print(f" - Total duration: {props['meta']['duration']:.1f}s")
print(f" - Client: {props['meta']['clientName']}")
return output_file
if __name__ == "__main__":
generator = SimplePPTVideoGenerator()
generator.run()