runapi-seedance 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ """Seedance client for RunAPI."""
2
+
3
+ from runapi.core import (
4
+ AuthenticationError,
5
+ InsufficientCreditsError,
6
+ NotFoundError,
7
+ RateLimitError,
8
+ TaskFailedError,
9
+ TaskTimeoutError,
10
+ ValidationError,
11
+ )
12
+
13
+ from .client import SeedanceClient
14
+
15
+ __all__ = [
16
+ "SeedanceClient",
17
+ "AuthenticationError",
18
+ "RateLimitError",
19
+ "InsufficientCreditsError",
20
+ "NotFoundError",
21
+ "ValidationError",
22
+ "TaskFailedError",
23
+ "TaskTimeoutError",
24
+ ]
@@ -0,0 +1,27 @@
1
+ """Seedance client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from runapi.core import ClientOptions, HttpClient, resolve_api_key
8
+
9
+ from .resources.text_to_video import TextToVideo
10
+
11
+
12
+ class SeedanceClient:
13
+ """Seedance video generation client.
14
+
15
+ Example::
16
+
17
+ client = SeedanceClient(api_key="sk-...")
18
+ result = client.text_to_video.run(
19
+ model="seedance-2.0", prompt="A cat walking through a garden"
20
+ )
21
+ """
22
+
23
+ def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
24
+ resolved_api_key = resolve_api_key(api_key)
25
+ client_options = ClientOptions(api_key=resolved_api_key, **options)
26
+ http = client_options.http_client or HttpClient(client_options)
27
+ self.text_to_video = TextToVideo(http)
@@ -0,0 +1,104 @@
1
+ CONTRACT = {
2
+ "text-to-video": {
3
+ "models": ["seedance-1.5-pro", "seedance-2.0", "seedance-2.0-fast", "seedance-v1-lite", "seedance-v1-pro", "seedance-v1-pro-fast"],
4
+ "fields_by_model": {
5
+ "seedance-1.5-pro": {
6
+ "aspect_ratio": {
7
+ "enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "21:9"]
8
+ },
9
+ "duration_seconds": {
10
+ "required": True,
11
+ "min": 4,
12
+ "max": 12,
13
+ "type": "integer"
14
+ },
15
+ "output_resolution": {
16
+ "enum": ["480p", "720p", "1080p"]
17
+ },
18
+ "seed": {
19
+ "type": "integer"
20
+ }
21
+ },
22
+ "seedance-2.0": {
23
+ "aspect_ratio": {
24
+ "enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "21:9", "auto"]
25
+ },
26
+ "duration_seconds": {
27
+ "min": 4,
28
+ "max": 15,
29
+ "type": "integer"
30
+ },
31
+ "output_resolution": {
32
+ "enum": ["480p", "720p", "1080p"]
33
+ },
34
+ "seed": {
35
+ "type": "integer"
36
+ }
37
+ },
38
+ "seedance-2.0-fast": {
39
+ "aspect_ratio": {
40
+ "enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "21:9", "auto"]
41
+ },
42
+ "duration_seconds": {
43
+ "min": 4,
44
+ "max": 15,
45
+ "type": "integer"
46
+ },
47
+ "output_resolution": {
48
+ "enum": ["480p", "720p"]
49
+ },
50
+ "seed": {
51
+ "type": "integer"
52
+ }
53
+ },
54
+ "seedance-v1-lite": {
55
+ "aspect_ratio": {
56
+ "enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "9:21"]
57
+ },
58
+ "duration_seconds": {
59
+ "enum": [5, 10],
60
+ "required": True,
61
+ "type": "integer"
62
+ },
63
+ "output_resolution": {
64
+ "enum": ["480p", "720p", "1080p"]
65
+ },
66
+ "seed": {
67
+ "type": "integer"
68
+ }
69
+ },
70
+ "seedance-v1-pro": {
71
+ "aspect_ratio": {
72
+ "enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "21:9"]
73
+ },
74
+ "duration_seconds": {
75
+ "enum": [5, 10],
76
+ "required": True,
77
+ "type": "integer"
78
+ },
79
+ "output_resolution": {
80
+ "enum": ["480p", "720p", "1080p"]
81
+ },
82
+ "seed": {
83
+ "type": "integer"
84
+ }
85
+ },
86
+ "seedance-v1-pro-fast": {
87
+ "duration_seconds": {
88
+ "enum": [5, 10],
89
+ "required": True,
90
+ "type": "integer"
91
+ },
92
+ "first_frame_image_url": {
93
+ "required": True
94
+ },
95
+ "output_resolution": {
96
+ "enum": ["720p", "1080p"]
97
+ },
98
+ "seed": {
99
+ "type": "integer"
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ from .text_to_video import TextToVideo
2
+
3
+ __all__ = ["TextToVideo"]
@@ -0,0 +1,170 @@
1
+ """Seedance text-to-video resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict
6
+
7
+ from runapi.core import Resource, ValidationError
8
+
9
+ from ..contract_gen import CONTRACT
10
+ from ..types import (
11
+ FRAME_FIELDS,
12
+ PROMPT_MAX_LENGTH_1_5,
13
+ PROMPT_MAX_LENGTH_2,
14
+ PROMPT_MAX_LENGTH_V1,
15
+ PROMPT_MIN_LENGTH,
16
+ REFERENCE_FIELDS,
17
+ SEED_RANGE,
18
+ V1_MODELS,
19
+ CompletedTextToVideoResponse,
20
+ TextToVideoResponse,
21
+ )
22
+
23
+
24
+ class TextToVideo(Resource):
25
+ """Generate videos from text prompts, images, or reference media."""
26
+
27
+ ENDPOINT = "/api/v1/seedance/text_to_video"
28
+
29
+ RESPONSE_CLASS = TextToVideoResponse
30
+ COMPLETED_RESPONSE_CLASS = CompletedTextToVideoResponse
31
+
32
+ def run(self, **params: Any) -> Any:
33
+ """Generate a video and poll until it completes.
34
+
35
+ Args:
36
+ **params: video generation parameters (model, ...).
37
+
38
+ Returns:
39
+ The completed (narrowed) video generation response.
40
+ """
41
+ task = self.create(**params)
42
+ return self._poll_until_complete(lambda: self.get(task.id))
43
+
44
+ def create(self, **params: Any) -> Any:
45
+ """Create a video generation task and return immediately with an id.
46
+
47
+ Args:
48
+ **params: video generation parameters (model, ...).
49
+
50
+ Returns:
51
+ The task creation result with an id.
52
+ """
53
+ compacted = self._compact_params(params)
54
+ self._validate_params(compacted)
55
+ return self._request("post", self.ENDPOINT, body=compacted)
56
+
57
+ def get(self, id: str) -> Any:
58
+ """Fetch the current status of a video generation task.
59
+
60
+ Args:
61
+ id: The task id returned by ``create``.
62
+
63
+ Returns:
64
+ The current task status.
65
+ """
66
+ return self._request("get", f"{self.ENDPOINT}/{id}")
67
+
68
+ def _validate_params(self, params: Dict[str, Any]) -> None:
69
+ prompt = params.get("prompt")
70
+ if not prompt:
71
+ raise ValidationError("prompt is required")
72
+
73
+ self._validate_contract(CONTRACT["text-to-video"], params)
74
+
75
+ model = params.get("model")
76
+ if model == "seedance-1.5-pro":
77
+ max_prompt = PROMPT_MAX_LENGTH_1_5
78
+ elif model in V1_MODELS:
79
+ max_prompt = PROMPT_MAX_LENGTH_V1
80
+ else:
81
+ max_prompt = PROMPT_MAX_LENGTH_2
82
+ if not (PROMPT_MIN_LENGTH <= len(prompt) <= max_prompt):
83
+ raise ValidationError(
84
+ f"prompt length must be between {PROMPT_MIN_LENGTH} and {max_prompt} characters"
85
+ )
86
+
87
+ if model == "seedance-1.5-pro":
88
+ self._validate_1_5_pro(params)
89
+ elif model in V1_MODELS:
90
+ self._validate_v1(params)
91
+ else:
92
+ self._validate_2(params)
93
+
94
+ def _validate_v1(self, params: Dict[str, Any]) -> None:
95
+ model = params.get("model")
96
+ has_image = self._field_present(params, "first_frame_image_url")
97
+
98
+ if has_image and self._field_present(params, "aspect_ratio"):
99
+ raise ValidationError(
100
+ "aspect_ratio is not accepted in image-to-video mode; it is derived from the image"
101
+ )
102
+
103
+ if self._field_present(params, "last_frame_image_url") and not (
104
+ model == "seedance-v1-lite" and has_image
105
+ ):
106
+ raise ValidationError(
107
+ "last_frame_image_url is only supported by seedance-v1-lite in image-to-video mode"
108
+ )
109
+
110
+ unsupported = [
111
+ "source_image_urls",
112
+ "reference_image_urls",
113
+ "reference_video_urls",
114
+ "reference_audio_urls",
115
+ "web_search",
116
+ "generate_audio",
117
+ ]
118
+ self._reject_unsupported(params, unsupported, model)
119
+
120
+ if model == "seedance-v1-pro-fast":
121
+ self._reject_unsupported(params, ["lock_camera", "seed"], model)
122
+
123
+ seed = params.get("seed")
124
+ if seed is not None:
125
+ if not (isinstance(seed, int) and not isinstance(seed, bool) and seed in SEED_RANGE):
126
+ raise ValidationError(
127
+ f"seed must be an integer between {SEED_RANGE.start} and {SEED_RANGE.stop - 1}"
128
+ )
129
+
130
+ def _validate_1_5_pro(self, params: Dict[str, Any]) -> None:
131
+ value = params.get("source_image_urls")
132
+ if isinstance(value, list) and len(value) > 2:
133
+ raise ValidationError("source_image_urls accepts at most 2 images for seedance-1.5-pro")
134
+
135
+ unsupported = [
136
+ "first_frame_image_url",
137
+ "last_frame_image_url",
138
+ "reference_image_urls",
139
+ "reference_video_urls",
140
+ "reference_audio_urls",
141
+ "web_search",
142
+ ]
143
+ self._reject_unsupported(params, unsupported, "seedance-1.5-pro")
144
+
145
+ def _validate_2(self, params: Dict[str, Any]) -> None:
146
+ unsupported = ["source_image_urls", "lock_camera"]
147
+ self._reject_unsupported(params, unsupported, params.get("model"))
148
+
149
+ self._validate_mode_conflicts(params)
150
+
151
+ def _validate_mode_conflicts(self, params: Dict[str, Any]) -> None:
152
+ has_frame = any(self._field_present(params, f) for f in FRAME_FIELDS)
153
+ has_reference = any(self._field_present(params, f) for f in REFERENCE_FIELDS)
154
+
155
+ if has_frame and has_reference:
156
+ raise ValidationError("Cannot use frame mode and reference mode at the same time")
157
+
158
+ def _reject_unsupported(self, params: Dict[str, Any], fields: Any, model: Any) -> None:
159
+ for field in fields:
160
+ if self._field_present(params, field):
161
+ raise ValidationError(f"{field} is not supported for {model}")
162
+
163
+ @staticmethod
164
+ def _field_present(params: Dict[str, Any], key: str) -> bool:
165
+ value = params.get(key)
166
+ if value is None:
167
+ return False
168
+ if hasattr(value, "__len__"):
169
+ return len(value) > 0
170
+ return True
@@ -0,0 +1,42 @@
1
+ """Seedance model lists, enums, and response models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from runapi.core import BaseModel, TaskResponse, optional, required
6
+
7
+ V1_MODELS = ["seedance-v1-lite", "seedance-v1-pro", "seedance-v1-pro-fast"]
8
+
9
+ SEED_RANGE = range(-1, 2_147_483_648)
10
+
11
+ PROMPT_MIN_LENGTH = 3
12
+ PROMPT_MAX_LENGTH_1_5 = 2500
13
+ PROMPT_MAX_LENGTH_2 = 20000
14
+ PROMPT_MAX_LENGTH_V1 = 10000
15
+
16
+ FRAME_FIELDS = ["first_frame_image_url", "last_frame_image_url"]
17
+ REFERENCE_FIELDS = ["reference_image_urls", "reference_video_urls", "reference_audio_urls"]
18
+
19
+
20
+ class Video(BaseModel):
21
+ url = optional(str)
22
+
23
+
24
+ class AsyncTaskResponse(TaskResponse):
25
+ """Seedance async task status response."""
26
+
27
+ id = required(str)
28
+ status = optional(str, enum=lambda: TaskResponse.Status.ALL)
29
+
30
+
31
+ class TextToVideoResponse(AsyncTaskResponse):
32
+ """Seedance video generation task status response."""
33
+
34
+ videos = optional([lambda: Video])
35
+ last_frame_image_url = optional(str)
36
+ error = optional(str)
37
+
38
+
39
+ class CompletedTextToVideoResponse(TextToVideoResponse):
40
+ """Narrowed response from ``run()`` once polling observes completion."""
41
+
42
+ videos = required([lambda: Video])
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: runapi-seedance
3
+ Version: 0.1.0
4
+ Summary: Seedance video generation client for RunAPI
5
+ Project-URL: Homepage, https://runapi.ai/models/seedance
6
+ Project-URL: Documentation, https://runapi.ai/docs#sdk-seedance
7
+ Author-email: RunAPI <contact@runapi.ai>
8
+ License-Expression: Apache-2.0
9
+ Keywords: ai,runapi,sdk,seedance,text-to-video,video
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: runapi-core
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Seedance Python SDK for RunAPI
15
+
16
+ The Seedance Python SDK is the language-specific package for Seedance on RunAPI.
17
+ Use it for text-to-video, image-to-video, and reference-media flows when your
18
+ application needs JSON request bodies, task status lookup, and consistent RunAPI
19
+ errors in Python.
20
+
21
+ For model details, use https://runapi.ai/models/seedance; for API reference, use
22
+ https://runapi.ai/docs#seedance; for SDK docs, use https://runapi.ai/docs#sdk-seedance.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install runapi-seedance
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```python
33
+ from runapi.seedance import SeedanceClient
34
+
35
+ client = SeedanceClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
36
+
37
+ task = client.text_to_video.create(
38
+ model="seedance-2.0",
39
+ prompt="A cat walking through a garden",
40
+ duration_seconds=8,
41
+ )
42
+ status = client.text_to_video.get(task.id)
43
+ ```
44
+
45
+ Use `create` to submit a task and return quickly, `get` to fetch the latest task
46
+ state, and `run` to create and poll until completion:
47
+
48
+ ```python
49
+ result = client.text_to_video.run(
50
+ model="seedance-2.0",
51
+ prompt="A cinematic drone shot over snowy mountains",
52
+ duration_seconds=8,
53
+ )
54
+ print(result.videos[0].url)
55
+ ```
56
+
57
+ In web request handlers, prefer `create` plus webhook or later `get` polling so a
58
+ worker is not held open.
59
+
60
+ RunAPI-generated file URLs are temporary. Download and store generated videos in
61
+ your own durable storage within 7 days; do not treat returned URLs as long-term
62
+ assets.
63
+
64
+ ## Language notes
65
+
66
+ Pass parameters as keyword arguments and catch the `runapi.seedance` error
67
+ classes when building video jobs or scripts. The available resource is
68
+ `text_to_video`. Keep `RUNAPI_API_KEY` in the environment or your secret
69
+ manager; never commit API keys or callback secrets.
70
+
71
+ ## Links
72
+
73
+ - Model page: https://runapi.ai/models/seedance
74
+ - SDK docs: https://runapi.ai/docs#sdk-seedance
75
+ - Product docs: https://runapi.ai/docs#seedance
76
+ - Pricing and rate limits: https://runapi.ai/models/seedance
77
+ - Full catalog: https://runapi.ai/models
78
+
79
+ ## License
80
+
81
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,10 @@
1
+ runapi/seedance/__init__.py,sha256=0bh7ZtuByL32-02Gx0KANL1mS9q_cKDw_rxcgCithmM,466
2
+ runapi/seedance/client.py,sha256=vewixJ3DDyl2nP6Sxvk3KRXJyvs2cbalFrS7NcxpH70,804
3
+ runapi/seedance/contract_gen.py,sha256=E-OpP82IuFqgEbhpfX_5_volT3nhcdx-m3n9uRB0Yb4,3429
4
+ runapi/seedance/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ runapi/seedance/types.py,sha256=_-FDrC-ZP7_SbuEJM9AFWJRGdFvRdJvxmnz0BnIK3Dw,1160
6
+ runapi/seedance/resources/__init__.py,sha256=1T9dH_XTn-pM1F5Kt2H-yZiLOWM8_sFTJXUXEmgyjkU,66
7
+ runapi/seedance/resources/text_to_video.py,sha256=CSpfWMbkFOsSoTP4AoB7zKuIIhccfglJu_owq9wP8-E,5866
8
+ runapi_seedance-0.1.0.dist-info/METADATA,sha256=7y9npS4lJ_sJHR3uk2p8Mk6S4y22jg905ZelPp4JImw,2503
9
+ runapi_seedance-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
10
+ runapi_seedance-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any