V 2.0.0 - Universal api #68 - Kling

This commit is contained in:
DESKTOP-TVBJISQ\Primere
2026-03-07 14:16:13 +01:00
parent 4f2dec1efe
commit 6a19c81b68
8 changed files with 701 additions and 457 deletions
+22 -15
View File
@@ -68,6 +68,8 @@ class PrimereApiProcessor:
"reference_images": ("IMAGE", {"default": None, "forceInput": True}),
"first_image": ("IMAGE", {"default": None, "forceInput": True}),
"last_image": ("IMAGE", {"default": None, "forceInput": True}),
"frontal_image": ("IMAGE", {"default": None, "forceInput": True}),
"reference_video": ("VIDEO", {"default": None, "forceInput": True}),
"width": ("INT", {"default": 1024, "max": 8192, "min": 64, "step": 64, "forceInput": True}),
"height": ("INT", {"default": 1024, "max": 8192, "min": 64, "step": 64, "forceInput": True}),
"aspect_ratio": ("STRING", {"forceInput": True, "default": "1:1"}),
@@ -82,7 +84,7 @@ class PrimereApiProcessor:
return {"required": cls.required_inputs, "optional": cls.optional_inputs, "hidden": hidden_inputs}
def process_uniapi(self, processor, api_provider, api_service, prompt, negative_prompt = None, batch = 1, reference_images = None, first_image = None, last_image = None, width = 1024, height = 1024, aspect_ratio = '1:1', seed = None, debug_mode = False, unique_id = None, **kwargs):
def process_uniapi(self, processor, api_provider, api_service, prompt, negative_prompt = None, batch = 1, reference_images = None, first_image = None, last_image = None, frontal_image = None, reference_video = None, width = 1024, height = 1024, aspect_ratio = '1:1', seed = None, debug_mode = False, unique_id = None, **kwargs):
API_SCHEMAS_PATH = os.path.join(PRIMERE_ROOT, 'front_end', 'api_schemas.json')
API_CONFIG_PATH = os.path.join(PRIMERE_ROOT, 'json', 'apiconfig.json')
API_SCHEMA_REGISTRY = api_schema_registry.load_and_validate_api_schema_registry(API_SCHEMAS_PATH, API_CONFIG_PATH)
@@ -187,22 +189,27 @@ class PrimereApiProcessor:
if isinstance(img_binary_api, dict) and len(img_binary_api) > 0:
selected_parameters.update(img_binary_api)
elif img_binary_api not in (None, "") and (not isinstance(img_binary_api, list) or len(img_binary_api) > 0):
elif isinstance(img_binary_api, list) and len(img_binary_api) > 0:
selected_parameters["reference_images"] = img_binary_api
if first_image is not None:
first_image_source = first_image
elif reference_images is not None:
first_image_source = reference_images[0] if isinstance(reference_images, list) else reference_images
else:
first_image_source = None
if first_image_source is not None:
first_image_result = external_api_backend.apply_reference_images_handler(
schema, api_provider,
{"img_binary_api": first_image_source, "loaded_client_for_upload": loaded_client_for_upload}
)
if isinstance(first_image_result, dict) and first_image_result:
selected_parameters.update(first_image_result)
first_image_source = first_image if first_image is not None else (reference_images[0] if isinstance(reference_images, list) else reference_images) if reference_images is not None else None
single_ref_inputs = [
("first_image", first_image_source),
("last_image", last_image),
("frontal_image", frontal_image),
("reference_video", reference_video),
]
if not debug_mode:
for ref_key, ref_source in single_ref_inputs:
if ref_source is None:
continue
ref_result = external_api_backend.apply_reference_images_handler(
schema, api_provider,
{"img_binary_api": ref_source, "loaded_client_for_upload": loaded_client_for_upload, "target_key": ref_key}
)
if isinstance(ref_result, dict) and ref_result:
selected_parameters.update(ref_result)
possible_parameters = schema.get("possible_parameters", {}) if isinstance(schema, dict) else {}
if isinstance(possible_parameters, dict):
File diff suppressed because one or more lines are too long
+30 -6
View File
@@ -57,8 +57,8 @@ def _marker_default(marker: Any) -> Any:
def _default_value(name: str) -> Any:
k = name.lower()
if "prompt" in k:
return "cute cat walking in the futuristic metropolis"
if k in {"prompt", "negative_prompt", "multi_prompt"}:
return None
if "response_modalities" in k:
return "IMAGE"
if "model" in k:
@@ -69,7 +69,7 @@ def _default_value(name: str) -> Any:
return "1K"
if "number" in k or "count" in k or "width" in k or "height" in k:
return 1
return f"default_{name}"
return None
def _is_optional_image_input(name: str) -> bool:
low = str(name or "").lower()
@@ -121,6 +121,21 @@ def _build_values(spec: dict[str, Any], values: dict[str, Any] | None = None) ->
marker_selected = _marker_default(possible_parameters.get(canonical))
selected = marker_selected if marker_selected is not None else _default_value(canonical)
if selected is not None:
raw_marker = possible_parameters.get(key) if not isinstance(possible_parameters.get(key), list) else possible_parameters.get(canonical)
if isinstance(raw_marker, str):
t = raw_marker.strip().upper()
if t == "INT":
try:
selected = int(selected)
except (ValueError, TypeError):
pass
elif t == "FLOAT":
try:
selected = round(float(selected), 1)
except (ValueError, TypeError):
pass
resolved[key] = selected
return resolved
@@ -165,21 +180,30 @@ def _remove_none_values(value: Any) -> Any:
for key, child in value.items():
if child is None:
continue
cleaned[key] = _remove_none_values(child)
result = _remove_none_values(child)
if result == {} or result == []:
continue
cleaned[key] = result
return cleaned
if isinstance(value, list):
cleaned_list = []
for child in value:
if child is None:
continue
cleaned_list.append(_remove_none_values(child))
result = _remove_none_values(child)
if result == {} or result == []:
continue
cleaned_list.append(result)
return cleaned_list
if isinstance(value, tuple):
cleaned_tuple = []
for child in value:
if child is None:
continue
cleaned_tuple.append(_remove_none_values(child))
result = _remove_none_values(child)
if result == {} or result == []:
continue
cleaned_tuple.append(result)
return tuple(cleaned_tuple)
return value
+2
View File
@@ -757,6 +757,8 @@ def canonical_param_name(name: str, *, number_of_images_as_seed: bool = False) -
return "model"
if number_of_images_as_seed and low == "number_of_images":
return "seed"
if low in {"negative_prompt", "multi_prompt", "system_prompt"}:
return low
if low in {"prompt", "contents"} or low.endswith("_prompt"):
return "prompt"
if "response_modalities" in low:
+42
View File
@@ -0,0 +1,42 @@
from __future__ import annotations
import os
import tempfile
from typing import Any
import numpy as np
from PIL import Image
def handle_reference_images(img_binary_api: Any = None, temp_file_ref: str = "", loaded_client_for_upload: Any = None, target_key: str = "first_image", **_: Any):
if temp_file_ref:
output = img_binary_api if isinstance(img_binary_api, list) else []
if hasattr(loaded_client_for_upload, "upload_file"):
output.append(loaded_client_for_upload.upload_file(temp_file_ref))
else:
output.append(temp_file_ref)
return output
if img_binary_api is not None and type(img_binary_api).__name__ == "Tensor":
img_array = (img_binary_api[0].cpu().numpy() * 255).astype(np.uint8)
pil_image = Image.fromarray(img_array)
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
tmp_path = tmp.name
pil_image.save(tmp_path, format="PNG")
try:
uploaded = loaded_client_for_upload.upload_file(tmp_path) if hasattr(loaded_client_for_upload, "upload_file") else tmp_path
finally:
try:
os.unlink(tmp_path)
except OSError:
pass
return {target_key: uploaded}
if img_binary_api is not None and isinstance(img_binary_api, str) and img_binary_api:
if os.path.isfile(img_binary_api) and hasattr(loaded_client_for_upload, "upload_file"):
uploaded = loaded_client_for_upload.upload_file(img_binary_api)
else:
uploaded = img_binary_api
return {target_key: uploaded}
return img_binary_api if isinstance(img_binary_api, list) else []
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
import json
from . import response_helper
def handle_response(api_result, schema=None):
try:
json_object = json.loads(json.dumps(api_result))
except (ValueError, TypeError) as exc:
raise RuntimeError(f"Invalid JSON response received: {api_result}") from exc
video = json_object.get("video") if isinstance(json_object, dict) else None
if not isinstance(video, dict):
raise RuntimeError(f"No 'video' key in Kling response: {json_object}")
video_url = video.get("url")
if not video_url:
raise RuntimeError(f"No URL in Kling video response: {video}")
video_bytes = response_helper.fetch_url_bytes(video_url)
return ["video_result", video_bytes]
+120 -37
View File
@@ -89,6 +89,9 @@
"model": "{{model}}",
"prompt": "{{prompt}}",
"image": "{{first_image}}",
"lastFrame": "{{last_image}}",
"referenceImages": "{{reference_images}}",
"video": "{{reference_video}}",
"config": {
"$call": "types.GenerateVideosConfig",
"$args": [],
@@ -99,7 +102,6 @@
"durationSeconds": "{{duration_seconds}}",
"personGeneration": "{{person_generation}}",
"reference_images": "{{reference_images}}",
"last_frame": "{{last_image}}",
"negative_prompt": "{{negative_prompt}}"
}
}
@@ -198,60 +200,141 @@
}
}
},
"Kling_V3PRO_T2V": {
"Kling": {
"provider": "FAL",
"service": "Kling_V3PRO_T2V",
"service": "Kling",
"response_handler": "FAL_Kling.py",
"reference_images_handler": "FAL_Kling.py",
"import_modules": [
"import fal_client"
],
"possible_parameters": {
"aspect_ratio": ["16:9", "9:16", "1:1"],
"duration": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
"generate_audio": [false, true],
"with_logs": [false, true]
"aspect_ratio": [
"16:9",
"9:16",
"1:1"
],
"duration": [
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
],
"generate_audio": [
false,
true
],
"with_logs": [
false,
true
],
"model_type": [
"text-to-video",
"image-to-video"
],
"version": [
"v3/pro",
"v3/standard",
"o3/pro",
"o3/standard"
],
"cfg_scale": "FLOAT"
},
"request": {
"method": "SDK",
"endpoint": "fal_client.submit",
"sdk_call": {
"args": [
"{{arg0}}"
],
"kwargs": {
"arguments": {
"prompt": "{{prompt}}",
"duration": "{{duration}}",
"aspect_ratio": "{{aspect_ratio}}",
"generate_audio": "{{generate_audio}}",
"with_logs": "{{with_logs}}"
}
}
"request_exclusions": [
{
"when": {
"path": "version",
"equals": "v3/pro"
},
"remove": [
"image_url"
]
},
{
"when": {
"path": "version",
"equals": "v3/standard"
},
"remove": [
"image_url"
]
},
{
"when": {
"path": "version",
"equals": "o3/standard"
},
"remove": [
"negative_prompt",
"cfg_scale",
"start_image_url",
"elements",
"aspect_ratio"
]
},
{
"when": {
"path": "version",
"equals": "o3/pro"
},
"remove": [
"negative_prompt",
"cfg_scale",
"start_image_url",
"elements",
"aspect_ratio"
]
},
{
"when": {
"path": "model_type",
"equals": "text-to-video"
},
"remove": [
"multi_prompt",
"start_image_url",
"image_url",
"elements"
]
}
}
},
"Kling_V3PRO_I2V": {
"provider": "FAL",
"service": "Kling_V3PRO_I2V",
"import_modules": [
"import fal_client"
],
"possible_parameters": {
"aspect_ratio": ["16:9", "9:16", "1:1"],
"duration": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
"generate_audio": [false, true],
"with_logs": [false, true]
"parameter_constraints": {
"cfg_scale": {"max": 1.0}
},
"request": {
"method": "SDK",
"endpoint": "fal_client.subscribe",
"sdk_call": {
"args": [
"{{arg0}}"
"fal-ai/kling-video/{{version}}/{{model_type}}"
],
"kwargs": {
"arguments": {
"prompt": "{{prompt}}",
"start_image_url": "{{reference_images}}",
"negative_prompt": "{{negative_prompt}}",
"multi_prompt": "{{multi_prompt}}",
"cfg_scale": "{{cfg_scale}}",
"start_image_url": "{{first_image}}",
"image_url": "{{first_image}}",
"end_image_url": "{{last_image}}",
"elements": [
{
"reference_image_urls": "{{reference_images}}",
"frontal_image_url": "{{frontal_image}}"
},
{
"video_url": "{{reference_video}}"
}
],
"duration": "{{duration}}",
"aspect_ratio": "{{aspect_ratio}}",
"generate_audio": "{{generate_audio}}",
+2
View File
@@ -181,6 +181,8 @@ def canonical_param_name(name: str) -> str:
return "resolution"
if low == "model" or low.endswith("_model"):
return "model"
if low in {"negative_prompt", "multi_prompt", "system_prompt"}:
return low
if low in {"prompt", "contents"} or low.endswith("_prompt"):
return "prompt"
if "response_modalities" in low: