200 lines
8.1 KiB
Python
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()
|