api: added seedance pro 1 fast
This commit is contained in:
@@ -143,3 +143,46 @@ Each model file SHOULD include a header comment:
|
||||
# API version: 2025-xx
|
||||
# KIE Docs: <url>
|
||||
|
||||
|
||||
## Video Models
|
||||
|
||||
Video-producing models MUST:
|
||||
- return a local file path (string)
|
||||
- use kie_api.video helpers
|
||||
- NOT decode frames
|
||||
- NOT return tensors
|
||||
|
||||
Public runner signature example:
|
||||
|
||||
```python
|
||||
def run_<model>_video(...) -> str:
|
||||
"""Returns a path to a local video file."""
|
||||
yaml
|
||||
|
||||
|
||||
This teaches Codex the rule **once**, forever.
|
||||
|
||||
---
|
||||
|
||||
### 🔹 Step 3 — Create your first KIE video model runner
|
||||
Now you’re ready for Codex.
|
||||
|
||||
📍 **Use VS Code + Codex**
|
||||
|
||||
**Prompt (copy exactly):**
|
||||
```text
|
||||
Add a KIE video model runner following docs/MODEL_IMPLEMENTATION_TEMPLATE.md.
|
||||
|
||||
Tasks:
|
||||
1. Create kie_api/<model>_video.py
|
||||
2. Use shared helpers:
|
||||
- _poll_task_until_complete
|
||||
- _extract_result_urls
|
||||
- _download_video
|
||||
- _write_video_to_temp_file
|
||||
3. Return a VIDEO file path (string).
|
||||
4. Do NOT decode video frames.
|
||||
5. Show diff before applying.
|
||||
|
||||
Always when done add the proper model to nodes.py and adjust the mappings to add this new model
|
||||
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
"""Seedance V1 Pro (Fast) image-to-video helper."""
|
||||
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
|
||||
from .auth import _load_api_key
|
||||
from .credits import _log_remaining_credits
|
||||
from .http import TransientKieError, requests
|
||||
from .jobs import _poll_task_until_complete
|
||||
from .log import _log
|
||||
from .results import _extract_result_urls
|
||||
from .upload import _image_tensor_to_png_bytes, _truncate_url, _upload_image
|
||||
from .validation import _validate_prompt
|
||||
from .video import _download_video, _write_video_to_temp_mp4
|
||||
|
||||
|
||||
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
|
||||
MODEL_NAME = "bytedance/v1-pro-fast-image-to-video"
|
||||
PROMPT_MAX_LENGTH = 10000
|
||||
RESOLUTION_OPTIONS = ["720p", "1080p"]
|
||||
DURATION_OPTIONS = ["5", "10"]
|
||||
|
||||
|
||||
def _validate_options(resolution: str, duration: str) -> None:
|
||||
if resolution not in RESOLUTION_OPTIONS:
|
||||
raise RuntimeError("Invalid resolution. Use the pinned enum options.")
|
||||
if duration not in DURATION_OPTIONS:
|
||||
raise RuntimeError("Invalid duration. Use the pinned enum options.")
|
||||
|
||||
|
||||
def _validate_image_input(images: torch.Tensor | None) -> torch.Tensor:
|
||||
if images is None:
|
||||
raise RuntimeError("images input is required.")
|
||||
if not isinstance(images, torch.Tensor):
|
||||
raise RuntimeError("images input must be a tensor batch.")
|
||||
if images.dim() != 4 or images.shape[-1] != 3:
|
||||
raise RuntimeError("images input must have shape [B, H, W, 3].")
|
||||
if images.shape[0] < 1:
|
||||
raise RuntimeError("images input batch is empty.")
|
||||
return images
|
||||
|
||||
|
||||
def _create_seedance_task(api_key: str, payload: dict[str, Any]) -> tuple[str, str]:
|
||||
try:
|
||||
response = requests.post(
|
||||
CREATE_TASK_URL,
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
json=payload,
|
||||
timeout=30,
|
||||
)
|
||||
except requests.RequestException as exc:
|
||||
raise RuntimeError(f"Failed to call createTask endpoint: {exc}") from exc
|
||||
|
||||
if response.status_code == 429 or response.status_code >= 500:
|
||||
raise TransientKieError(
|
||||
f"createTask 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("createTask endpoint did not return valid JSON.") from exc
|
||||
|
||||
if payload_json.get("code") != 200:
|
||||
message = payload_json.get("message") or payload_json.get("msg")
|
||||
raise RuntimeError(f"createTask endpoint returned error code {payload_json.get('code')}: {message}")
|
||||
|
||||
task_id = (payload_json.get("data") or {}).get("taskId")
|
||||
if not task_id:
|
||||
raise RuntimeError("createTask endpoint did not return a taskId.")
|
||||
|
||||
return task_id, response.text
|
||||
|
||||
|
||||
def run_seedancev1pro_fast_i2v_video(
|
||||
prompt: str,
|
||||
images: torch.Tensor,
|
||||
resolution: str,
|
||||
duration: str,
|
||||
poll_interval_s: float,
|
||||
timeout_s: int,
|
||||
log: bool,
|
||||
) -> Path:
|
||||
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
|
||||
_validate_options(resolution, duration)
|
||||
images = _validate_image_input(images)
|
||||
|
||||
api_key = _load_api_key()
|
||||
|
||||
if images.shape[0] > 1:
|
||||
_log(log, f"More than 1 image provided ({images.shape[0]}); only the first will be used.")
|
||||
|
||||
_log(log, "Uploading source image for Seedance I2V...")
|
||||
png_bytes = _image_tensor_to_png_bytes(images[0])
|
||||
image_url = _upload_image(api_key, png_bytes)
|
||||
_log(log, f"Image upload success: {_truncate_url(image_url)}")
|
||||
|
||||
payload = {
|
||||
"model": MODEL_NAME,
|
||||
"input": {
|
||||
"prompt": prompt,
|
||||
"image_url": image_url,
|
||||
"resolution": resolution,
|
||||
"duration": duration,
|
||||
},
|
||||
}
|
||||
|
||||
_log(log, "Creating Seedance V1 Pro Fast I2V task...")
|
||||
start_time = time.time()
|
||||
task_id, create_response_text = _create_seedance_task(api_key, payload)
|
||||
_log(log, f"createTask response (elapsed={time.time() - start_time:.1f}s): {create_response_text}")
|
||||
_log(log, f"Task created with ID {task_id}. Polling for completion...")
|
||||
|
||||
record_data = _poll_task_until_complete(
|
||||
api_key,
|
||||
task_id,
|
||||
poll_interval_s,
|
||||
timeout_s,
|
||||
log,
|
||||
start_time,
|
||||
)
|
||||
|
||||
result_urls = _extract_result_urls(record_data)
|
||||
video_url = result_urls[0]
|
||||
_log(log, f"Downloading video result from {video_url}...")
|
||||
|
||||
video_bytes = _download_video(video_url)
|
||||
video_path = _write_video_to_temp_mp4(video_bytes)
|
||||
_log(log, f"Video saved to {video_path}")
|
||||
|
||||
_log_remaining_credits(log, record_data, api_key, _log)
|
||||
return video_path
|
||||
|
||||
|
||||
def run_seedancev1pro_fast_i2v(
|
||||
prompt: str,
|
||||
images: torch.Tensor,
|
||||
resolution: str,
|
||||
duration: str,
|
||||
poll_interval_s: float,
|
||||
timeout_s: int,
|
||||
log: bool,
|
||||
) -> Path:
|
||||
"""Backward-compatible alias."""
|
||||
return run_seedancev1pro_fast_i2v_video(
|
||||
prompt,
|
||||
images,
|
||||
resolution,
|
||||
duration,
|
||||
poll_interval_s,
|
||||
timeout_s,
|
||||
log,
|
||||
)
|
||||
|
||||
|
||||
class KIE_SeedanceV1Pro_Fast_I2V:
|
||||
HELP = """
|
||||
KIE Seedance V1 Pro Fast (Image-to-Video)
|
||||
|
||||
Transform a single image plus prompt into an mp4 animation using ByteDance's fast Seedance V1 Pro pipeline.
|
||||
|
||||
Inputs:
|
||||
- prompt (STRING)
|
||||
- images (IMAGE tensor, first frame used)
|
||||
- resolution: 720p or 1080p
|
||||
- duration: 5s or 10s
|
||||
- poll_interval_s / timeout_s / log
|
||||
|
||||
Outputs:
|
||||
- VIDEO: Path to a temporary .mp4 file.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"multiline": True}),
|
||||
"images": ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"resolution": ("COMBO", {"options": RESOLUTION_OPTIONS, "default": "720p"}),
|
||||
"duration": ("COMBO", {"options": DURATION_OPTIONS, "default": "5"}),
|
||||
"log": ("BOOLEAN", {"default": True}),
|
||||
"poll_interval_s": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 60.0, "step": 0.5}),
|
||||
"timeout_s": ("INT", {"default": 600, "min": 1, "max": 3600, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("VIDEO",)
|
||||
RETURN_NAMES = ("video",)
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "kie/api"
|
||||
|
||||
def generate(
|
||||
self,
|
||||
prompt: str,
|
||||
images: torch.Tensor,
|
||||
resolution: str = "720p",
|
||||
duration: str = "5",
|
||||
log: bool = True,
|
||||
poll_interval_s: float = 1.0,
|
||||
timeout_s: int = 600,
|
||||
):
|
||||
video_path = run_seedancev1pro_fast_i2v_video(
|
||||
prompt=prompt,
|
||||
images=images,
|
||||
resolution=resolution,
|
||||
duration=duration,
|
||||
poll_interval_s=poll_interval_s,
|
||||
timeout_s=timeout_s,
|
||||
log=log,
|
||||
)
|
||||
return (str(video_path),)
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Shared helpers for handling video results from KIE models."""
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from .http import requests
|
||||
|
||||
|
||||
def _download_video(url: str) -> bytes:
|
||||
"""Download video bytes from a result URL."""
|
||||
try:
|
||||
response = requests.get(url, timeout=180)
|
||||
except requests.RequestException as exc:
|
||||
raise RuntimeError(f"Failed to download result video: {exc}") from exc
|
||||
|
||||
if response.status_code != 200:
|
||||
raise RuntimeError(f"Failed to download result video (status code {response.status_code}).")
|
||||
|
||||
return response.content
|
||||
|
||||
|
||||
def _write_video_to_temp_mp4(video_bytes: bytes) -> Path:
|
||||
"""Persist video bytes to a temporary .mp4 file and return its path."""
|
||||
try:
|
||||
temp_file = NamedTemporaryFile(delete=False, suffix=".mp4")
|
||||
with temp_file:
|
||||
temp_file.write(video_bytes)
|
||||
return Path(temp_file.name)
|
||||
except OSError as exc:
|
||||
raise RuntimeError("Failed to write temporary video file.") from exc
|
||||
@@ -19,6 +19,7 @@ from .kie_api.seedream45_edit import (
|
||||
QUALITY_OPTIONS as SEEDREAM_EDIT_QUALITY_OPTIONS,
|
||||
run_seedream45_edit,
|
||||
)
|
||||
from .kie_api.seedancev1pro_fast_i2v import KIE_SeedanceV1Pro_Fast_I2V
|
||||
|
||||
|
||||
class KIE_GetRemainingCredits:
|
||||
@@ -243,10 +244,12 @@ NODE_CLASS_MAPPINGS = {
|
||||
"KIE_NanoBananaPro_Image": KIE_NanoBananaPro_Image,
|
||||
"KIE_Seedream45_TextToImage": KIE_Seedream45_TextToImage,
|
||||
"KIE_Seedream45_Edit": KIE_Seedream45_Edit,
|
||||
"KIE_SeedanceV1Pro_Fast_I2V": KIE_SeedanceV1Pro_Fast_I2V,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"KIE_GetRemainingCredits": "KIE Get Remaining Credits",
|
||||
"KIE_NanoBananaPro_Image": "KIE Nano Banana Pro (Image)",
|
||||
"KIE_Seedream45_TextToImage": "KIE Seedream 4.5 Text-To-Image",
|
||||
"KIE_Seedream45_Edit": "KIE Seedream 4.5 Edit",
|
||||
"KIE_SeedanceV1Pro_Fast_I2V": "KIE Seedance V1 Pro Fast (I2V)",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# KIE Seedance V1 Pro Fast (I2V)
|
||||
|
||||
Convert a single reference image plus prompt into a short video clip using
|
||||
ByteDance’s rapid Seedance V1 Pro image-to-video model.
|
||||
|
||||
---
|
||||
|
||||
## Inputs
|
||||
|
||||
- **Prompt**
|
||||
Describe how the source image should animate.
|
||||
|
||||
- **Images**
|
||||
ComfyUI image tensor batch. Only the first frame is uploaded.
|
||||
|
||||
- **Resolution**
|
||||
Choose 720p for faster jobs or 1080p for higher quality.
|
||||
|
||||
- **Duration**
|
||||
Output length (5s or 10s).
|
||||
|
||||
- **Poll Interval**
|
||||
How often to check the task status (seconds).
|
||||
|
||||
- **Timeout**
|
||||
Maximum time to wait before failing (seconds).
|
||||
|
||||
- **Log**
|
||||
Enable console logging for troubleshooting.
|
||||
|
||||
---
|
||||
|
||||
## Outputs
|
||||
|
||||
- **VIDEO**
|
||||
Path to a temporary `.mp4` file saved to disk. Downstream nodes can consume this file immediately.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- The reference image is uploaded temporarily to the KIE API.
|
||||
- Credits are consumed per request. Monitor remaining credits with the provided node.
|
||||
- Video rendering can take a minute or more depending on resolution and duration.
|
||||
|
||||
## Debugging
|
||||
Visit https://kie.ai/logs to monitor job progress or retrieve outputs if the node times out.
|
||||
Reference in New Issue
Block a user