generate_image.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. #!/usr/bin/env python3
  2. """
  3. Generate and edit images using OpenRouter API with various image generation models.
  4. Supports models like:
  5. - google/gemini-3.1-flash-image-preview (generation and editing)
  6. - black-forest-labs/flux.2-pro (generation and editing)
  7. - black-forest-labs/flux.2-flex (generation)
  8. - And more image generation models available on OpenRouter
  9. For image editing, provide an input image along with an editing prompt.
  10. """
  11. import sys
  12. import json
  13. import base64
  14. import argparse
  15. from pathlib import Path
  16. from typing import Optional
  17. def check_env_file() -> Optional[str]:
  18. """Check if .env file exists and contains OPENROUTER_API_KEY."""
  19. # Look for .env in current directory and parent directories
  20. current_dir = Path.cwd()
  21. for parent in [current_dir] + list(current_dir.parents):
  22. env_file = parent / ".env"
  23. if env_file.exists():
  24. with open(env_file, 'r') as f:
  25. for line in f:
  26. if line.startswith('OPENROUTER_API_KEY='):
  27. api_key = line.split('=', 1)[1].strip().strip('"').strip("'")
  28. if api_key:
  29. return api_key
  30. return None
  31. def load_image_as_base64(image_path: str) -> str:
  32. """Load an image file and return it as a base64 data URL."""
  33. path = Path(image_path)
  34. if not path.exists():
  35. print(f"❌ Error: Image file not found: {image_path}")
  36. sys.exit(1)
  37. # Determine MIME type from extension
  38. ext = path.suffix.lower()
  39. mime_types = {
  40. '.png': 'image/png',
  41. '.jpg': 'image/jpeg',
  42. '.jpeg': 'image/jpeg',
  43. '.gif': 'image/gif',
  44. '.webp': 'image/webp',
  45. }
  46. mime_type = mime_types.get(ext, 'image/png')
  47. with open(path, 'rb') as f:
  48. image_data = f.read()
  49. base64_data = base64.b64encode(image_data).decode('utf-8')
  50. return f"data:{mime_type};base64,{base64_data}"
  51. def save_base64_image(base64_data: str, output_path: str) -> None:
  52. """Save base64 encoded image to file."""
  53. # Remove data URL prefix if present
  54. if ',' in base64_data:
  55. base64_data = base64_data.split(',', 1)[1]
  56. # Decode and save
  57. image_data = base64.b64decode(base64_data)
  58. with open(output_path, 'wb') as f:
  59. f.write(image_data)
  60. def generate_image(
  61. prompt: str,
  62. model: str = "google/gemini-3.1-flash-image-preview",
  63. output_path: str = "generated_image.png",
  64. api_key: Optional[str] = None,
  65. input_image: Optional[str] = None
  66. ) -> dict:
  67. """
  68. Generate or edit an image using OpenRouter API.
  69. Args:
  70. prompt: Text description of the image to generate, or editing instructions
  71. model: OpenRouter model ID (default: google/gemini-3.1-flash-image-preview)
  72. output_path: Path to save the generated image
  73. api_key: OpenRouter API key (will check .env if not provided)
  74. input_image: Path to an input image for editing (optional)
  75. Returns:
  76. dict: Response from OpenRouter API
  77. """
  78. try:
  79. import requests
  80. except ImportError:
  81. print("Error: 'requests' library not found. Install with: pip install requests")
  82. sys.exit(1)
  83. # Check for API key
  84. if not api_key:
  85. api_key = check_env_file()
  86. if not api_key:
  87. print("❌ Error: OPENROUTER_API_KEY not found!")
  88. print("\nPlease create a .env file in your project directory with:")
  89. print("OPENROUTER_API_KEY=your-api-key-here")
  90. print("\nOr set the environment variable:")
  91. print("export OPENROUTER_API_KEY=your-api-key-here")
  92. print("\nGet your API key from: https://openrouter.ai/keys")
  93. sys.exit(1)
  94. # Determine if this is generation or editing
  95. is_editing = input_image is not None
  96. if is_editing:
  97. print(f"✏️ Editing image with model: {model}")
  98. print(f"📷 Input image: {input_image}")
  99. print(f"📝 Edit prompt: {prompt}")
  100. # Load input image as base64
  101. image_data_url = load_image_as_base64(input_image)
  102. # Build multimodal message content for image editing
  103. message_content = [
  104. {
  105. "type": "text",
  106. "text": prompt
  107. },
  108. {
  109. "type": "image_url",
  110. "image_url": {
  111. "url": image_data_url
  112. }
  113. }
  114. ]
  115. else:
  116. print(f"🎨 Generating image with model: {model}")
  117. print(f"📝 Prompt: {prompt}")
  118. message_content = prompt
  119. # Make API request
  120. response = requests.post(
  121. url="https://openrouter.ai/api/v1/chat/completions",
  122. headers={
  123. "Authorization": f"Bearer {api_key}",
  124. "Content-Type": "application/json",
  125. },
  126. json={
  127. "model": model,
  128. "messages": [
  129. {
  130. "role": "user",
  131. "content": message_content
  132. }
  133. ],
  134. "modalities": ["image", "text"]
  135. }
  136. )
  137. # Check for errors
  138. if response.status_code != 200:
  139. print(f"❌ API Error ({response.status_code}): {response.text}")
  140. sys.exit(1)
  141. result = response.json()
  142. # Extract and save image
  143. if result.get("choices"):
  144. message = result["choices"][0]["message"]
  145. # Handle both 'images' and 'content' response formats
  146. images = []
  147. if message.get("images"):
  148. images = message["images"]
  149. elif message.get("content"):
  150. # Some models return content as array with image parts
  151. content = message["content"]
  152. if isinstance(content, list):
  153. for part in content:
  154. if isinstance(part, dict) and part.get("type") == "image":
  155. images.append(part)
  156. if images:
  157. # Save the first image
  158. image = images[0]
  159. if "image_url" in image:
  160. image_url = image["image_url"]["url"]
  161. save_base64_image(image_url, output_path)
  162. print(f"✅ Image saved to: {output_path}")
  163. elif "url" in image:
  164. save_base64_image(image["url"], output_path)
  165. print(f"✅ Image saved to: {output_path}")
  166. else:
  167. print(f"⚠️ Unexpected image format: {image}")
  168. else:
  169. print("⚠️ No image found in response")
  170. if message.get("content"):
  171. print(f"Response content: {message['content']}")
  172. else:
  173. print("❌ No choices in response")
  174. print(f"Response: {json.dumps(result, indent=2)}")
  175. return result
  176. def main():
  177. parser = argparse.ArgumentParser(
  178. description="Generate or edit images using OpenRouter API",
  179. formatter_class=argparse.RawDescriptionHelpFormatter,
  180. epilog="""
  181. Examples:
  182. # Generate with default model (Gemini 3.1 Flash Image Preview)
  183. python generate_image.py "A beautiful sunset over mountains"
  184. # Use a specific model
  185. python generate_image.py "A cat in space" --model "black-forest-labs/flux.2-pro"
  186. # Specify output path
  187. python generate_image.py "Abstract art" --output my_image.png
  188. # Edit an existing image
  189. python generate_image.py "Make the sky purple" --input photo.jpg --output edited.png
  190. # Edit with a specific model
  191. python generate_image.py "Add a hat to the person" --input portrait.png -m "black-forest-labs/flux.2-pro"
  192. Popular image models:
  193. - google/gemini-3.1-flash-image-preview (default, high quality, generation + editing)
  194. - black-forest-labs/flux.2-pro (fast, high quality, generation + editing)
  195. - black-forest-labs/flux.2-flex (development version)
  196. """
  197. )
  198. parser.add_argument(
  199. "prompt",
  200. type=str,
  201. help="Text description of the image to generate, or editing instructions"
  202. )
  203. parser.add_argument(
  204. "--model", "-m",
  205. type=str,
  206. default="google/gemini-3.1-flash-image-preview",
  207. help="OpenRouter model ID (default: google/gemini-3.1-flash-image-preview)"
  208. )
  209. parser.add_argument(
  210. "--output", "-o",
  211. type=str,
  212. default="generated_image.png",
  213. help="Output file path (default: generated_image.png)"
  214. )
  215. parser.add_argument(
  216. "--input", "-i",
  217. type=str,
  218. help="Input image path for editing (enables edit mode)"
  219. )
  220. parser.add_argument(
  221. "--api-key",
  222. type=str,
  223. help="OpenRouter API key (will check .env if not provided)"
  224. )
  225. args = parser.parse_args()
  226. generate_image(
  227. prompt=args.prompt,
  228. model=args.model,
  229. output_path=args.output,
  230. api_key=args.api_key,
  231. input_image=args.input
  232. )
  233. if __name__ == "__main__":
  234. main()