223 lines
7.7 KiB
Python
223 lines
7.7 KiB
Python
"""
|
||
Script generation module using Google Gemini Pro
|
||
|
||
This module generates natural Japanese narration scripts based on:
|
||
- Extracted PDF data
|
||
- Video transcription
|
||
- Template constraints
|
||
"""
|
||
|
||
import os
|
||
import json
|
||
from typing import Dict, Any, List, Optional
|
||
|
||
try:
|
||
import google.generativeai as genai
|
||
except ImportError:
|
||
genai = None
|
||
|
||
|
||
class ScriptGenerator:
|
||
"""Generate narration scripts using Gemini Pro"""
|
||
|
||
def __init__(self, api_key: Optional[str] = None):
|
||
self.api_key = api_key or os.getenv("GEMINI_API_KEY")
|
||
if not self.api_key:
|
||
raise ValueError("GEMINI_API_KEY not found in environment")
|
||
|
||
if genai is None:
|
||
raise ImportError(
|
||
"google-generativeai not installed. "
|
||
"Run: pip install google-generativeai"
|
||
)
|
||
|
||
genai.configure(api_key=self.api_key)
|
||
self.model = genai.GenerativeModel('gemini-1.5-pro')
|
||
|
||
def generate(
|
||
self,
|
||
pdf_data: Dict[str, Any],
|
||
video_data: Optional[Dict[str, Any]] = None,
|
||
style: str = "friendly"
|
||
) -> Dict[str, Any]:
|
||
"""Generate narration script
|
||
|
||
Args:
|
||
pdf_data: Structured data from PDF extraction
|
||
video_data: Optional video transcription data
|
||
style: Narration style ('friendly', 'professional', 'casual')
|
||
|
||
Returns:
|
||
Dictionary containing:
|
||
- segments: List of narration segments
|
||
- total_duration: Estimated total duration in seconds
|
||
- sync_points: Recommended chart/video sync points
|
||
"""
|
||
print(" Generating script with Gemini Pro...")
|
||
|
||
# Build the prompt
|
||
prompt = self._build_prompt(pdf_data, style)
|
||
|
||
try:
|
||
# Call Gemini API
|
||
response = self.model.generate_content(prompt)
|
||
|
||
# Parse the response
|
||
script_data = self._parse_response(response.text)
|
||
|
||
print(f" ✓ Generated {len(script_data['segments'])} script segments")
|
||
return script_data
|
||
|
||
except Exception as e:
|
||
print(f" ⚠️ Gemini API error: {e}")
|
||
print(" Falling back to template-based script...")
|
||
return self._generate_fallback_script(pdf_data)
|
||
|
||
def _build_prompt(self, pdf_data: Dict, style: str) -> str:
|
||
"""Build the prompt for Gemini Pro"""
|
||
client_name = pdf_data.get("client_name", "お客様")
|
||
current_age = pdf_data.get("current_age", 35)
|
||
retirement_age = pdf_data.get("retirement_age", 65)
|
||
current_assets = pdf_data.get("current_assets", 5000)
|
||
annual_income = pdf_data.get("annual_income", 600)
|
||
annual_expenses = pdf_data.get("annual_expenses", 450)
|
||
|
||
prompt = f"""
|
||
あなたはファイナンシャルプランナーのナレーション原稿作成アシスタントです。
|
||
以下の顧客情報に基づいて、3〜5分の解説動画用のナレーション原稿を作成してください。
|
||
|
||
【顧客情報】
|
||
- お名前: {client_name}様
|
||
- 現在年齢: {current_age}歳
|
||
- 退職予定年齢: {retirement_age}歳
|
||
- 現在の資産: {current_assets}万円
|
||
- 年収: {annual_income}万円
|
||
- 年間支出: {annual_expenses}万円
|
||
|
||
【原稿作成の指示】
|
||
1. 合計3〜5分(180〜300秒)の原稿を作成してください
|
||
2. 以下のセグメントに分割してください:
|
||
- オープニング(挨拶と概要説明)
|
||
- 現在の状況説明
|
||
- 将来の資産推移の解説
|
||
- 退職後の生活資金について
|
||
- リスクと対策の提案
|
||
- クロージング(まとめと安心感)
|
||
|
||
3. 各セグメントには以下を含めてください:
|
||
- id: セグメント番号(1から開始)
|
||
- text: ナレーション文(自然な日本語、敬語)
|
||
- duration: 推定時間(秒)
|
||
- type: シーンタイプ("narration", "chart_intro", "chart_highlight", "transition")
|
||
- chartSync: チャート表示の指示(該当する場合のみ)
|
||
|
||
4. トーンは{style}(親しみやすく、でも専門的)にしてください
|
||
|
||
5. 出力はJSON形式で、以下の構造で返してください:
|
||
{{
|
||
"segments": [
|
||
{{
|
||
"id": 1,
|
||
"text": "ナレーション文",
|
||
"duration": 3.5,
|
||
"type": "narration",
|
||
"chartSync": {{
|
||
"chartId": "assetDepletion",
|
||
"highlightYear": 2025
|
||
}}
|
||
}}
|
||
]
|
||
}}
|
||
|
||
必ずJSON形式のみを返してください。説明文は不要です。
|
||
"""
|
||
return prompt
|
||
|
||
def _parse_response(self, response_text: str) -> Dict[str, Any]:
|
||
"""Parse Gemini's JSON response"""
|
||
# Remove markdown code blocks if present
|
||
text = response_text.strip()
|
||
if text.startswith("```json"):
|
||
text = text[7:]
|
||
if text.startswith("```"):
|
||
text = text[3:]
|
||
if text.endswith("```"):
|
||
text = text[:-3]
|
||
text = text.strip()
|
||
|
||
# Parse JSON
|
||
data = json.loads(text)
|
||
|
||
# Calculate total duration
|
||
total_duration = sum(seg.get("duration", 0) for seg in data["segments"])
|
||
|
||
return {
|
||
"segments": data["segments"],
|
||
"total_duration": total_duration,
|
||
}
|
||
|
||
def _generate_fallback_script(self, pdf_data: Dict) -> Dict[str, Any]:
|
||
"""Generate a template-based script as fallback"""
|
||
client_name = pdf_data.get("client_name", "お客様")
|
||
current_age = pdf_data.get("current_age", 35)
|
||
retirement_age = pdf_data.get("retirement_age", 65)
|
||
|
||
segments = [
|
||
{
|
||
"id": 1,
|
||
"text": f"{client_name}様、こんにちは。これから、あなたの人生設計について、詳しくご説明させていただきます。",
|
||
"duration": 5.0,
|
||
"type": "narration",
|
||
},
|
||
{
|
||
"id": 2,
|
||
"text": f"現在{current_age}歳の{client_name}様の資産状況を見てみましょう。",
|
||
"duration": 3.5,
|
||
"type": "chart_intro",
|
||
"chartSync": {
|
||
"chartId": "cashFlow",
|
||
},
|
||
},
|
||
{
|
||
"id": 3,
|
||
"text": f"{retirement_age}歳での退職を想定した場合、将来の資産推移はこのようになります。",
|
||
"duration": 4.0,
|
||
"type": "chart_highlight",
|
||
"chartSync": {
|
||
"chartId": "assetDepletion",
|
||
},
|
||
},
|
||
{
|
||
"id": 4,
|
||
"text": "退職後の生活資金については、計画的な準備が大切です。",
|
||
"duration": 3.5,
|
||
"type": "narration",
|
||
},
|
||
{
|
||
"id": 5,
|
||
"text": "ご不明な点がございましたら、いつでもご相談ください。ありがとうございました。",
|
||
"duration": 4.0,
|
||
"type": "narration",
|
||
},
|
||
]
|
||
|
||
return {
|
||
"segments": segments,
|
||
"total_duration": sum(s["duration"] for s in segments),
|
||
}
|
||
|
||
|
||
def generate_script(
|
||
pdf_data: Dict[str, Any],
|
||
video_data: Optional[Dict[str, Any]] = None
|
||
) -> Dict[str, Any]:
|
||
"""Convenience function for script generation"""
|
||
try:
|
||
generator = ScriptGenerator()
|
||
return generator.generate(pdf_data, video_data)
|
||
except (ValueError, ImportError) as e:
|
||
print(f" ⚠️ Cannot initialize ScriptGenerator: {e}")
|
||
print(" Using fallback template script...")
|
||
generator_fallback = ScriptGenerator.__new__(ScriptGenerator)
|
||
return generator_fallback._generate_fallback_script(pdf_data)
|