#!/usr/bin/env python3 """ Auto Life Plan Video Generator - Main Entry Point This script orchestrates the entire video generation pipeline: 1. Parse PDF/Video inputs (Gemini Pro / Whisper) 2. Generate script (Gemini Pro) 3. Fetch B-Roll assets (Pexels API) 4. Build chart data (Simulation engine) 5. Generate render_props.json 6. Trigger Remotion render Usage: python main.py --pdf path/to/report.pdf python main.py --pdf report.pdf --video interview.mp4 python main.py --config config.json """ import argparse import json import subprocess import sys from pathlib import Path from typing import Dict, Any # Import backend modules (to be implemented) # from backend.extractor import extract_pdf_data, extract_video_data # from backend.scripter import generate_script # from backend.media_fetcher import fetch_broll # from backend.data_builder import build_chart_data class VideoGeneratorPipeline: """Main orchestrator for video generation""" def __init__(self, config: Dict[str, Any]): self.config = config self.project_root = Path(__file__).parent self.assets_dir = self.project_root / "assets" self.input_dir = self.assets_dir / "0_input" self.processed_dir = self.assets_dir / "1_processed" self.output_dir = self.assets_dir / "2_output" # Ensure directories exist for dir_path in [self.input_dir, self.processed_dir, self.output_dir]: dir_path.mkdir(parents=True, exist_ok=True) def run(self) -> str: """Execute the full pipeline and return output video path""" print("šŸš€ Starting Auto Life Plan Video Generation Pipeline...") # Step 1: Extract data from PDF print("\nšŸ“„ Step 1/6: Extracting data from PDF...") pdf_data = self.extract_pdf() # Step 2: Extract data from video (if provided) print("\nšŸŽ„ Step 2/6: Processing video footage...") video_data = self.extract_video() # Step 3: Generate narration script print("\nāœļø Step 3/6: Generating narration script...") script = self.generate_script(pdf_data, video_data) # Step 4: Fetch B-Roll assets print("\nšŸŽ¬ Step 4/6: Fetching B-Roll assets...") broll_assets = self.fetch_broll(script) # Step 5: Build render props print("\nšŸ“Š Step 5/6: Building render props...") render_props = self.build_render_props(pdf_data, script, broll_assets) # Save render_props.json props_file = self.processed_dir / "render_props.json" with open(props_file, "w", encoding="utf-8") as f: json.dump(render_props, f, ensure_ascii=False, indent=2) print(f" āœ“ Saved render props to {props_file}") # Step 6: Render video with Remotion print("\nšŸŽžļø Step 6/6: Rendering video with Remotion...") output_path = self.render_video(props_file) print(f"\nāœ… Video generation complete! Output: {output_path}") return str(output_path) def extract_pdf(self) -> Dict[str, Any]: """Extract data from PDF using Gemini Pro""" # TODO: Implement backend.extractor.extract_pdf_data print(" [MOCK] Extracting PDF data...") return { "client_name": "å±±ē”°å¤ŖéƒŽ", "current_age": 35, "retirement_age": 65, "life_expectancy": 100, "current_assets": 5000, "annual_income": 600, "annual_expenses": 450, } def extract_video(self) -> Dict[str, Any]: """Extract data from video using Whisper (if video provided)""" if not self.config.get("video_path"): print(" [SKIP] No video file provided") return {} # TODO: Implement backend.extractor.extract_video_data print(" [MOCK] Extracting video timestamps and transcription...") return { "transcription": "こんにごは...", "timestamps": [ {"time": 0.5, "text": "Introduction", "keywords": ["äæé™ŗ", "å°†ę„"]}, {"time": 15.2, "text": "Main topic", "keywords": ["資産", "čØˆē”»"]}, ], } def generate_script(self, pdf_data: Dict, video_data: Dict) -> Dict[str, Any]: """Generate narration script using Gemini Pro""" # TODO: Implement backend.scripter.generate_script print(" [MOCK] Generating script with Gemini Pro...") return { "segments": [ { "id": 1, "text": "ć“ć‚Œć‹ć‚‰ć‚ćŖćŸć®å°†ę„čØ­čØˆć«ć¤ć„ć¦čŖ¬ę˜Žć—ć¾ć™ć€‚", "duration": 3.5, "type": "narration", }, { "id": 2, "text": "ē¾åœØć®č³‡ē”£ēŠ¶ę³ć‚’č¦‹ć¦ćæć¾ć—ć‚‡ć†ć€‚", "duration": 2.8, "type": "chart_intro", }, ] } def fetch_broll(self, script: Dict) -> Dict[str, Any]: """Fetch B-Roll footage from Pexels API""" # TODO: Implement backend.media_fetcher.fetch_broll print(" [MOCK] Fetching B-Roll from Pexels...") return { "retirement": "https://example.com/retirement.mp4", "family": "https://example.com/family.mp4", } def build_render_props( self, pdf_data: Dict, script: Dict, broll: Dict ) -> Dict[str, Any]: """Build the complete render_props.json structure""" # TODO: Implement backend.data_builder.build_chart_data print(" [MOCK] Building chart data and render props...") return { "meta": { "title": f"{pdf_data.get('client_name', 'お客様')}ę§˜ć®äŗŗē”ŸčØ­čØˆ", "currencyUnit": "万円", "duration": 180, # 3 minutes }, "charts": { "assetDepletion": { "timeline": [ { "year": 2025, "ages": {"rep": 35, "spouse": 33}, "financials": { "income": 600, "expenditure": 450, "assetBalance": 5000, }, }, # ... more years ], "meta": {"currencyUnit": "万円", "startYear": 2025}, }, "cashFlow": { "timeline": [ { "year": 2025, "age": 35, "income": {"total": 600}, "expenses": { "living": 200, "housing": 150, "education": 50, "tax": 50, "event": 0, }, }, # ... more years ], "meta": {"currencyUnit": "万円"}, }, }, "script": script["segments"], "assets": { "broll": broll, "videoClips": [], }, } def render_video(self, props_file: Path) -> Path: """Render video using Remotion""" output_file = self.output_dir / "life_plan_video.mp4" # Remotion render command cmd = [ "npx", "remotion", "render", "frontend/src/index.ts", "MainVideo", str(output_file), f"--props={props_file}", ] print(f" Running: {' '.join(cmd)}") # TODO: Uncomment when frontend is ready # try: # subprocess.run(cmd, check=True, cwd=self.project_root) # except subprocess.CalledProcessError as e: # print(f" āŒ Remotion render failed: {e}") # sys.exit(1) print(" [MOCK] Remotion render would execute here") return output_file def main(): """CLI entry point""" parser = argparse.ArgumentParser( description="Auto Life Plan Video Generator", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python main.py --pdf assets/0_input/report.pdf python main.py --pdf report.pdf --video interview.mp4 python main.py --config config.json --output custom_output.mp4 """, ) parser.add_argument( "--pdf", type=str, help="Path to the Life Plan PDF report", ) parser.add_argument( "--video", type=str, help="Path to the interview video (optional)", ) parser.add_argument( "--config", type=str, help="Path to configuration JSON file", ) parser.add_argument( "--output", type=str, help="Custom output filename (default: life_plan_video.mp4)", ) parser.add_argument( "--dry-run", action="store_true", help="Generate render_props.json without rendering", ) args = parser.parse_args() # Build configuration config = {} if args.config: with open(args.config, "r", encoding="utf-8") as f: config = json.load(f) else: if not args.pdf: parser.error("--pdf is required when --config is not provided") config["pdf_path"] = args.pdf config["video_path"] = args.video if args.output: config["output_filename"] = args.output config["dry_run"] = args.dry_run # Run pipeline pipeline = VideoGeneratorPipeline(config) try: output_path = pipeline.run() print(f"\nšŸŽ‰ Success! Video saved to: {output_path}") except Exception as e: print(f"\nāŒ Pipeline failed: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()