From 4cd88ba4777381d3f6afbb8c160be33e62139a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Fri, 10 Apr 2026 18:50:41 +0200 Subject: [PATCH] fix: skip aspect_ratio for kling-o1 models and detect image MIME via Pillow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kling-o1-pro and kling-o1-std silently fail when aspect_ratio is included in the payload — they derive it from the input image. Added VIDEO_ASPECT_RATIO_MODELS whitelist so only kling-elements and minimax-hailuo receive the parameter. Also switched image_to_base64 to use Pillow for format detection instead of trusting the file extension, which correctly handles files saved with the wrong extension. Co-Authored-By: Claude Sonnet 4.6 --- freepik_cli/api/models.py | 9 ++++++++- freepik_cli/api/videos.py | 4 ++-- freepik_cli/utils/files.py | 26 +++++++++++++++++--------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/freepik_cli/api/models.py b/freepik_cli/api/models.py index 0b73b36..61127ed 100644 --- a/freepik_cli/api/models.py +++ b/freepik_cli/api/models.py @@ -107,7 +107,14 @@ VIDEO_IMAGE_FIELDS: dict[VideoModel, str] = { VideoModel.MINIMAX_HAILUO: "image", } -# Kling Elements requires named aspect ratio slugs +# Models that support an aspect_ratio parameter at all +VIDEO_ASPECT_RATIO_MODELS: set[VideoModel] = { + VideoModel.KLING_ELEMENTS_PRO, + VideoModel.KLING_ELEMENTS_STD, + VideoModel.MINIMAX_HAILUO, +} + +# Of those, these require named slug aspect ratios instead of "W:H" strings VIDEO_SLUG_ASPECT_RATIO_MODELS: set[VideoModel] = { VideoModel.KLING_ELEMENTS_PRO, VideoModel.KLING_ELEMENTS_STD, diff --git a/freepik_cli/api/videos.py b/freepik_cli/api/videos.py index 9bd637b..d3b01dc 100644 --- a/freepik_cli/api/videos.py +++ b/freepik_cli/api/videos.py @@ -9,7 +9,7 @@ from freepik_cli.api.models import ( VIDEO_POST_ENDPOINTS, VIDEO_STATUS_ENDPOINTS, VIDEO_IMAGE_FIELDS, - VIDEO_SLUG_ASPECT_RATIO_MODELS, + VIDEO_ASPECT_RATIO_MODELS, VideoModel, get_output_urls, get_status, @@ -49,7 +49,7 @@ class VideoAPI: effective_duration = 6 payload["duration"] = str(effective_duration) - if aspect_ratio: + if aspect_ratio and model in VIDEO_ASPECT_RATIO_MODELS: payload["aspect_ratio"] = normalize_aspect_ratio_video(aspect_ratio, model) if seed is not None: diff --git a/freepik_cli/utils/files.py b/freepik_cli/utils/files.py index f3da0ca..e9364bb 100644 --- a/freepik_cli/utils/files.py +++ b/freepik_cli/utils/files.py @@ -20,16 +20,24 @@ from rich.progress import ( def image_to_base64(path: Path) -> str: - """Read an image file and return a base64-encoded string.""" - suffix = path.suffix.lower().lstrip(".") - mime_map = { - "jpg": "image/jpeg", - "jpeg": "image/jpeg", - "png": "image/png", - "gif": "image/gif", - "webp": "image/webp", + """Read an image file and return a base64-encoded string. + + Uses Pillow to detect the actual format rather than trusting the file + extension — mismatched extensions (e.g. a JPEG saved as .png) would + produce an incorrect MIME type that causes silent failures with some models. + """ + from PIL import Image + + _pillow_to_mime = { + "JPEG": "image/jpeg", + "PNG": "image/png", + "GIF": "image/gif", + "WEBP": "image/webp", } - mime = mime_map.get(suffix, "image/jpeg") + with Image.open(path) as img: + fmt = img.format or "JPEG" + mime = _pillow_to_mime.get(fmt, "image/jpeg") + with open(path, "rb") as f: encoded = base64.b64encode(f.read()).decode() return f"data:{mime};base64,{encoded}"