返回 ppt-master
backend_stability.py
根目录 / skills / ppt-master / scripts / image_backends / backend_stability.py
1 #!/usr/bin/env python3
2 """
3 Stability AI image generation backend.
4
5 Configuration keys:
6 STABILITY_API_KEY (required)
7 STABILITY_BASE_URL (optional)
8 STABILITY_MODEL (optional)
9 """
10
11 import sys
12 from pathlib import Path
13
14 _SCRIPTS_DIR = Path(__file__).resolve().parents[1]
15 if str(_SCRIPTS_DIR) not in sys.path:
16 sys.path.insert(0, str(_SCRIPTS_DIR))
17
18 from console_encoding import configure_utf8_stdio # noqa: E402
19
20 configure_utf8_stdio()
21
22 if __name__ == "__main__":
23 print(__doc__)
24 print("Use via: python3 skills/ppt-master/scripts/image_gen.py \"prompt\" --backend stability")
25 raise SystemExit(0 if any(arg in {"-h", "--help", "help"} for arg in sys.argv[1:]) else 1)
26
27 import os
28 import time
29
30 import requests
31
32 from image_backends.backend_common import (
33 MAX_RETRIES,
34 http_error,
35 is_permanent_error,
36 is_rate_limit_error,
37 normalize_image_size,
38 report_resolution,
39 require_api_key,
40 resolve_output_path,
41 retry_delay,
42 )
43
44
45 VALID_ASPECT_RATIOS = [
46 "1:1", "2:3", "3:2", "4:5", "5:4",
47 "9:16", "16:9", "9:21", "21:9",
48 ]
49
50 DEFAULT_BASE_URL = "https://api.stability.ai"
51 DEFAULT_MODEL = "stable-image-core"
52
53 MODEL_ENDPOINTS = {
54 "core": "/v2beta/stable-image/generate/core",
55 "stable-image-core": "/v2beta/stable-image/generate/core",
56 "ultra": "/v2beta/stable-image/generate/ultra",
57 "stable-image-ultra": "/v2beta/stable-image/generate/ultra",
58 }
59
60
61 def _validate_request_options(aspect_ratio: str, image_size: str) -> None:
62 """Validate unified request options before any retryable work."""
63 if aspect_ratio not in VALID_ASPECT_RATIOS:
64 raise ValueError(
65 f"Unsupported aspect ratio '{aspect_ratio}' for Stability backend. "
66 f"Supported: {VALID_ASPECT_RATIOS}"
67 )
68 normalized_size = normalize_image_size(image_size)
69 if normalized_size != "1K":
70 raise ValueError(
71 "Stability Core and Ultra generation only support the default '1K' preset; "
72 f"got '{image_size}'. Use a Stability upscale endpoint for larger output."
73 )
74
75
76 def _resolve_endpoint(model: str | None, base_url: str) -> tuple[str, str]:
77 """Resolve the Stability model alias and endpoint URL."""
78 resolved_model = model or DEFAULT_MODEL
79
80 normalized_model = resolved_model.lower()
81 endpoint = MODEL_ENDPOINTS.get(normalized_model)
82 if not endpoint:
83 supported = sorted(MODEL_ENDPOINTS)
84 raise ValueError(
85 f"Unsupported Stability model '{resolved_model}'. Supported aliases: {supported}"
86 )
87 return normalized_model, base_url.rstrip("/") + endpoint
88
89
90 def _generate_image(api_key: str, prompt: str,
91 aspect_ratio: str = "1:1", image_size: str = "1K",
92 output_dir: str = None, filename: str = None,
93 model: str | None = None, base_url: str = DEFAULT_BASE_URL) -> str:
94 """Generate one image with the Stability backend."""
95 _validate_request_options(aspect_ratio, image_size)
96
97 resolved_model, url = _resolve_endpoint(model, base_url)
98 headers = {
99 "Authorization": f"Bearer {api_key}",
100 "Accept": "image/*",
101 }
102 data = {
103 "prompt": prompt,
104 "aspect_ratio": aspect_ratio,
105 "output_format": "png",
106 }
107
108 print("[Stability AI]")
109 print(f" Model: {resolved_model}")
110 print(f" Prompt: {prompt[:120]}{'...' if len(prompt) > 120 else ''}")
111 print(f" Aspect Ratio: {aspect_ratio}")
112 print(f" Preset Size: {image_size}")
113 print()
114 print(" [..] Generating...", end="", flush=True)
115 start = time.time()
116
117 response = requests.post(
118 url,
119 headers=headers,
120 data=data,
121 files={"none": ""},
122 timeout=300,
123 )
124 elapsed = time.time() - start
125 print(f"\n [DONE] Response received ({elapsed:.1f}s)")
126
127 if response.status_code != 200:
128 raise http_error(response, "Stability generation")
129
130 path = resolve_output_path(prompt, output_dir, filename, ".png")
131 with open(path, "wb") as f:
132 f.write(response.content)
133 print(f" File saved to: {path}")
134 report_resolution(path)
135 return path
136
137
138 def generate(prompt: str,
139 aspect_ratio: str = "1:1", image_size: str = "1K",
140 output_dir: str = None, filename: str = None,
141 model: str = None, max_retries: int = MAX_RETRIES) -> str:
142 """Generate an image with retries using the Stability backend."""
143 base_url = os.environ.get("STABILITY_BASE_URL") or DEFAULT_BASE_URL
144 resolved_model = model or os.environ.get("STABILITY_MODEL")
145 _validate_request_options(aspect_ratio, image_size)
146 _resolve_endpoint(resolved_model, base_url)
147 api_key = require_api_key(
148 "STABILITY_API_KEY",
149 message="No API key found. Set STABILITY_API_KEY in the current environment or a .env file.",
150 )
151
152 last_error = None
153 for attempt in range(max_retries + 1):
154 try:
155 return _generate_image(
156 api_key=api_key,
157 prompt=prompt,
158 aspect_ratio=aspect_ratio,
159 image_size=image_size,
160 output_dir=output_dir,
161 filename=filename,
162 model=resolved_model,
163 base_url=base_url,
164 )
165 except Exception as exc:
166 last_error = exc
167 if is_permanent_error(exc):
168 raise
169 if attempt >= max_retries:
170 break
171 limited = is_rate_limit_error(exc)
172 delay = retry_delay(attempt, rate_limited=limited)
173 label = "Rate limit hit" if limited else f"Error: {exc}"
174 print(f"\n [WARN] {label}. Retrying in {delay}s...")
175 time.sleep(delay)
176
177 raise RuntimeError(f"Failed after {max_retries + 1} attempts. Last error: {last_error}")
178
178 lines PYTHON