kling motion fixes again

This commit is contained in:
Steve Tietze
2026-01-09 11:26:07 +07:00
parent dbce554bc3
commit 68846ca56d
3 changed files with 88 additions and 57 deletions
+5 -22
View File
@@ -2,7 +2,6 @@
import json
import time
from pathlib import Path
from typing import Any
import torch
@@ -15,7 +14,7 @@ from .log import _log
from .results import _extract_result_urls
from .upload import _image_tensor_to_png_bytes, _truncate_url, _upload_image, _upload_video
from .validation import _validate_prompt
from .video import _download_video, _video_bytes_to_comfy_video
from .video import _coerce_video_to_mp4_bytes, _download_video, _video_bytes_to_comfy_video
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
@@ -111,29 +110,13 @@ def run_kling26motion_i2v_video(
image_url = _upload_image(api_key, png_bytes)
_log(log, f"Image upload success: {_truncate_url(image_url)}")
# Resolve the input video into bytes so it can be uploaded.
video_bytes: bytes
if isinstance(video, (bytes, bytearray)):
video_bytes = bytes(video)
elif isinstance(video, str):
try:
video_bytes = Path(video).read_bytes()
except OSError as exc:
raise RuntimeError(f"Failed to read video file: {exc}") from exc
elif isinstance(video, dict):
video_path = video.get("path") or video.get("filename")
if not video_path:
raise RuntimeError("video input dict must include a 'path' or 'filename'.")
try:
video_bytes = Path(video_path).read_bytes()
except OSError as exc:
raise RuntimeError(f"Failed to read video file: {exc}") from exc
else:
raise RuntimeError("video input must be bytes, a file path string, or a dict with a path.")
# Resolve the input video into MP4 bytes so it can be uploaded.
video_bytes, source_desc = _coerce_video_to_mp4_bytes(video)
_log(log, f"Motion video source: {source_desc}")
# Upload the motion reference video using the shared upload helper.
_log(log, "Uploading motion reference video for Kling 2.6 Motion I2V...")
video_url = _upload_video(api_key, video_bytes)
video_url = _upload_video(api_key, video_bytes, filename="motion.mp4")
_log(log, f"Video upload success: {_truncate_url(video_url)}")
# Normalize the resolution mode to the API's expected values.
+28 -35
View File
@@ -9,13 +9,12 @@ from .http import TransientKieError, requests
UPLOAD_URL = "https://kieai.redpandaai.co/api/file-stream-upload"
UPLOAD_PATH = "images/user-uploads"
IMAGE_UPLOAD_PATH = "images/user-uploads"
VIDEO_UPLOAD_PATH = "videos/user-uploads"
def _truncate_url(url: str, max_length: int = 80) -> str:
if len(url) <= max_length:
return url
return url[:max_length] + "..."
return url if len(url) <= max_length else url[:max_length] + "..."
def _image_tensor_to_png_bytes(image: torch.Tensor) -> bytes:
@@ -31,11 +30,11 @@ def _image_tensor_to_png_bytes(image: torch.Tensor) -> bytes:
working = image.detach().cpu()
working = working.contiguous()
height, width, _channels = working.shape
h, w, _ = working.shape
data_bytes = bytes(working.view(-1).tolist())
try:
pil_image = Image.frombytes("RGB", (width, height), data_bytes)
pil_image = Image.frombytes("RGB", (w, h), data_bytes)
except Exception as exc:
raise RuntimeError("Failed to convert tensor to image.") from exc
@@ -50,7 +49,7 @@ def _upload_image(api_key: str, png_bytes: bytes) -> str:
UPLOAD_URL,
headers={"Authorization": f"Bearer {api_key}"},
files={"file": ("image.png", png_bytes, "image/png")},
data={"uploadPath": UPLOAD_PATH},
data={"uploadPath": IMAGE_UPLOAD_PATH},
timeout=120,
)
except requests.RequestException as exc:
@@ -58,21 +57,15 @@ def _upload_image(api_key: str, png_bytes: bytes) -> str:
if response.status_code == 429 or response.status_code >= 500:
raise TransientKieError(
f"upload returned HTTP {response.status_code}: {response.text}", status_code=response.status_code
f"upload returned HTTP {response.status_code}: {response.text}",
status_code=response.status_code,
)
try:
payload_json: Any = response.json()
except json.JSONDecodeError as exc:
raise RuntimeError("Upload endpoint did not return valid JSON.") from exc
payload = response.json()
if not payload.get("success") or payload.get("code") != 200:
raise RuntimeError(f"Upload failed: {payload.get('msg')}")
success = payload_json.get("success")
code = payload_json.get("code")
if not success or code != 200:
raise RuntimeError(f"Upload failed (code={code}): {payload_json.get('msg')}")
data = payload_json.get("data") or {}
url = data.get("downloadUrl")
url = (payload.get("data") or {}).get("downloadUrl")
if not url:
raise RuntimeError("Upload response missing downloadUrl.")
@@ -80,36 +73,36 @@ def _upload_image(api_key: str, png_bytes: bytes) -> str:
def _upload_video(api_key: str, video_bytes: bytes, filename: str = "video.mp4") -> str:
if not filename:
raise RuntimeError("filename is required for video uploads.")
if not isinstance(video_bytes, (bytes, bytearray)):
raise RuntimeError("video_bytes must be raw bytes.")
if len(video_bytes) == 0:
raise RuntimeError("video_bytes is empty.")
if not filename.lower().endswith(".mp4"):
filename = f"{filename}.mp4"
try:
response = requests.post(
UPLOAD_URL,
headers={"Authorization": f"Bearer {api_key}"},
files={"file": (filename, video_bytes, "video/mp4")},
data={"uploadPath": UPLOAD_PATH, "fileName": filename},
timeout=180,
data={"uploadPath": VIDEO_UPLOAD_PATH, "fileName": filename},
timeout=300,
)
except requests.RequestException as exc:
raise RuntimeError(f"Failed to upload video: {exc}") from exc
if response.status_code == 429 or response.status_code >= 500:
raise TransientKieError(
f"upload returned HTTP {response.status_code}: {response.text}", status_code=response.status_code
f"upload returned HTTP {response.status_code}: {response.text}",
status_code=response.status_code,
)
try:
payload_json: Any = response.json()
except json.JSONDecodeError as exc:
raise RuntimeError("Upload endpoint did not return valid JSON.") from exc
payload = response.json()
if not payload.get("success") or payload.get("code") != 200:
raise RuntimeError(f"Upload failed: {payload.get('msg')}")
success = payload_json.get("success")
code = payload_json.get("code")
if not success or code != 200:
raise RuntimeError(f"Upload failed (code={code}): {payload_json.get('msg')}")
data = payload_json.get("data") or {}
url = data.get("downloadUrl")
url = (payload.get("data") or {}).get("downloadUrl")
if not url:
raise RuntimeError("Upload response missing downloadUrl.")
+55
View File
@@ -25,6 +25,61 @@ def _download_video(url: str) -> bytes:
return response.content
def _coerce_video_to_mp4_bytes(video) -> tuple[bytes, str]:
"""Coerce ComfyUI VIDEO input into MP4 bytes for upload."""
if isinstance(video, (bytes, bytearray)):
return bytes(video), "bytes"
if isinstance(video, str):
try:
return Path(video).read_bytes(), f"path:{video}"
except OSError as exc:
raise RuntimeError(f"Failed to read video file: {exc}") from exc
if isinstance(video, dict):
video_path = video.get("path") or video.get("filename")
if not video_path:
raise RuntimeError("video input dict must include a 'path' or 'filename'.")
try:
return Path(video_path).read_bytes(), f"dict_path:{video_path}"
except OSError as exc:
raise RuntimeError(f"Failed to read video file: {exc}") from exc
temp_dir = None
for attr in ("get_temp_directory", "get_temp_folder", "get_output_directory"):
getter = getattr(folder_paths, attr, None)
if callable(getter):
temp_dir = Path(getter())
break
if temp_dir is None:
temp_dir = Path.cwd()
if hasattr(video, "save_to") and callable(getattr(video, "save_to")):
temp_path = temp_dir / f"kie_video_{int(time.time() * 1000)}.mp4"
try:
video.save_to(str(temp_path))
return temp_path.read_bytes(), f"save_to:{temp_path}"
except Exception as exc:
raise RuntimeError(f"Failed to save Comfy VIDEO input: {exc}") from exc
if hasattr(video, "save") and callable(getattr(video, "save")):
temp_path = temp_dir / f"kie_video_{int(time.time() * 1000)}.mp4"
try:
video.save(str(temp_path))
return temp_path.read_bytes(), f"save:{temp_path}"
except Exception as exc:
raise RuntimeError(f"Failed to save Comfy VIDEO input: {exc}") from exc
path_attr = getattr(video, "path", None)
if isinstance(path_attr, str):
try:
return Path(path_attr).read_bytes(), f"path_attr:{path_attr}"
except OSError as exc:
raise RuntimeError(f"Failed to read video file: {exc}") from exc
raise RuntimeError("video input must be bytes, a file path string, a dict with a path, or a Comfy VIDEO object.")
def _video_bytes_to_comfy_video(video_bytes: bytes):
"""
Convert MP4 bytes into a ComfyUI VIDEO object that the official SaveVideo node accepts.