From a2358ba01ca113c859ecaea5dd2c8b592c02ffca Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Fri, 24 Apr 2026 18:38:31 +0100 Subject: [PATCH] fix: correct Kling API params per official docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - camera_control: only kling-v1 and kling-v1-5 support it (not v3) - For preset types (down_back etc.), config must be absent — only 'simple' type uses config fields - camera_control and image_tail are mutually exclusive in I2V - cfg_scale not supported by kling-v2.x models — now skipped - duration sent as string to match API examples - v3/v3-omni removed from camera control UI list Co-Authored-By: Claude Sonnet 4.6 --- backend/kling_api.php | 61 +++++++++++++++---------- frontend/src/components/VideoGenTab.jsx | 4 +- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/backend/kling_api.php b/backend/kling_api.php index 6908644..d27f241 100644 --- a/backend/kling_api.php +++ b/backend/kling_api.php @@ -146,15 +146,20 @@ class KlingAPI { * Text-to-Video generation */ public function textToVideo($prompt, $opts = []) { + $modelName = $opts['model_name'] ?? 'kling-v3'; $payload = [ - 'prompt' => $prompt, - 'model_name' => $opts['model_name'] ?? 'kling-v2-6', - 'mode' => $opts['mode'] ?? 'std', - 'duration' => intval($opts['duration'] ?? 5), + 'prompt' => $prompt, + 'model_name' => $modelName, + 'mode' => $opts['mode'] ?? 'std', + 'duration' => strval(intval($opts['duration'] ?? 5)), 'aspect_ratio' => $opts['aspect_ratio'] ?? '16:9', - 'cfg_scale' => floatval($opts['cfg_scale'] ?? 0.5) ]; + // cfg_scale not supported by v2.x models + if (!preg_match('/^kling-v2/', $modelName)) { + $payload['cfg_scale'] = floatval($opts['cfg_scale'] ?? 0.5); + } + if (!empty($opts['negative_prompt'])) { $payload['negative_prompt'] = $opts['negative_prompt']; } @@ -163,14 +168,16 @@ class KlingAPI { $payload['sound'] = 'on'; } - // Camera/motion control — only supported by kling-v1, kling-v1-5, kling-v3, kling-v3-omni - $cameraControlModels = ['kling-v1', 'kling-v1-5', 'kling-v3', 'kling-v3-omni']; + // camera_control only supported by kling-v1 and kling-v1-5 per capability map if (!empty($opts['camera_control']) && $opts['camera_control'] !== 'none' - && in_array($opts['model_name'] ?? '', $cameraControlModels)) { - $payload['camera_control'] = [ - 'type' => $opts['camera_control'], - 'config' => ['horizontal' => 0, 'vertical' => 0, 'pan' => 0, 'tilt' => 0, 'roll' => 0, 'zoom' => 0] - ]; + && in_array($modelName, ['kling-v1', 'kling-v1-5'])) { + $cameraType = $opts['camera_control']; + // For preset types, config must be null/absent; only 'simple' type uses config + if ($cameraType === 'simple') { + $payload['camera_control'] = ['type' => 'simple', 'config' => ['horizontal' => 0, 'vertical' => 0, 'pan' => 0, 'tilt' => 0, 'roll' => 0, 'zoom' => 0]]; + } else { + $payload['camera_control'] = ['type' => $cameraType]; + } } error_log("Kling T2V request: model={$payload['model_name']}, mode={$payload['mode']}, duration={$payload['duration']}"); @@ -184,14 +191,19 @@ class KlingAPI { * Image-to-Video generation */ public function imageToVideo($images, $prompt, $opts = []) { + $modelName = $opts['model_name'] ?? 'kling-v3'; $payload = [ - 'model_name' => $opts['model_name'] ?? 'kling-v2-6', - 'mode' => $opts['mode'] ?? 'std', - 'duration' => intval($opts['duration'] ?? 5), + 'model_name' => $modelName, + 'mode' => $opts['mode'] ?? 'std', + 'duration' => strval(intval($opts['duration'] ?? 5)), 'aspect_ratio' => $opts['aspect_ratio'] ?? '16:9', - 'cfg_scale' => floatval($opts['cfg_scale'] ?? 0.5) ]; + // cfg_scale not supported by v2.x models + if (!preg_match('/^kling-v2/', $modelName)) { + $payload['cfg_scale'] = floatval($opts['cfg_scale'] ?? 0.5); + } + if (!empty($prompt)) { $payload['prompt'] = $prompt; } @@ -204,14 +216,17 @@ class KlingAPI { $payload['sound'] = 'on'; } - // Camera/motion control — only supported by kling-v1, kling-v1-5, kling-v3, kling-v3-omni - $cameraControlModels = ['kling-v1', 'kling-v1-5', 'kling-v3', 'kling-v3-omni']; + // camera_control only for kling-v1/v1-5; also mutually exclusive with image_tail + $hasTailFrame = isset($images[1]); if (!empty($opts['camera_control']) && $opts['camera_control'] !== 'none' - && in_array($opts['model_name'] ?? '', $cameraControlModels)) { - $payload['camera_control'] = [ - 'type' => $opts['camera_control'], - 'config' => ['horizontal' => 0, 'vertical' => 0, 'pan' => 0, 'tilt' => 0, 'roll' => 0, 'zoom' => 0] - ]; + && in_array($modelName, ['kling-v1', 'kling-v1-5']) + && !$hasTailFrame) { + $cameraType = $opts['camera_control']; + if ($cameraType === 'simple') { + $payload['camera_control'] = ['type' => 'simple', 'config' => ['horizontal' => 0, 'vertical' => 0, 'pan' => 0, 'tilt' => 0, 'roll' => 0, 'zoom' => 0]]; + } else { + $payload['camera_control'] = ['type' => $cameraType]; + } } // Start frame — field name is `image`, value is clean base64 (no data URI prefix) diff --git a/frontend/src/components/VideoGenTab.jsx b/frontend/src/components/VideoGenTab.jsx index c2f3d00..c9d662e 100644 --- a/frontend/src/components/VideoGenTab.jsx +++ b/frontend/src/components/VideoGenTab.jsx @@ -240,8 +240,8 @@ const VideoGenTab = ({ activeProjectId, rerunData, onRerunLoaded, onBusyChange, { value: 'kling-v1-6', label: 'V1.6', description: 'Legacy' }, { value: 'kling-v1', label: 'V1', description: 'Cam control' }, ]; - // Models that support camera / motion control - const klingCameraControlModels = ['kling-v1', 'kling-v1-5', 'kling-v3', 'kling-v3-omni']; + // camera_control presets only supported by kling-v1 and kling-v1-5 per official capability map + const klingCameraControlModels = ['kling-v1', 'kling-v1-5']; const cameraPresets = [ { value: 'none', label: 'None' },