""" PowerPoint presentation extractor This module handles: - Extracting slides from PPTX files - Converting slides to images - Extracting text content from slides - Matching slides to script segments """ import os from typing import Dict, Any, List, Optional from pathlib import Path from dotenv import load_dotenv load_dotenv() try: from pptx import Presentation except ImportError: Presentation = None try: from PIL import Image import io except ImportError: Image = None class PPTExtractor: """Extract slides and content from PowerPoint presentations""" def __init__(self): if Presentation is None: raise ImportError("python-pptx not installed. Run: pip install python-pptx") if Image is None: raise ImportError("Pillow not installed. Run: pip install Pillow") def extract(self, ppt_path: str, output_dir: Optional[str] = None) -> Dict[str, Any]: """ Extract slides from PowerPoint presentation Args: ppt_path: Path to PPTX file output_dir: Directory to save slide images (default: assets/1_processed/slides) Returns: Dictionary containing slide information: { "total_slides": int, "slides": [ { "index": 0, "title": str, "text": str, "image_path": str, "notes": str }, ... ] } """ ppt_path = Path(ppt_path) if not ppt_path.exists(): raise FileNotFoundError(f"PPT file not found: {ppt_path}") # Set output directory if output_dir is None: # CRITICAL: Output directly to public/ for Remotion to find them via staticFile output_dir = Path("public") else: output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) print(f" šŸ“Š Loading PowerPoint: {ppt_path.name}") prs = Presentation(str(ppt_path)) slides_data = [] for idx, slide in enumerate(prs.slides): slide_info = self._extract_slide_content(slide, idx) # Export slide as image using LibreOffice/PowerPoint image_path = output_dir / f"slide_{idx + 1:03d}.png" slide_info["image_path"] = str(image_path) slides_data.append(slide_info) print(f" āœ“ Extracted slide {idx + 1}: {slide_info['title'][:50]}...") result = { "total_slides": len(slides_data), "slides": slides_data, "source_file": str(ppt_path) } print(f" āœ“ Extracted {len(slides_data)} slides from PowerPoint") # Export slides as images self._export_slides_as_images(str(ppt_path), output_dir) return result def _extract_slide_content(self, slide, index: int) -> Dict[str, Any]: """Extract text content from a single slide""" slide_info = { "index": index, "title": "", "text": "", "notes": "" } # Extract title (usually the first text box) shapes_text = [] for shape in slide.shapes: if hasattr(shape, "text") and shape.text: text = shape.text.strip() if text: if not slide_info["title"]: slide_info["title"] = text else: shapes_text.append(text) # Combine all text slide_info["text"] = "\n".join(shapes_text) # Extract notes if slide.has_notes_slide: notes_slide = slide.notes_slide if notes_slide.notes_text_frame: slide_info["notes"] = notes_slide.notes_text_frame.text.strip() return slide_info def _export_slides_as_images(self, ppt_path: str, output_dir: Path): """ Export slides as PNG images Tries multiple methods in order: 1. Windows PowerPoint COM (if on Windows) 2. LibreOffice (cross-platform) 3. Placeholder images (fallback) """ import sys import subprocess # Method 1: Try Windows PowerPoint COM if sys.platform == 'win32': try: print(" šŸ–¼ļø Converting slides to images with PowerPoint...") success = self._export_with_powerpoint_com(ppt_path, output_dir) if success: print(" āœ“ Slides exported as images") return except Exception as e: print(f" āš ļø PowerPoint COM failed: {e}") # Method 2: Try LibreOffice try: print(" šŸ–¼ļø Converting slides to images with LibreOffice...") subprocess.run([ "soffice", "--headless", "--convert-to", "png", "--outdir", str(output_dir), ppt_path ], check=True, capture_output=True, timeout=60) print(" āœ“ Slides exported as images") return except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): pass # Method 3: Generate placeholders print(" āš ļø No conversion tool available, generating placeholder images...") self._generate_placeholder_images(output_dir) def _export_with_powerpoint_com(self, ppt_path: str, output_dir: Path) -> bool: """ Export slides using Windows PowerPoint COM interface Returns True if successful, False otherwise """ try: import win32com.client import pythoncom except ImportError: print(" āš ļø pywin32 not installed. Run: pip install pywin32") return False try: pythoncom.CoInitialize() powerpoint = win32com.client.Dispatch("PowerPoint.Application") powerpoint.Visible = 1 # Ensure output directory exists output_dir.mkdir(parents=True, exist_ok=True) # Open presentation (use absolute path) prs = powerpoint.Presentations.Open(str(Path(ppt_path).absolute()), WithWindow=False) # Export each slide as PNG for i, slide in enumerate(prs.Slides): output_file = output_dir / f"slide_{i + 1:03d}.png" # Use absolute path for export slide.Export(str(output_file.absolute()), "PNG", 1920, 1080) # Close presentation prs.Close() powerpoint.Quit() pythoncom.CoUninitialize() return True except Exception as e: print(f" āš ļø PowerPoint COM error: {e}") try: powerpoint.Quit() pythoncom.CoUninitialize() except: pass return False def _generate_placeholder_images(self, output_dir: Path): """Generate placeholder slide images""" from PIL import Image, ImageDraw, ImageFont # Create placeholder images for slides slide_files = list(output_dir.glob("slide_*.png")) if not slide_files: # Generate at least one placeholder img = Image.new('RGB', (1920, 1080), color='white') draw = ImageDraw.Draw(img) # Draw simple text text = "PowerPoint Slide" draw.text((960, 540), text, fill='black', anchor="mm") img.save(output_dir / "slide_001.png") print(" āœ“ Generated placeholder slide image") def extract_ppt_data(ppt_path: str) -> Dict[str, Any]: """Convenience function for PPT extraction""" extractor = PPTExtractor() return extractor.extract(ppt_path) if __name__ == "__main__": # Test extraction import sys if len(sys.argv) < 2: print("Usage: python ppt_extractor.py ") sys.exit(1) ppt_path = sys.argv[1] result = extract_ppt_data(ppt_path) print(f"\nšŸ“Š Extracted {result['total_slides']} slides") for slide in result['slides']: print(f"\nSlide {slide['index'] + 1}:") print(f" Title: {slide['title']}") print(f" Text length: {len(slide['text'])} chars") print(f" Image: {slide['image_path']}")