From d0f454df290061386fa7daf0661ba77f5e4802af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Fri, 10 Apr 2026 18:06:40 +0200 Subject: [PATCH] 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 --- freepik_cli/api/client.py | 9 +++++++- freepik_cli/api/models.py | 39 ++++++++++++++++++++++++++++++++ freepik_cli/commands/generate.py | 4 ++-- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/freepik_cli/api/client.py b/freepik_cli/api/client.py index 665b811..03a2942 100644 --- a/freepik_cli/api/client.py +++ b/freepik_cli/api/client.py @@ -34,10 +34,17 @@ class FreepikAPIError(Exception): 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 = { 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.", - 422: "Invalid request parameters. Check the options you provided.", 429: "Rate limit exceeded. Please wait before retrying.", } hint = hints.get(response.status_code) diff --git a/freepik_cli/api/models.py b/freepik_cli/api/models.py index d023524..4e03f89 100644 --- a/freepik_cli/api/models.py +++ b/freepik_cli/api/models.py @@ -124,6 +124,45 @@ VIDEO_UPSCALE_POST_ENDPOINTS: dict[VideoUpscaleMode, str] = { 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 # --------------------------------------------------------------------------- diff --git a/freepik_cli/commands/generate.py b/freepik_cli/commands/generate.py index 67e89a1..27ad5a9 100644 --- a/freepik_cli/commands/generate.py +++ b/freepik_cli/commands/generate.py @@ -10,7 +10,7 @@ import typer from freepik_cli.api.client import FreepikAPIError, FreepikClient from freepik_cli.api.edit import EditAPI 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.utils.config import FreepikConfig 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 payload: dict = {"prompt": prompt} if aspect_ratio: - payload["aspect_ratio"] = aspect_ratio + payload["aspect_ratio"] = normalize_aspect_ratio(aspect_ratio, model) if negative_prompt: payload["negative_prompt"] = negative_prompt if seed is not None: