refactor
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
POST
|
||||
/api/v1/jobs/createTask
|
||||
Create Task
|
||||
Create a new generation task
|
||||
|
||||
Request Parameters
|
||||
The API accepts a JSON payload with the following structure:
|
||||
|
||||
Request Body Structure
|
||||
{
|
||||
"model": "string",
|
||||
"callBackUrl": "string (optional)",
|
||||
"input": {
|
||||
// Input parameters based on form configuration
|
||||
}
|
||||
}
|
||||
Root Level Parameters
|
||||
model
|
||||
Required
|
||||
string
|
||||
The model name to use for generation
|
||||
|
||||
Example:
|
||||
|
||||
"bytedance/v1-pro-fast-image-to-video"
|
||||
callBackUrl
|
||||
Optional
|
||||
string
|
||||
Callback URL for task completion notifications. Optional parameter. If provided, the system will send POST requests to this URL when the task completes (success or failure). If not provided, no callback notifications will be sent.
|
||||
|
||||
Example:
|
||||
|
||||
"https://your-domain.com/api/callback"
|
||||
Input Object Parameters
|
||||
The input object contains the following parameters based on the form configuration:
|
||||
|
||||
input.prompt
|
||||
Required
|
||||
string
|
||||
The text prompt used to generate the video
|
||||
|
||||
Max length: 10000 characters
|
||||
Example:
|
||||
|
||||
"A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw"
|
||||
input.image_url
|
||||
Required
|
||||
string(URL)
|
||||
The URL of the image used to generate video
|
||||
|
||||
File URL after upload, not file content; Accepted types: image/jpeg, image/png, image/webp; Max size: 10.0MB
|
||||
Example:
|
||||
|
||||
"https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp"
|
||||
input.resolution
|
||||
Optional
|
||||
string
|
||||
Video resolution - 480p for faster generation, 720p for balance, 1080p for higher quality
|
||||
|
||||
Available options:
|
||||
|
||||
720p
|
||||
-
|
||||
720p
|
||||
1080p
|
||||
-
|
||||
1080p
|
||||
Example:
|
||||
|
||||
"720p"
|
||||
input.duration
|
||||
Optional
|
||||
string
|
||||
Duration of the video in seconds
|
||||
|
||||
Available options:
|
||||
|
||||
5
|
||||
-
|
||||
5s
|
||||
10
|
||||
-
|
||||
10s
|
||||
Example:
|
||||
|
||||
"5"
|
||||
Request Example
|
||||
|
||||
cURL
|
||||
|
||||
JavaScript
|
||||
|
||||
Python
|
||||
import requests
|
||||
import json
|
||||
|
||||
url = "https://api.kie.ai/api/v1/jobs/createTask"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer YOUR_API_KEY"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": "bytedance/v1-pro-fast-image-to-video",
|
||||
"callBackUrl": "https://your-domain.com/api/callback",
|
||||
"input": {
|
||||
"prompt": "A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw",
|
||||
"image_url": "https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp",
|
||||
"resolution": "720p",
|
||||
"duration": "5"
|
||||
}
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
||||
result = response.json()
|
||||
print(result)
|
||||
Response Example
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"taskId": "task_12345678"
|
||||
}
|
||||
}
|
||||
Response Fields
|
||||
code
|
||||
Status code, 200 for success, others for failure
|
||||
message
|
||||
Response message, error description when failed
|
||||
data.taskId
|
||||
Task ID for querying task status
|
||||
Callback Notifications
|
||||
When you provide the callBackUrl parameter when creating a task, the system will send POST requests to the specified URL upon task completion (success or failure).
|
||||
|
||||
Success Callback Example
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"completeTime": 1755599644000,
|
||||
"consumeCredits": 100,
|
||||
"costTime": 8,
|
||||
"createTime": 1755599634000,
|
||||
"model": "bytedance/v1-pro-fast-image-to-video",
|
||||
"param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"bytedance/v1-pro-fast-image-to-video\",\"input\":{\"prompt\":\"A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw\",\"image_url\":\"https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp\",\"resolution\":\"720p\",\"duration\":\"5\"}}",
|
||||
"remainedCredits": 2510330,
|
||||
"resultJson": "{\"resultUrls\":[\"https://example.com/generated-image.jpg\"]}",
|
||||
"state": "success",
|
||||
"taskId": "e989621f54392584b05867f87b160672",
|
||||
"updateTime": 1755599644000
|
||||
},
|
||||
"msg": "Playground task completed successfully."
|
||||
}
|
||||
Failure Callback Example
|
||||
{
|
||||
"code": 501,
|
||||
"data": {
|
||||
"completeTime": 1755597081000,
|
||||
"consumeCredits": 0,
|
||||
"costTime": 0,
|
||||
"createTime": 1755596341000,
|
||||
"failCode": "500",
|
||||
"failMsg": "Internal server error",
|
||||
"model": "bytedance/v1-pro-fast-image-to-video",
|
||||
"param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"bytedance/v1-pro-fast-image-to-video\",\"input\":{\"prompt\":\"A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw\",\"image_url\":\"https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp\",\"resolution\":\"720p\",\"duration\":\"5\"}}",
|
||||
"remainedCredits": 2510430,
|
||||
"state": "fail",
|
||||
"taskId": "bd3a37c523149e4adf45a3ddb5faf1a8",
|
||||
"updateTime": 1755597097000
|
||||
},
|
||||
"msg": "Playground task failed."
|
||||
}
|
||||
+16
-1
@@ -4,6 +4,8 @@ import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from .http import requests
|
||||
|
||||
|
||||
def _image_bytes_to_tensor(image_bytes: bytes) -> torch.Tensor:
|
||||
"""Convert image bytes into a normalized torch tensor.
|
||||
@@ -24,4 +26,17 @@ def _image_bytes_to_tensor(image_bytes: bytes) -> torch.Tensor:
|
||||
tensor = tensor.float() / 255.0
|
||||
return tensor.unsqueeze(0)
|
||||
except Exception as exc:
|
||||
raise RuntimeError("Failed to decode result image.") from exc
|
||||
raise RuntimeError("Failed to decode result image.") from exc
|
||||
|
||||
|
||||
def _download_image(url: str) -> bytes:
|
||||
"""Download a result image and return its raw bytes."""
|
||||
try:
|
||||
response = requests.get(url, timeout=120)
|
||||
except requests.RequestException as exc:
|
||||
raise RuntimeError(f"Failed to download result image: {exc}") from exc
|
||||
|
||||
if response.status_code != 200:
|
||||
raise RuntimeError(f"Failed to download result image (status code {response.status_code}).")
|
||||
|
||||
return response.content
|
||||
|
||||
+1
-6
@@ -15,16 +15,12 @@ import time
|
||||
from typing import Any
|
||||
|
||||
from .http import TransientKieError, requests
|
||||
from .log import _log
|
||||
|
||||
|
||||
RECORD_INFO_URL = "https://api.kie.ai/api/v1/jobs/recordInfo"
|
||||
|
||||
|
||||
def _log(enabled: bool, msg: str) -> None:
|
||||
if enabled:
|
||||
print(f"[KIE] {msg}")
|
||||
|
||||
|
||||
def _fetch_task_record(api_key: str, task_id: str) -> tuple[dict[str, Any], str, Any]:
|
||||
"""Fetch the current record for a task.
|
||||
|
||||
@@ -163,4 +159,3 @@ def _poll_task_until_complete(
|
||||
if should_log:
|
||||
_log(log, f"Polling again in {interval} seconds...")
|
||||
time.sleep(interval)
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"""Shared logging helpers for KIE API modules."""
|
||||
|
||||
|
||||
def _log(enabled: bool, msg: str) -> None:
|
||||
"""Log a message to stdout when verbose logging is enabled."""
|
||||
if enabled:
|
||||
print(f"[KIE] {msg}")
|
||||
+6
-90
@@ -12,20 +12,19 @@ This file is intentionally model-specific and not generic.
|
||||
|
||||
import json
|
||||
import time
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
from PIL import Image
|
||||
|
||||
from .auth import _load_api_key
|
||||
from .credits import _log_remaining_credits
|
||||
from .http import TransientKieError, requests
|
||||
from .jobs import _fetch_task_record, _poll_task_until_complete, _should_retry_fail
|
||||
from .log import _log
|
||||
from .results import _extract_result_urls
|
||||
from .upload import _image_tensor_to_png_bytes, _truncate_url, _upload_image
|
||||
from .images import _image_bytes_to_tensor
|
||||
|
||||
|
||||
from .images import _download_image, _image_bytes_to_tensor
|
||||
from .validation import _validate_prompt
|
||||
|
||||
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
|
||||
RECORD_INFO_URL = "https://api.kie.ai/api/v1/jobs/recordInfo"
|
||||
@@ -33,52 +32,7 @@ MODEL_NAME = "nano-banana-pro"
|
||||
ASPECT_RATIO_OPTIONS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "auto"]
|
||||
RESOLUTION_OPTIONS = ["1K", "2K", "4K"]
|
||||
OUTPUT_FORMAT_OPTIONS = ["png", "jpg"]
|
||||
|
||||
# -----------------------------
|
||||
# Constants / enums
|
||||
# -----------------------------
|
||||
|
||||
# -----------------------------
|
||||
# Validation
|
||||
# -----------------------------
|
||||
|
||||
# -----------------------------
|
||||
# Request building (Nano Banana specific)
|
||||
# -----------------------------
|
||||
|
||||
# -----------------------------
|
||||
# Task lifecycle (submit + poll)
|
||||
# -----------------------------
|
||||
|
||||
# -----------------------------
|
||||
# Result handling (parse + download + decode)
|
||||
# -----------------------------
|
||||
|
||||
|
||||
def _log(enabled: bool, msg: str) -> None:
|
||||
"""Log a message to stdout when verbose logging is enabled.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
Raises:
|
||||
None.
|
||||
"""
|
||||
if enabled:
|
||||
print(f"[KIE] {msg}")
|
||||
|
||||
|
||||
def _validate_prompt(prompt: str) -> None:
|
||||
"""Validate presence and length of the generation prompt.
|
||||
|
||||
Returns:
|
||||
None.
|
||||
Raises:
|
||||
RuntimeError: If the prompt is empty or exceeds 10000 characters.
|
||||
"""
|
||||
if not prompt:
|
||||
raise RuntimeError("Prompt is required.")
|
||||
if len(prompt) > 10000:
|
||||
raise RuntimeError("Prompt exceeds the maximum length of 10000 characters.")
|
||||
PROMPT_MAX_LENGTH = 10000
|
||||
|
||||
|
||||
def _create_nano_banana_task(api_key: str, payload: dict[str, Any]) -> tuple[str, str]:
|
||||
@@ -121,8 +75,6 @@ def _create_nano_banana_task(api_key: str, payload: dict[str, Any]) -> tuple[str
|
||||
return task_id, response.text
|
||||
|
||||
|
||||
|
||||
|
||||
def _create_nanobanana_task(api_key: str, payload: dict[str, Any]) -> tuple[str, str]:
|
||||
"""Backward-compatible alias for node usage."""
|
||||
return _create_nano_banana_task(api_key, payload)
|
||||
@@ -147,47 +99,11 @@ def _poll_nanobanana_until_complete(
|
||||
return _poll_task_until_complete(api_key, task_id, poll_interval_s, timeout_s, log, start_time)
|
||||
|
||||
|
||||
def _extract_result_urls(record_data: dict[str, Any]) -> list[str]:
|
||||
result_json = record_data.get("resultJson")
|
||||
if not result_json:
|
||||
raise RuntimeError("Task completed without resultJson.")
|
||||
|
||||
try:
|
||||
parsed = json.loads(result_json)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise RuntimeError("resultJson is not valid JSON.") from exc
|
||||
|
||||
result_urls = parsed.get("resultUrls")
|
||||
if not result_urls or not isinstance(result_urls, list):
|
||||
raise RuntimeError("resultJson does not contain resultUrls.")
|
||||
|
||||
return result_urls
|
||||
|
||||
|
||||
def _extract_nanobanana_result_urls(record_data: dict[str, Any]) -> list[str]:
|
||||
"""Backward-compatible alias for node usage."""
|
||||
return _extract_result_urls(record_data)
|
||||
|
||||
|
||||
def _download_image(url: str) -> bytes:
|
||||
"""Download a result image and return its raw bytes.
|
||||
|
||||
Returns:
|
||||
The downloaded image content.
|
||||
Raises:
|
||||
RuntimeError: If the download fails or returns a non-200 status.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(url, timeout=120)
|
||||
except requests.RequestException as exc:
|
||||
raise RuntimeError(f"Failed to download result image: {exc}") from exc
|
||||
|
||||
if response.status_code != 200:
|
||||
raise RuntimeError(f"Failed to download result image (status code {response.status_code}).")
|
||||
|
||||
return response.content
|
||||
|
||||
|
||||
def _download_nanobanana_image(url: str) -> bytes:
|
||||
"""Backward-compatible alias for node usage."""
|
||||
return _download_image(url)
|
||||
@@ -217,7 +133,7 @@ def run_nanobanana_image_job(
|
||||
RuntimeError: For validation errors, non-retryable API failures, timeouts, or decoding failures.
|
||||
TransientKieError: For retryable API/task failures (used to trigger retries when enabled).
|
||||
"""
|
||||
_validate_prompt(prompt)
|
||||
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
|
||||
|
||||
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
|
||||
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
"""Shared helpers for parsing KIE job results."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _extract_result_urls(record_data: dict[str, Any]) -> list[str]:
|
||||
"""Extract the resultUrls list from a recordInfo payload."""
|
||||
result_json = record_data.get("resultJson")
|
||||
if not result_json:
|
||||
raise RuntimeError("Task completed without resultJson.")
|
||||
|
||||
try:
|
||||
parsed = json.loads(result_json)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise RuntimeError("resultJson is not valid JSON.") from exc
|
||||
|
||||
result_urls = parsed.get("resultUrls")
|
||||
if not result_urls or not isinstance(result_urls, list):
|
||||
raise RuntimeError("resultJson does not contain resultUrls.")
|
||||
|
||||
return result_urls
|
||||
@@ -20,10 +20,12 @@ import torch
|
||||
from .auth import _load_api_key
|
||||
from .credits import _log_remaining_credits
|
||||
from .http import TransientKieError, requests
|
||||
from .images import _image_bytes_to_tensor
|
||||
from .images import _download_image, _image_bytes_to_tensor
|
||||
from .jobs import _poll_task_until_complete
|
||||
from .nanobanana import _download_image, _extract_result_urls, _log
|
||||
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
|
||||
|
||||
|
||||
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
|
||||
@@ -33,13 +35,6 @@ QUALITY_OPTIONS = ["basic", "high"]
|
||||
PROMPT_MAX_LENGTH = 3000
|
||||
|
||||
|
||||
def _validate_prompt(prompt: str) -> None:
|
||||
if not prompt:
|
||||
raise RuntimeError("Prompt is required.")
|
||||
if len(prompt) > PROMPT_MAX_LENGTH:
|
||||
raise RuntimeError("Prompt exceeds the maximum length of 3000 characters.")
|
||||
|
||||
|
||||
def _validate_options(aspect_ratio: str, quality: str) -> None:
|
||||
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
|
||||
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
|
||||
@@ -118,7 +113,7 @@ def run_seedream45_edit(
|
||||
RuntimeError: For validation errors or non-retryable API/task failures.
|
||||
TransientKieError: For retryable API/task failures.
|
||||
"""
|
||||
_validate_prompt(prompt)
|
||||
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
|
||||
_validate_options(aspect_ratio, quality)
|
||||
images = _validate_image_input(images)
|
||||
|
||||
@@ -167,4 +162,3 @@ def run_seedream45_edit(
|
||||
|
||||
_log_remaining_credits(log, record_data, api_key, _log)
|
||||
return image_tensor
|
||||
|
||||
|
||||
@@ -9,13 +9,11 @@ import torch
|
||||
from .auth import _load_api_key
|
||||
from .credits import _log_remaining_credits
|
||||
from .http import TransientKieError, requests
|
||||
from .images import _image_bytes_to_tensor
|
||||
from .images import _download_image, _image_bytes_to_tensor
|
||||
from .jobs import _poll_task_until_complete
|
||||
from .nanobanana import (
|
||||
_download_image,
|
||||
_extract_result_urls,
|
||||
_log,
|
||||
)
|
||||
from .log import _log
|
||||
from .results import _extract_result_urls
|
||||
from .validation import _validate_prompt
|
||||
|
||||
|
||||
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
|
||||
@@ -25,13 +23,6 @@ QUALITY_OPTIONS = ["basic", "high"]
|
||||
PROMPT_MAX_LENGTH = 3000
|
||||
|
||||
|
||||
def _validate_prompt(prompt: str) -> None:
|
||||
if not prompt:
|
||||
raise RuntimeError("Prompt is required.")
|
||||
if len(prompt) > PROMPT_MAX_LENGTH:
|
||||
raise RuntimeError("Prompt exceeds the maximum length of 3000 characters.")
|
||||
|
||||
|
||||
def _validate_options(aspect_ratio: str, quality: str) -> None:
|
||||
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
|
||||
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
|
||||
@@ -79,7 +70,7 @@ def run_seedream45_text_to_image(
|
||||
timeout_s: int,
|
||||
log: bool,
|
||||
) -> torch.Tensor:
|
||||
_validate_prompt(prompt)
|
||||
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
|
||||
_validate_options(aspect_ratio, quality)
|
||||
|
||||
api_key = _load_api_key()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Shared validation helpers for KIE API modules."""
|
||||
|
||||
|
||||
def _validate_prompt(prompt: str, *, max_length: int) -> None:
|
||||
"""Ensure prompts are present and below the specified maximum length."""
|
||||
if not prompt:
|
||||
raise RuntimeError("Prompt is required.")
|
||||
if len(prompt) > max_length:
|
||||
raise RuntimeError(f"Prompt exceeds the maximum length of {max_length} characters.")
|
||||
Reference in New Issue
Block a user