#!/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()