fix: normalize aspect ratio per model, surface invalid_params in errors
Models like mystic, flux-pro-1.1, and seedream-v4/v4-5 require named aspect ratio slugs (e.g. "square_1_1", "widescreen_16_9") while other models accept the "W:H" format directly. - Add normalize_aspect_ratio() mapping W:H strings to slugs for affected models - Apply normalization in generate-image before building the request payload - Improve FreepikAPIError to surface invalid_params field details from the API response, so "Validation error" now also shows which field failed and why Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,10 +34,17 @@ class FreepikAPIError(Exception):
|
|||||||
or f"HTTP {response.status_code}"
|
or f"HTTP {response.status_code}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Append individual field validation errors when present
|
||||||
|
invalid = body.get("invalid_params", [])
|
||||||
|
if invalid:
|
||||||
|
details = "\n".join(
|
||||||
|
f" • {p.get('field', '?')}: {p.get('reason', '')}" for p in invalid
|
||||||
|
)
|
||||||
|
message = f"{message}\n\n{details}"
|
||||||
|
|
||||||
hints = {
|
hints = {
|
||||||
401: "Check your API key — set FREEPIK_API_KEY or use --api-key.",
|
401: "Check your API key — set FREEPIK_API_KEY or use --api-key.",
|
||||||
403: "Your plan may not support this feature. Check your Freepik subscription.",
|
403: "Your plan may not support this feature. Check your Freepik subscription.",
|
||||||
422: "Invalid request parameters. Check the options you provided.",
|
|
||||||
429: "Rate limit exceeded. Please wait before retrying.",
|
429: "Rate limit exceeded. Please wait before retrying.",
|
||||||
}
|
}
|
||||||
hint = hints.get(response.status_code)
|
hint = hints.get(response.status_code)
|
||||||
|
|||||||
@@ -124,6 +124,45 @@ VIDEO_UPSCALE_POST_ENDPOINTS: dict[VideoUpscaleMode, str] = {
|
|||||||
VIDEO_UPSCALE_STATUS_ENDPOINT = "/v1/ai/video-upscaler/{task_id}"
|
VIDEO_UPSCALE_STATUS_ENDPOINT = "/v1/ai/video-upscaler/{task_id}"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Aspect ratio normalization
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Models that require named slug aspect ratios instead of "W:H" strings
|
||||||
|
SLUG_ASPECT_RATIO_MODELS: set[ImageModel] = {
|
||||||
|
ImageModel.MYSTIC,
|
||||||
|
ImageModel.FLUX_PRO_1_1,
|
||||||
|
ImageModel.SEEDREAM_V4,
|
||||||
|
ImageModel.SEEDREAM_V4_5,
|
||||||
|
}
|
||||||
|
|
||||||
|
# User-friendly "W:H" → API slug mapping
|
||||||
|
_RATIO_TO_SLUG: dict[str, str] = {
|
||||||
|
"1:1": "square_1_1",
|
||||||
|
"16:9": "widescreen_16_9",
|
||||||
|
"9:16": "social_story_9_16",
|
||||||
|
"4:3": "classic_4_3",
|
||||||
|
"3:4": "traditional_3_4",
|
||||||
|
"3:2": "standard_3_2",
|
||||||
|
"2:3": "portrait_2_3",
|
||||||
|
"2:1": "horizontal_2_1",
|
||||||
|
"1:2": "vertical_1_2",
|
||||||
|
"4:5": "social_post_4_5",
|
||||||
|
"21:9": "widescreen_16_9", # closest match
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_aspect_ratio(ratio: str, model: ImageModel) -> str:
|
||||||
|
"""Convert a user-facing aspect ratio to the format required by the model."""
|
||||||
|
if model not in SLUG_ASPECT_RATIO_MODELS:
|
||||||
|
return ratio # free-form models accept "1:1" directly
|
||||||
|
slug = _RATIO_TO_SLUG.get(ratio)
|
||||||
|
if slug:
|
||||||
|
return slug
|
||||||
|
# Already a slug (user passed "square_1_1" directly) — pass through
|
||||||
|
return ratio
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Response normalization helpers
|
# Response normalization helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import typer
|
|||||||
from freepik_cli.api.client import FreepikAPIError, FreepikClient
|
from freepik_cli.api.client import FreepikAPIError, FreepikClient
|
||||||
from freepik_cli.api.edit import EditAPI
|
from freepik_cli.api.edit import EditAPI
|
||||||
from freepik_cli.api.images import ImageAPI
|
from freepik_cli.api.images import ImageAPI
|
||||||
from freepik_cli.api.models import IconStyle, ImageModel, VideoModel
|
from freepik_cli.api.models import IconStyle, ImageModel, VideoModel, normalize_aspect_ratio
|
||||||
from freepik_cli.api.videos import VideoAPI
|
from freepik_cli.api.videos import VideoAPI
|
||||||
from freepik_cli.utils.config import FreepikConfig
|
from freepik_cli.utils.config import FreepikConfig
|
||||||
from freepik_cli.utils.console import GenerationResult, console, print_error, print_no_wait, print_result
|
from freepik_cli.utils.console import GenerationResult, console, print_error, print_no_wait, print_result
|
||||||
@@ -86,7 +86,7 @@ def generate_image(
|
|||||||
# Build request payload
|
# Build request payload
|
||||||
payload: dict = {"prompt": prompt}
|
payload: dict = {"prompt": prompt}
|
||||||
if aspect_ratio:
|
if aspect_ratio:
|
||||||
payload["aspect_ratio"] = aspect_ratio
|
payload["aspect_ratio"] = normalize_aspect_ratio(aspect_ratio, model)
|
||||||
if negative_prompt:
|
if negative_prompt:
|
||||||
payload["negative_prompt"] = negative_prompt
|
payload["negative_prompt"] = negative_prompt
|
||||||
if seed is not None:
|
if seed is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user