diff --git a/docs/KIE_API_SEEDANCEV1Pro_FAST_I2V.md b/docs/KIE_API_SEEDANCEV1Pro_FAST_I2V.md new file mode 100644 index 0000000..4af53e5 --- /dev/null +++ b/docs/KIE_API_SEEDANCEV1Pro_FAST_I2V.md @@ -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." +} \ No newline at end of file diff --git a/kie_api/images.py b/kie_api/images.py index eb1859a..bacfb8e 100644 --- a/kie_api/images.py +++ b/kie_api/images.py @@ -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 \ No newline at end of file + 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 diff --git a/kie_api/jobs.py b/kie_api/jobs.py index 7624e19..2bf0c88 100644 --- a/kie_api/jobs.py +++ b/kie_api/jobs.py @@ -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) - diff --git a/kie_api/log.py b/kie_api/log.py new file mode 100644 index 0000000..70d7223 --- /dev/null +++ b/kie_api/log.py @@ -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}") diff --git a/kie_api/nanobanana.py b/kie_api/nanobanana.py index 6ed8f84..71e7e5a 100644 --- a/kie_api/nanobanana.py +++ b/kie_api/nanobanana.py @@ -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.") diff --git a/kie_api/results.py b/kie_api/results.py new file mode 100644 index 0000000..d6fecf4 --- /dev/null +++ b/kie_api/results.py @@ -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 diff --git a/kie_api/seedream45_edit.py b/kie_api/seedream45_edit.py index 769d79b..c4d4d3c 100644 --- a/kie_api/seedream45_edit.py +++ b/kie_api/seedream45_edit.py @@ -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 - diff --git a/kie_api/seedream45_t2i.py b/kie_api/seedream45_t2i.py index 55d52ce..db9e495 100644 --- a/kie_api/seedream45_t2i.py +++ b/kie_api/seedream45_t2i.py @@ -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() diff --git a/kie_api/validation.py b/kie_api/validation.py new file mode 100644 index 0000000..0001905 --- /dev/null +++ b/kie_api/validation.py @@ -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.")