""" Video Slicing Module Extract important segments from source video based on transcription analysis. This is an optional feature for including interview clips in the final video. """ import os from pathlib import Path from typing import Dict, Any, List, Optional try: from google import genai from google.genai import types except ImportError: genai = None from backend.video_processor.utils import slice_video, get_video_duration class VideoSlicer: """Extract important segments from source video""" def __init__(self, api_key: Optional[str] = None): self.api_key = api_key or os.getenv("GEMINI_API_KEY") # CRITICAL: Output directly to public/ for Remotion to find them via staticFile self.output_dir = Path("public") self.output_dir.mkdir(parents=True, exist_ok=True) if self.api_key and genai: self.client = genai.Client(api_key=self.api_key) else: self.client = None def extract_key_moments( self, video_path: str, transcription_data: Dict[str, Any], max_clips: int = 3, target_duration: float = 5.0 ) -> List[Dict[str, Any]]: """ Analyze transcription and extract key video moments Args: video_path: Path to source video transcription_data: Transcription with segments and timestamps max_clips: Maximum number of clips to extract target_duration: Target duration for each clip in seconds Returns: List of clip info dictionaries with start, end, filepath, description """ print(f" ✂️ Analyzing video for key moments...") # Get video duration try: duration = get_video_duration(video_path) print(f" ℹ️ Video duration: {duration:.1f}s") except Exception as e: print(f" ⚠️ Could not get video duration: {e}") return [] # Use Gemini to identify key moments from transcription if self.client and "segments" in transcription_data: key_segments = self._identify_key_segments( transcription_data["segments"], max_clips=max_clips ) else: # Fallback: use simple heuristics key_segments = self._simple_segment_selection( transcription_data.get("segments", []), max_clips=max_clips ) # Extract video clips clips = [] for i, segment_info in enumerate(key_segments[:max_clips]): start = segment_info["start"] end = min(segment_info["end"], start + target_duration) # Ensure within video bounds if end > duration: end = duration if start >= duration: continue # Extract clip output_path = self.output_dir / f"clip_{i+1}_{start:.1f}-{end:.1f}.mp4" try: if not output_path.exists(): print(f" ✂️ Extracting clip {i+1}: {start:.1f}s - {end:.1f}s") clip_path = slice_video(video_path, start, end, str(output_path)) else: clip_path = str(output_path) print(f" ✓ Clip {i+1} already cached") clips.append({ "id": f"clip_{i+1}", "outputPath": clip_path, "filepath": clip_path, # Keep for backward compatibility if any "start": start, "end": end, "duration": end - start, "description": segment_info.get("text", "")[:100] }) except Exception as e: print(f" ⚠️ Failed to extract clip {i+1}: {e}") print(f" ✓ Extracted {len(clips)} video clips") return clips def _identify_key_segments( self, segments: List[Dict[str, Any]], max_clips: int ) -> List[Dict[str, Any]]: """Use Gemini to identify important segments""" if not self.client: return self._simple_segment_selection(segments, max_clips) # Prepare transcript for analysis transcript_text = "\n".join([ f"[{seg['start']:.1f}s - {seg['end']:.1f}s] {seg['text']}" for seg in segments ]) prompt = f""" 以下は動画のトランスクリプトです。ライフプランニングの説明動画に最も適した重要なシーンを{max_clips}つ選んでください。 トランスクリプト: {transcript_text} 以下のJSON形式で、重要なシーンの開始時刻、終了時刻、理由を返してください: ```json [ {{ "start": 開始時刻(秒), "end": 終了時刻(秒), "text": "そのシーンのテキスト", "reason": "選んだ理由" }} ] ``` """ try: response = self.client.models.generate_content( model='gemini-2.0-flash-exp', contents=prompt ) response_text = response.text.strip() if "```json" in response_text: json_start = response_text.find("```json") + 7 json_end = response_text.find("```", json_start) response_text = response_text[json_start:json_end].strip() elif "```" in response_text: json_start = response_text.find("```") + 3 json_end = response_text.find("```", json_start) response_text = response_text[json_start:json_end].strip() import json key_segments = json.loads(response_text) print(f" 🤖 Gemini identified {len(key_segments)} key moments") return key_segments except Exception as e: print(f" ⚠️ Gemini analysis failed: {e}, using fallback...") return self._simple_segment_selection(segments, max_clips) def _simple_segment_selection( self, segments: List[Dict[str, Any]], max_clips: int ) -> List[Dict[str, Any]]: """Simple heuristic: select evenly spaced segments""" if not segments: return [] # Select evenly distributed segments step = len(segments) // max_clips if len(segments) >= max_clips else 1 selected = [segments[i * step] for i in range(min(max_clips, len(segments)))] return selected def extract_video_clips( video_path: str, transcription_data: Dict[str, Any], max_clips: int = 3 ) -> List[Dict[str, Any]]: """ Convenience function to extract video clips Args: video_path: Path to source video transcription_data: Transcription with segments max_clips: Maximum number of clips to extract Returns: List of clip information dictionaries """ try: slicer = VideoSlicer() return slicer.extract_key_moments(video_path, transcription_data, max_clips=max_clips) except Exception as e: print(f" ⚠️ Video slicing failed: {e}") return []