generate-slides.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. """
  2. generate-slides.py - Generate slide images using Gemini API
  3. Usage:
  4. python generate-slides.py <slide-deck-dir> [--model MODEL]
  5. Arguments:
  6. slide-deck-dir Directory containing prompts/ folder with slide prompts
  7. Options:
  8. --model MODEL Gemini model to use (default: gemini-3-pro-image-preview)
  9. Environment:
  10. GOOGLE_API_KEY or GEMINI_API_KEY must be set
  11. Example:
  12. python generate-slides.py ./slide-deck/my-presentation --model gemini-3-pro-image-preview
  13. """
  14. import os
  15. import sys
  16. import time
  17. import argparse
  18. from pathlib import Path
  19. def check_dependencies():
  20. """Check and install required dependencies."""
  21. try:
  22. from google import genai
  23. from google.genai import types
  24. return genai, types
  25. except ImportError:
  26. print("Installing google-genai package...")
  27. import subprocess
  28. subprocess.check_call([sys.executable, "-m", "pip", "install", "google-genai", "-q"])
  29. from google import genai
  30. from google.genai import types
  31. return genai, types
  32. def generate_slide(client, types, model: str, prompt: str, output_path: Path, max_retries: int = 3) -> bool:
  33. """Generate a single slide image with retry logic."""
  34. for attempt in range(max_retries):
  35. try:
  36. if attempt > 0:
  37. wait_time = 5 * (attempt + 1)
  38. print(f" Retry {attempt}/{max_retries-1} (waiting {wait_time}s)...")
  39. time.sleep(wait_time)
  40. else:
  41. print(f" Generating image...")
  42. response = client.models.generate_content(
  43. model=model,
  44. contents=prompt,
  45. config=types.GenerateContentConfig(
  46. image_config=types.ImageConfig(
  47. aspect_ratio="16:9",
  48. image_size="4K"
  49. )
  50. )
  51. )
  52. # Extract image from response
  53. if response.candidates:
  54. for part in response.candidates[0].content.parts:
  55. if hasattr(part, 'inline_data') and part.inline_data and part.inline_data.data:
  56. image_data = part.inline_data.data
  57. # Save image (data is already bytes)
  58. output_path.parent.mkdir(parents=True, exist_ok=True)
  59. with open(output_path, "wb") as f:
  60. f.write(image_data)
  61. size_kb = len(image_data) / 1024
  62. print(f" Saved: {output_path.name} ({size_kb:.1f} KB)")
  63. return True
  64. print(f" Warning: No image in response")
  65. except Exception as e:
  66. print(f" Error: {e}")
  67. if attempt == max_retries - 1:
  68. return False
  69. return False
  70. def find_slides_to_generate(prompts_dir: Path, slides_dir: Path) -> list:
  71. """Find slides that need generation (have prompts but no output or small output)."""
  72. slides = []
  73. for prompt_file in sorted(prompts_dir.glob("*.txt")):
  74. slide_name = prompt_file.stem
  75. output_file = slides_dir / f"{slide_name}.png"
  76. # Skip if output exists and is valid (> 10KB)
  77. if output_file.exists() and output_file.stat().st_size > 10000:
  78. continue
  79. slides.append({
  80. "name": slide_name,
  81. "prompt_file": prompt_file,
  82. "output_file": output_file,
  83. })
  84. return slides
  85. def main():
  86. parser = argparse.ArgumentParser(description="Generate slide images using Gemini API")
  87. parser.add_argument("slide_deck_dir", help="Directory containing prompts/ folder")
  88. parser.add_argument("--model", default="gemini-3-pro-image-preview",
  89. help="Gemini model to use (default: gemini-3-pro-image-preview)")
  90. args = parser.parse_args()
  91. # Check dependencies
  92. genai, types = check_dependencies()
  93. # Initialize paths
  94. deck_dir = Path(args.slide_deck_dir)
  95. prompts_dir = deck_dir / "prompts"
  96. slides_dir = deck_dir / "slides"
  97. if not prompts_dir.exists():
  98. print(f"Error: Prompts directory not found: {prompts_dir}")
  99. sys.exit(1)
  100. # Get API key
  101. api_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
  102. if not api_key:
  103. print("Error: GOOGLE_API_KEY or GEMINI_API_KEY environment variable not set")
  104. sys.exit(1)
  105. # Initialize client
  106. client = genai.Client(api_key=api_key)
  107. # Create output directory
  108. slides_dir.mkdir(parents=True, exist_ok=True)
  109. # Find slides to generate
  110. slides = find_slides_to_generate(prompts_dir, slides_dir)
  111. if not slides:
  112. print("All slides already generated. Nothing to do.")
  113. # List existing slides
  114. existing = sorted(slides_dir.glob("*.png"))
  115. print(f"\nExisting slides ({len(existing)}):")
  116. for slide in existing:
  117. size_kb = slide.stat().st_size / 1024
  118. print(f" - {slide.name} ({size_kb:.1f} KB)")
  119. return
  120. print(f"Generating {len(slides)} slides using {args.model}...")
  121. print(f"Output directory: {slides_dir}\n")
  122. success_count = 0
  123. failed_slides = []
  124. for i, slide in enumerate(slides):
  125. print(f"[{i + 1}/{len(slides)}] {slide['name']}")
  126. # Read prompt
  127. prompt = slide["prompt_file"].read_text(encoding="utf-8")
  128. # Generate slide
  129. if generate_slide(client, types, args.model, prompt, slide["output_file"]):
  130. success_count += 1
  131. else:
  132. failed_slides.append(slide["name"])
  133. print(f"\nDone! Generated {success_count}/{len(slides)} slides.")
  134. if failed_slides:
  135. print(f"\nFailed slides ({len(failed_slides)}):")
  136. for name in failed_slides:
  137. print(f" - {name}")
  138. # List all slides in output directory
  139. all_slides = sorted(slides_dir.glob("*.png"))
  140. print(f"\nTotal slides in output: {len(all_slides)}")
  141. for slide in all_slides:
  142. size_kb = slide.stat().st_size / 1024
  143. print(f" - {slide.name} ({size_kb:.1f} KB)")
  144. if __name__ == "__main__":
  145. main()