rairo commited on
Commit
6862917
·
verified ·
1 Parent(s): aa7e0f2

Update sozo_gen.py

Browse files
Files changed (1) hide show
  1. sozo_gen.py +50 -31
sozo_gen.py CHANGED
@@ -26,6 +26,7 @@ from google import genai
26
  import requests
27
  # In sozo_gen.py, near the other google imports
28
  from google.genai import types as genai_types
 
29
 
30
  # --- Configuration ---
31
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s')
@@ -359,9 +360,10 @@ def prepare_plot_data(spec: ChartSpecification, df: pd.DataFrame):
359
  return df[spec.x_col]
360
 
361
  # UPDATED: animate_chart now uses blit=False for accurate timing
 
362
  def animate_chart(spec: ChartSpecification, df: pd.DataFrame, dur: float, out: Path, fps: int = FPS) -> str:
363
  plot_data = prepare_plot_data(spec, df)
364
- frames = max(10, int(dur * fps))
365
  fig, ax = plt.subplots(figsize=(WIDTH / 100, HEIGHT / 100), dpi=100)
366
  plt.tight_layout(pad=3.0)
367
  ctype = spec.chart_type
@@ -664,6 +666,8 @@ def generate_video_from_project(df: pd.DataFrame, raw_md: str, data_context: Dic
664
  logging.info(f"Generating video for project {project_id} with voice {voice_model}")
665
  llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", google_api_key=API_KEY, temperature=0.2)
666
 
 
 
667
  story_prompt = f"""
668
  Based on the following report, create a script for a {VIDEO_SCENES}-scene video.
669
  1. The first scene MUST be an "Introduction". It must contain narration and a stock video tag like: <generate_stock_video: "search query">.
@@ -673,10 +677,11 @@ def generate_video_from_project(df: pd.DataFrame, raw_md: str, data_context: Dic
673
  Report: {raw_md}
674
  Only output the script, no extra text.
675
  """
676
- script = llm.invoke(story_prompt).content
677
  scenes = [s.strip() for s in script.split("[SCENE_BREAK]") if s.strip()]
678
  video_parts, audio_parts, temps = [], [], []
679
  total_audio_duration = 0.0
 
680
 
681
  for i, sc in enumerate(scenes):
682
  mp4 = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
@@ -701,40 +706,54 @@ def generate_video_from_project(df: pd.DataFrame, raw_md: str, data_context: Dic
701
  video_dur = audio_dur + 1.5
702
 
703
  try:
704
- # --- Primary Visual Generation ---
705
- chart_descs = extract_chart_tags(sc)
706
- pexels_descs = extract_pexels_tags(sc)
707
-
708
- if pexels_descs:
709
- logging.info(f"Scene {i+1}: Primary attempt with Pexels.")
710
- query = extract_keywords_for_query(narrative, llm)
711
- video_path = search_and_download_pexels_video(query, video_dur, mp4)
712
- if not video_path: raise ValueError("Pexels search returned no results.")
 
 
 
 
713
  video_parts.append(video_path)
714
- elif chart_descs:
715
- logging.info(f"Scene {i+1}: Primary attempt with animated chart.")
716
- safe_chart(chart_descs[0], df, video_dur, mp4, data_context)
717
- video_parts.append(str(mp4))
718
  else:
719
- raise ValueError("No visual tag found in scene.")
 
 
 
 
 
 
720
  except Exception as e:
721
- # --- Fallback Visual Generation ---
722
- logging.warning(f"Scene {i+1}: Primary visual failed ({e}). Triggering fallback.")
723
- try:
724
- fallback_query = "abstract technology background"
725
- video_path = search_and_download_pexels_video(fallback_query, video_dur, mp4)
726
- if not video_path: raise ValueError("Fallback Pexels search failed.")
727
- video_parts.append(video_path)
728
- except Exception as fallback_e:
729
- # --- Final Failsafe ---
730
- logging.error(f"Scene {i+1}: Fallback visual also failed ({fallback_e}). Using placeholder.")
731
- placeholder = placeholder_img()
732
- placeholder.save(str(mp4).replace(".mp4", ".png"))
733
- animate_image_fade(cv2.imread(str(mp4).replace(".mp4", ".png")), video_dur, mp4)
734
- video_parts.append(str(mp4))
735
 
736
  temps.append(mp4)
737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_vid, \
739
  tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_aud, \
740
  tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as final_vid:
@@ -743,7 +762,7 @@ def generate_video_from_project(df: pd.DataFrame, raw_md: str, data_context: Dic
743
  audio_mix_path = Path(temp_aud.name)
744
  final_vid_path = Path(final_vid.name)
745
 
746
- concat_media(video_parts, silent_vid_path)
747
  concat_media(audio_parts, audio_mix_path)
748
 
749
  cmd = [
 
26
  import requests
27
  # In sozo_gen.py, near the other google imports
28
  from google.genai import types as genai_types
29
+ import math # Add this import at the top of your sozo_gen.py file
30
 
31
  # --- Configuration ---
32
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s')
 
360
  return df[spec.x_col]
361
 
362
  # UPDATED: animate_chart now uses blit=False for accurate timing
363
+
364
  def animate_chart(spec: ChartSpecification, df: pd.DataFrame, dur: float, out: Path, fps: int = FPS) -> str:
365
  plot_data = prepare_plot_data(spec, df)
366
+ frames = math.ceil(dur * fps) # Use math.ceil to always round up frames
367
  fig, ax = plt.subplots(figsize=(WIDTH / 100, HEIGHT / 100), dpi=100)
368
  plt.tight_layout(pad=3.0)
369
  ctype = spec.chart_type
 
666
  logging.info(f"Generating video for project {project_id} with voice {voice_model}")
667
  llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", google_api_key=API_KEY, temperature=0.2)
668
 
669
+ domain = detect_dataset_domain(df)
670
+
671
  story_prompt = f"""
672
  Based on the following report, create a script for a {VIDEO_SCENES}-scene video.
673
  1. The first scene MUST be an "Introduction". It must contain narration and a stock video tag like: <generate_stock_video: "search query">.
 
677
  Report: {raw_md}
678
  Only output the script, no extra text.
679
  """
680
+ script = llm.invoke(story_prompt).content.strip()
681
  scenes = [s.strip() for s in script.split("[SCENE_BREAK]") if s.strip()]
682
  video_parts, audio_parts, temps = [], [], []
683
  total_audio_duration = 0.0
684
+ conclusion_video_path = None
685
 
686
  for i, sc in enumerate(scenes):
687
  mp4 = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
 
706
  video_dur = audio_dur + 1.5
707
 
708
  try:
709
+ primary_query = None
710
+ narration_lower = narrative.lower()
711
+ is_conclusion_scene = any(k in narration_lower for k in ["conclusion", "summary", "in closing", "final thoughts"])
712
+
713
+ if any(k in narration_lower for k in ["introduction", "welcome", "let's begin"]):
714
+ primary_query = f"abstract technology background {domain}"
715
+ elif is_conclusion_scene:
716
+ primary_query = f"future strategy business meeting {domain}"
717
+
718
+ if primary_query:
719
+ logging.info(f"Scene {i+1}: Pre-emptive guard triggered. Query: '{primary_query}'")
720
+ video_path = search_and_download_pexels_video(primary_query, video_dur, mp4)
721
+ if not video_path: raise ValueError("Pexels search failed for guarded query.")
722
  video_parts.append(video_path)
723
+ if is_conclusion_scene:
724
+ conclusion_video_path = video_path
 
 
725
  else:
726
+ chart_descs = extract_chart_tags(sc)
727
+ if chart_descs:
728
+ logging.info(f"Scene {i+1}: Primary attempt with animated chart.")
729
+ safe_chart(chart_descs[0], df, video_dur, mp4, data_context)
730
+ video_parts.append(str(mp4))
731
+ else:
732
+ raise ValueError("No chart tag found in a middle scene.")
733
  except Exception as e:
734
+ logging.warning(f"Scene {i+1}: Primary visual failed ({e}). Marking for fallback.")
735
+ video_parts.append("FALLBACK_NEEDED")
 
 
 
 
 
 
 
 
 
 
 
 
736
 
737
  temps.append(mp4)
738
 
739
+ # Post-processing loop to apply the conclusion video as a fallback
740
+ if not conclusion_video_path: # Failsafe if conclusion scene itself failed
741
+ logging.warning("No conclusion video was generated; creating a generic one for fallbacks.")
742
+ fallback_mp4 = Path(tempfile.gettempdir()) / f"{uuid.uuid4()}.mp4"
743
+ conclusion_video_path = search_and_download_pexels_video(f"data visualization abstract {domain}", 5.0, fallback_mp4)
744
+ if conclusion_video_path: temps.append(fallback_mp4)
745
+
746
+ final_video_parts = []
747
+ for part in video_parts:
748
+ if part == "FALLBACK_NEEDED":
749
+ if conclusion_video_path:
750
+ logging.info("Applying conclusion video as fallback for a failed scene.")
751
+ final_video_parts.append(conclusion_video_path)
752
+ else:
753
+ logging.error("Cannot apply fallback; no conclusion video available.")
754
+ else:
755
+ final_video_parts.append(part)
756
+
757
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_vid, \
758
  tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_aud, \
759
  tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as final_vid:
 
762
  audio_mix_path = Path(temp_aud.name)
763
  final_vid_path = Path(final_vid.name)
764
 
765
+ concat_media(final_video_parts, silent_vid_path)
766
  concat_media(audio_parts, audio_mix_path)
767
 
768
  cmd = [