| """ |
| Defines structured JSON schema for video scripts. |
| """ |
| from typing import List, Dict, Any, Optional |
| from pydantic import BaseModel, Field |
|
|
| class VideoScriptSection(BaseModel): |
| """ |
| Represents a section of a video script. |
| """ |
| section_type: str = Field(..., description="Type of section: hook, key_points, explanation, conclusion, call_to_action") |
| content: str = Field(..., description="Content text of this section") |
| display_title: Optional[str] = Field(None, description="Display title for this section (for UI)") |
|
|
| class VideoScript(BaseModel): |
| """ |
| Structured schema for video scripts. |
| """ |
| title: str = Field(..., description="Title of the video") |
| target_audience: Optional[str] = Field(None, description="Target audience for this video") |
| tone: Optional[str] = Field(None, description="Tone of the video: professional, casual, etc.") |
| sections: List[VideoScriptSection] = Field(..., description="Ordered sections of the script") |
| call_to_action: Optional[str] = Field(None, description="Call to action for the video") |
| key_summary_points: Optional[List[str]] = Field(default_factory=list, description="Key summary points") |
| sources: Optional[List[str]] = Field(default_factory=list, description="Sources used for the script") |
| fact_check_confirmation: Optional[str] = Field(None, description="Confirmation that facts have been checked") |
| |
| def get_full_script_text(self) -> str: |
| """ |
| Generate the full script text suitable for narration. |
| |
| Returns: |
| Combined script text from all sections |
| """ |
| return "\n\n".join(section.content for section in self.sections) |
| |
| def get_formatted_display(self) -> str: |
| """ |
| Generate formatted script for display in chat UI. |
| |
| Returns: |
| Markdown formatted script for display |
| """ |
| result = [f"## π¬ {self.title}"] |
| |
| if self.target_audience: |
| result.append(f"**Target Audience:** {self.target_audience}") |
| |
| if self.tone: |
| result.append(f"**Tone:** {self.tone}") |
| |
| result.append("") |
| |
| |
| for section in self.sections: |
| if section.display_title: |
| emoji = self._get_emoji_for_section(section.section_type) |
| result.append(f"### {emoji} {section.display_title.upper()}") |
| |
| |
| result.append(section.content) |
| result.append("") |
| |
| |
| if self.key_summary_points and len(self.key_summary_points) > 0: |
| result.append("### π KEY SUMMARY POINTS") |
| for point in self.key_summary_points: |
| result.append(f"- {point}") |
| result.append("") |
| |
| |
| if self.sources and len(self.sources) > 0: |
| result.append("### π SOURCES") |
| for source in self.sources: |
| result.append(f"- {source}") |
| |
| return "\n".join(result) |
| |
| def _get_emoji_for_section(self, section_type: str) -> str: |
| """ |
| Get appropriate emoji for section type. |
| |
| Args: |
| section_type: Type of section |
| |
| Returns: |
| Emoji character |
| """ |
| emoji_map = { |
| "hook": "π―", |
| "key_points": "π", |
| "explanation": "π‘", |
| "detail": "π", |
| "context": "π", |
| "conclusion": "π", |
| "call_to_action": "β‘" |
| } |
| return emoji_map.get(section_type, "β¨") |
| |
| def get_legacy_format(self) -> Dict[str, Any]: |
| """ |
| Get script in the legacy format for backward compatibility. |
| |
| Returns: |
| Dictionary in the original format used by the system |
| """ |
| |
| script_text = self.get_full_script_text() |
| |
| |
| cta_text = self.call_to_action or "" |
| for section in self.sections: |
| if section.section_type == "call_to_action": |
| cta_text = section.content |
| break |
| |
| |
| return { |
| "title": self.title, |
| "script": script_text, |
| "callToAction": cta_text, |
| "factCheckConfirmation": self.fact_check_confirmation or "", |
| "keySummaryPoints": self.key_summary_points or [], |
| "sources": self.sources or [] |
| } |
| |
| def convert_legacy_to_script(legacy_data: Dict[str, Any]) -> VideoScript: |
| """ |
| Convert legacy script data to VideoScript format. |
| |
| Args: |
| legacy_data: Legacy script data dictionary |
| |
| Returns: |
| VideoScript object |
| """ |
| sections = [] |
| |
| |
| script_content = legacy_data.get("script", "") |
| |
| |
| hook = "" |
| remaining_script = script_content |
| if "." in script_content: |
| hook_parts = script_content.split(".", 1) |
| if len(hook_parts) > 0: |
| hook = hook_parts[0].strip() + "." |
| remaining_script = hook_parts[1].strip() |
| |
| |
| if hook: |
| sections.append( |
| VideoScriptSection( |
| section_type="hook", |
| content=hook, |
| display_title="Hook" |
| ) |
| ) |
| |
| |
| if remaining_script: |
| sections.append( |
| VideoScriptSection( |
| section_type="explanation", |
| content=remaining_script, |
| display_title="Main Content" |
| ) |
| ) |
| |
| |
| call_to_action = legacy_data.get("callToAction", "") |
| if call_to_action: |
| sections.append( |
| VideoScriptSection( |
| section_type="call_to_action", |
| content=call_to_action, |
| display_title="Call To Action" |
| ) |
| ) |
| |
| |
| return VideoScript( |
| title=legacy_data.get("title", "Video Script"), |
| sections=sections, |
| key_summary_points=legacy_data.get("keySummaryPoints", []), |
| sources=legacy_data.get("sources", []), |
| call_to_action=call_to_action, |
| fact_check_confirmation=legacy_data.get("factCheckConfirmation", "") |
| ) |