Skip to content

Commit a651208

Browse files
committed
feat: canonical model names, lucy-2.1, and deprecation warnings
Add canonical model names (lucy-2, lucy-clip, lucy-restyle-2, lucy-image-2, live-avatar, etc.) to match the updated API naming convention. Add new models lucy-2.1 (realtime + batch) and lucy-2.1-vton (realtime). Deprecated model names still work but now emit a DeprecationWarning guiding users to migrate.
1 parent d148ca6 commit a651208

23 files changed

Lines changed: 435 additions & 152 deletions

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def main():
3434
async with DecartClient(api_key=os.getenv("DECART_API_KEY")) as client:
3535
# Edit an image
3636
result = await client.process({
37-
"model": models.image("lucy-pro-i2i"),
37+
"model": models.image("lucy-image-2"),
3838
"prompt": "Apply a painterly oil-on-canvas look while preserving the composition",
3939
"data": open("input.png", "rb"),
4040
})
@@ -53,7 +53,7 @@ For video editing jobs, use the queue API to submit jobs and poll for results:
5353
async with DecartClient(api_key=os.getenv("DECART_API_KEY")) as client:
5454
# Submit and poll automatically
5555
result = await client.queue.submit_and_poll({
56-
"model": models.video("lucy-pro-v2v"),
56+
"model": models.video("lucy-clip"),
5757
"prompt": "Restyle this footage with anime shading and vibrant neon highlights",
5858
"data": open("input.mp4", "rb"),
5959
"on_status_change": lambda job: print(f"Status: {job.status}"),
@@ -72,7 +72,7 @@ Or manage the polling manually:
7272
async with DecartClient(api_key=os.getenv("DECART_API_KEY")) as client:
7373
# Submit the job
7474
job = await client.queue.submit({
75-
"model": models.video("lucy-pro-v2v"),
75+
"model": models.video("lucy-clip"),
7676
"prompt": "Add cinematic teal-and-orange grading and gentle film grain",
7777
"data": open("input.mp4", "rb"),
7878
})

decart/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ class DecartClient:
3737
3838
# Image editing (sync) - use process()
3939
image = await client.process({
40-
"model": models.image("lucy-pro-i2i"),
40+
"model": models.image("lucy-image-2"),
4141
"prompt": "Apply a painterly oil-on-canvas look while preserving the composition",
4242
"data": open("input.png", "rb"),
4343
})
4444
4545
# Video editing (async) - use queue
4646
result = await client.queue.submit_and_poll({
47-
"model": models.video("lucy-pro-v2v"),
47+
"model": models.video("lucy-clip"),
4848
"prompt": "Restyle this footage with anime shading and vibrant neon highlights",
4949
"data": open("input.mp4", "rb"),
5050
})
@@ -84,7 +84,7 @@ def queue(self) -> QueueClient:
8484
```python
8585
# Submit and poll automatically
8686
result = await client.queue.submit_and_poll({
87-
"model": models.video("lucy-pro-v2v"),
87+
"model": models.video("lucy-clip"),
8888
"prompt": "Restyle this footage with anime shading and vibrant neon highlights",
8989
"data": open("input.mp4", "rb"),
9090
})

decart/models.py

Lines changed: 177 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,75 @@
1+
import warnings
12
from typing import Literal, Optional, List, Generic, TypeVar
23
from pydantic import BaseModel, Field, ConfigDict, model_validator
34
from .errors import ModelNotFoundError
45
from .types import FileInput, MotionTrajectoryInput
56

67

7-
RealTimeModels = Literal["mirage", "mirage_v2", "lucy_v2v_720p_rt", "lucy_2_rt", "live_avatar"]
8+
RealTimeModels = Literal[
9+
# Canonical names
10+
"lucy",
11+
"lucy-2",
12+
"lucy-2.1",
13+
"lucy-2.1-vton",
14+
"lucy-restyle",
15+
"lucy-restyle-2",
16+
"live-avatar",
17+
# Deprecated names
18+
"mirage",
19+
"mirage_v2",
20+
"lucy_v2v_720p_rt",
21+
"lucy_2_rt",
22+
"live_avatar",
23+
]
824
VideoModels = Literal[
9-
"lucy-pro-v2v",
25+
# Canonical names
26+
"lucy-clip",
27+
"lucy-2",
28+
"lucy-2.1",
29+
"lucy-restyle-2",
1030
"lucy-motion",
31+
# Deprecated names
32+
"lucy-pro-v2v",
1133
"lucy-restyle-v2v",
1234
"lucy-2-v2v",
1335
]
14-
ImageModels = Literal["lucy-pro-i2i"]
36+
ImageModels = Literal[
37+
# Canonical names
38+
"lucy-image-2",
39+
# Deprecated names
40+
"lucy-pro-i2i",
41+
]
1542
Model = Literal[RealTimeModels, VideoModels, ImageModels]
1643

44+
MODEL_ALIASES: dict[str, str] = {
45+
# Realtime aliases
46+
"mirage": "lucy-restyle",
47+
"mirage_v2": "lucy-restyle-2",
48+
"lucy_v2v_720p_rt": "lucy",
49+
"lucy_2_rt": "lucy-2",
50+
"live_avatar": "live-avatar",
51+
# Video aliases
52+
"lucy-pro-v2v": "lucy-clip",
53+
"lucy-restyle-v2v": "lucy-restyle-2",
54+
"lucy-2-v2v": "lucy-2",
55+
# Image aliases
56+
"lucy-pro-i2i": "lucy-image-2",
57+
}
58+
59+
_warned_aliases: set[str] = set()
60+
61+
62+
def _warn_deprecated(model: str) -> None:
63+
canonical = MODEL_ALIASES.get(model)
64+
if canonical and model not in _warned_aliases:
65+
_warned_aliases.add(model)
66+
warnings.warn(
67+
f'Model "{model}" is deprecated. Use "{canonical}" instead. '
68+
f"See https://docs.platform.decart.ai/models for details.",
69+
DeprecationWarning,
70+
stacklevel=3,
71+
)
72+
1773
# Type variable for model name
1874
ModelT = TypeVar("ModelT", bound=str)
1975

@@ -49,6 +105,7 @@ class VideoToVideoInput(DecartBaseModel):
49105
max_length=1000,
50106
)
51107
data: FileInput
108+
reference_image: Optional[FileInput] = None
52109
seed: Optional[int] = None
53110
resolution: Optional[str] = None
54111
enhance_prompt: Optional[bool] = None
@@ -121,6 +178,64 @@ class ImageToImageInput(DecartBaseModel):
121178

122179
_MODELS = {
123180
"realtime": {
181+
# Canonical names
182+
"lucy": ModelDefinition(
183+
name="lucy",
184+
url_path="/v1/stream",
185+
fps=25,
186+
width=1280,
187+
height=704,
188+
input_schema=BaseModel,
189+
),
190+
"lucy-2": ModelDefinition(
191+
name="lucy-2",
192+
url_path="/v1/stream",
193+
fps=20,
194+
width=1280,
195+
height=720,
196+
input_schema=BaseModel,
197+
),
198+
"lucy-2.1": ModelDefinition(
199+
name="lucy-2.1",
200+
url_path="/v1/stream",
201+
fps=20,
202+
width=1088,
203+
height=624,
204+
input_schema=BaseModel,
205+
),
206+
"lucy-2.1-vton": ModelDefinition(
207+
name="lucy-2.1-vton",
208+
url_path="/v1/stream",
209+
fps=20,
210+
width=1088,
211+
height=624,
212+
input_schema=BaseModel,
213+
),
214+
"lucy-restyle": ModelDefinition(
215+
name="lucy-restyle",
216+
url_path="/v1/stream",
217+
fps=25,
218+
width=1280,
219+
height=704,
220+
input_schema=BaseModel,
221+
),
222+
"lucy-restyle-2": ModelDefinition(
223+
name="lucy-restyle-2",
224+
url_path="/v1/stream",
225+
fps=22,
226+
width=1280,
227+
height=704,
228+
input_schema=BaseModel,
229+
),
230+
"live-avatar": ModelDefinition(
231+
name="live-avatar",
232+
url_path="/v1/stream",
233+
fps=25,
234+
width=1280,
235+
height=720,
236+
input_schema=BaseModel,
237+
),
238+
# Deprecated names
124239
"mirage": ModelDefinition(
125240
name="mirage",
126241
url_path="/v1/stream",
@@ -163,40 +278,84 @@ class ImageToImageInput(DecartBaseModel):
163278
),
164279
},
165280
"video": {
166-
"lucy-pro-v2v": ModelDefinition(
167-
name="lucy-pro-v2v",
168-
url_path="/v1/generate/lucy-pro-v2v",
281+
# Canonical names
282+
"lucy-clip": ModelDefinition(
283+
name="lucy-clip",
284+
url_path="/v1/jobs/lucy-clip",
169285
fps=25,
170286
width=1280,
171287
height=704,
172288
input_schema=VideoToVideoInput,
173289
),
290+
"lucy-2": ModelDefinition(
291+
name="lucy-2",
292+
url_path="/v1/jobs/lucy-2",
293+
fps=20,
294+
width=1280,
295+
height=720,
296+
input_schema=VideoEdit2Input,
297+
),
298+
"lucy-2.1": ModelDefinition(
299+
name="lucy-2.1",
300+
url_path="/v1/jobs/lucy-2.1",
301+
fps=20,
302+
width=1088,
303+
height=624,
304+
input_schema=VideoEdit2Input,
305+
),
306+
"lucy-restyle-2": ModelDefinition(
307+
name="lucy-restyle-2",
308+
url_path="/v1/jobs/lucy-restyle-2",
309+
fps=22,
310+
width=1280,
311+
height=704,
312+
input_schema=VideoRestyleInput,
313+
),
174314
"lucy-motion": ModelDefinition(
175315
name="lucy-motion",
176-
url_path="/v1/generate/lucy-motion",
316+
url_path="/v1/jobs/lucy-motion",
177317
fps=25,
178318
width=1280,
179319
height=704,
180320
input_schema=ImageToMotionVideoInput,
181321
),
322+
# Deprecated names
323+
"lucy-pro-v2v": ModelDefinition(
324+
name="lucy-pro-v2v",
325+
url_path="/v1/jobs/lucy-pro-v2v",
326+
fps=25,
327+
width=1280,
328+
height=704,
329+
input_schema=VideoToVideoInput,
330+
),
182331
"lucy-restyle-v2v": ModelDefinition(
183332
name="lucy-restyle-v2v",
184-
url_path="/v1/generate/lucy-restyle-v2v",
185-
fps=25,
333+
url_path="/v1/jobs/lucy-restyle-v2v",
334+
fps=22,
186335
width=1280,
187336
height=704,
188337
input_schema=VideoRestyleInput,
189338
),
190339
"lucy-2-v2v": ModelDefinition(
191340
name="lucy-2-v2v",
192-
url_path="/v1/generate/lucy-2-v2v",
341+
url_path="/v1/jobs/lucy-2-v2v",
193342
fps=20,
194343
width=1280,
195344
height=720,
196345
input_schema=VideoEdit2Input,
197346
),
198347
},
199348
"image": {
349+
# Canonical names
350+
"lucy-image-2": ModelDefinition(
351+
name="lucy-image-2",
352+
url_path="/v1/generate/lucy-image-2",
353+
fps=25,
354+
width=1280,
355+
height=704,
356+
input_schema=ImageToImageInput,
357+
),
358+
# Deprecated names
200359
"lucy-pro-i2i": ModelDefinition(
201360
name="lucy-pro-i2i",
202361
url_path="/v1/generate/lucy-pro-i2i",
@@ -213,6 +372,7 @@ class Models:
213372
@staticmethod
214373
def realtime(model: RealTimeModels) -> RealTimeModelDefinition:
215374
"""Get a realtime model definition for WebRTC streaming."""
375+
_warn_deprecated(model)
216376
try:
217377
return _MODELS["realtime"][model] # type: ignore[return-value]
218378
except KeyError:
@@ -225,11 +385,13 @@ def video(model: VideoModels) -> VideoModelDefinition:
225385
Video models only support the queue API.
226386
227387
Available models:
228-
- "lucy-pro-v2v" - Video-to-video
388+
- "lucy-clip" - Video-to-video
389+
- "lucy-2" - Video editing with reference image support
390+
- "lucy-2.1" - Video editing (newer, higher quality)
391+
- "lucy-restyle-2" - Video restyling with prompt or reference image
229392
- "lucy-motion" - Image-to-motion-video
230-
- "lucy-restyle-v2v" - Video-to-video with prompt or reference image
231-
- "lucy-2-v2v" - Video-to-video editing (long-form, 720p)
232393
"""
394+
_warn_deprecated(model)
233395
try:
234396
return _MODELS["video"][model] # type: ignore[return-value]
235397
except KeyError:
@@ -242,8 +404,9 @@ def image(model: ImageModels) -> ImageModelDefinition:
242404
Image models only support the process (sync) API.
243405
244406
Available models:
245-
- "lucy-pro-i2i" - Image-to-image
407+
- "lucy-image-2" - Image-to-image
246408
"""
409+
_warn_deprecated(model)
247410
try:
248411
return _MODELS["image"][model] # type: ignore[return-value]
249412
except KeyError:

decart/queue/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ class QueueClient:
3737
3838
# Option 1: Submit and poll automatically
3939
result = await client.queue.submit_and_poll({
40-
"model": models.video("lucy-pro-v2v"),
40+
"model": models.video("lucy-clip"),
4141
"prompt": "Restyle this clip with anime shading and saturated colors",
4242
"data": open("input.mp4", "rb"),
4343
"on_status_change": lambda job: print(f"Status: {job.status}"),
4444
})
4545
4646
# Option 2: Submit and poll manually
4747
job = await client.queue.submit({
48-
"model": models.video("lucy-pro-v2v"),
48+
"model": models.video("lucy-clip"),
4949
"prompt": "Add cinematic teal-and-orange grading and subtle film grain",
5050
"data": open("input.mp4", "rb"),
5151
})

decart/realtime/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ async def connect(
113113

114114
model_name: RealTimeModels = options.model.name # type: ignore[assignment]
115115

116-
is_avatar_live = model_name == "live_avatar"
116+
is_avatar_live = model_name in ("live_avatar", "live-avatar")
117117
audio_stream_manager: Optional[AudioStreamManager] = None
118118

119119
if is_avatar_live and local_track is None:

decart/realtime/webrtc_connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ async def on_ice_connection_state_change():
246246
self._pc.addTransceiver("audio", direction="recvonly")
247247
logger.debug("Added video+audio transceivers (recvonly) for subscribe mode")
248248
else:
249-
if model_name == "live_avatar":
249+
if model_name in ("live_avatar", "live-avatar"):
250250
self._pc.addTransceiver("video", direction="recvonly")
251251
logger.debug("Added video transceiver (recvonly) for avatar-live mode")
252252
self._pc.addTrack(local_track)

decart/tokens/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class TokensClient:
2828
# With expiry, model restrictions, and constraints:
2929
token = await client.tokens.create(
3030
expires_in=120,
31-
allowed_models=["lucy_2_rt"],
31+
allowed_models=["lucy-2"],
3232
constraints={"realtime": {"maxSessionDuration": 300}},
3333
)
3434
```
@@ -70,7 +70,7 @@ async def create(
7070
token = await client.tokens.create(
7171
metadata={"role": "viewer"},
7272
expires_in=120,
73-
allowed_models=["lucy_2_rt"],
73+
allowed_models=["lucy-2"],
7474
constraints={"realtime": {"maxSessionDuration": 300}},
7575
)
7676
```

examples/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ export DECART_API_KEY="your-api-key-here"
2020

2121
### Process API
2222

23-
- **`process_video.py`** - Edit a local video with `lucy-pro-v2v`
24-
- **`process_image.py`** - Edit the bundled example image with `lucy-pro-i2i`
23+
- **`process_video.py`** - Edit a local video with `lucy-clip`
24+
- **`process_image.py`** - Edit the bundled example image with `lucy-image-2`
2525
- **`process_url.py`** - Transform videos from URLs
2626
- **`queue_image_example.py`** - Turn the bundled example image into motion with `lucy-motion`
2727

0 commit comments

Comments
 (0)