Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ For video editing jobs, use the queue API to submit jobs and poll for results:
async with DecartClient(api_key=os.getenv("DECART_API_KEY")) as client:
# Submit and poll automatically
result = await client.queue.submit_and_poll({
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Restyle this footage with anime shading and vibrant neon highlights",
"data": open("input.mp4", "rb"),
"on_status_change": lambda job: print(f"Status: {job.status}"),
Expand All @@ -72,7 +72,7 @@ Or manage the polling manually:
async with DecartClient(api_key=os.getenv("DECART_API_KEY")) as client:
# Submit the job
job = await client.queue.submit({
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Add cinematic teal-and-orange grading and gentle film grain",
"data": open("input.mp4", "rb"),
})
Expand Down
4 changes: 2 additions & 2 deletions decart/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class DecartClient:

# Video editing (async) - use queue
result = await client.queue.submit_and_poll({
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Restyle this footage with anime shading and vibrant neon highlights",
"data": open("input.mp4", "rb"),
})
Expand Down Expand Up @@ -84,7 +84,7 @@ def queue(self) -> QueueClient:
```python
# Submit and poll automatically
result = await client.queue.submit_and_poll({
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Restyle this footage with anime shading and vibrant neon highlights",
"data": open("input.mp4", "rb"),
})
Expand Down
58 changes: 8 additions & 50 deletions decart/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
]
VideoModels = Literal[
# Canonical names
"lucy-clip",
"lucy-2.1",
"lucy-2.5",
"lucy-vton-3.5",
Expand All @@ -28,9 +27,7 @@
"lucy-latest",
"lucy-vton-latest",
"lucy-restyle-latest",
"lucy-clip-latest",
# Deprecated / alias names
"lucy-pro-v2v",
"lucy-restyle-v2v",
]
ImageModels = Literal[
Expand All @@ -49,7 +46,6 @@

MODEL_ALIASES: dict[str, str] = {
# Video aliases
"lucy-pro-v2v": "lucy-clip",
"lucy-restyle-v2v": "lucy-restyle-2",
# Image aliases
"lucy-pro-i2i": "lucy-image-2",
Expand Down Expand Up @@ -127,19 +123,6 @@ class ModelDefinition(DecartBaseModel, Generic[ModelT]):
"""Type alias for model definitions with arbitrary (non-registry) model names."""


class VideoToVideoInput(DecartBaseModel):
prompt: str = Field(
...,
min_length=1,
max_length=1000,
)
data: FileInput
reference_image: Optional[FileInput] = None
seed: Optional[int] = None
resolution: Optional[str] = None
enhance_prompt: Optional[bool] = None


class VideoRestyleInput(DecartBaseModel):
"""Input for lucy-restyle-v2v model.

Expand Down Expand Up @@ -205,8 +188,8 @@ class ImageToImageInput(DecartBaseModel):
name="lucy-2.1",
url_path="/v1/stream",
fps=30,
width=1088,
height=624,
width=1280,
height=720,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect lucy-2.1 native geometry

Medium Severity

lucy-2.1 realtime and video entries now advertise 1280x720, which is lucy-2.5's native size. lucy-2.1 remains 1088x624 in the JS and iOS SDKs, and this repo previously kept that geometry when lucy-2.5 was corrected. Clients that size capture from model.width and model.height will send the wrong frame size.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7f10492. Configure here.

),
"lucy-2.5": ModelDefinition(
name="lucy-2.5",
Expand All @@ -228,8 +211,8 @@ class ImageToImageInput(DecartBaseModel):
name="lucy-latest",
url_path="/v1/stream",
fps=30,
width=1088,
height=624,
width=1280,
height=720,
supported_speeds=("fast",),
),
# Server-side alias currently resolves to lucy-vton-3.6.
Expand Down Expand Up @@ -267,20 +250,12 @@ class ImageToImageInput(DecartBaseModel):
},
"video": {
# Canonical names
"lucy-clip": ModelDefinition(
name="lucy-clip",
url_path="/v1/jobs/lucy-clip",
fps=25,
width=1280,
height=704,
input_schema=VideoToVideoInput,
),
"lucy-2.1": ModelDefinition(
name="lucy-2.1",
url_path="/v1/jobs/lucy-2.1",
fps=20,
width=1088,
height=624,
width=1280,
height=720,
input_schema=VideoEdit2Input,
),
"lucy-2.5": ModelDefinition(
Expand Down Expand Up @@ -320,8 +295,8 @@ class ImageToImageInput(DecartBaseModel):
name="lucy-latest",
url_path="/v1/jobs/lucy-latest",
fps=20,
width=1088,
height=624,
width=1280,
height=720,
input_schema=VideoEdit2Input,
),
# Server-side alias currently resolves to lucy-vton-3.6.
Expand All @@ -341,23 +316,7 @@ class ImageToImageInput(DecartBaseModel):
height=704,
input_schema=VideoRestyleInput,
),
"lucy-clip-latest": ModelDefinition(
name="lucy-clip-latest",
url_path="/v1/jobs/lucy-clip-latest",
fps=25,
width=1280,
height=704,
input_schema=VideoToVideoInput,
),
# Deprecated names
"lucy-pro-v2v": ModelDefinition(
name="lucy-pro-v2v",
url_path="/v1/jobs/lucy-pro-v2v",
fps=25,
width=1280,
height=704,
input_schema=VideoToVideoInput,
),
"lucy-restyle-v2v": ModelDefinition(
name="lucy-restyle-v2v",
url_path="/v1/jobs/lucy-restyle-v2v",
Expand Down Expand Up @@ -416,7 +375,6 @@ def video(model: VideoModels) -> VideoModelDefinition:
Video models only support the queue API.

Available models:
- "lucy-clip" - Video-to-video
- "lucy-2.1" - Video editing (newer, higher quality)
- "lucy-2.5" - Video editing (latest generation)
- "lucy-restyle-2" - Video restyling with prompt or reference image
Expand Down
4 changes: 2 additions & 2 deletions decart/queue/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ class QueueClient:

# Option 1: Submit and poll automatically
result = await client.queue.submit_and_poll({
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Restyle this clip with anime shading and saturated colors",
"data": open("input.mp4", "rb"),
"on_status_change": lambda job: print(f"Status: {job.status}"),
})

# Option 2: Submit and poll manually
job = await client.queue.submit({
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Add cinematic teal-and-orange grading and subtle film grain",
"data": open("input.mp4", "rb"),
})
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export DECART_API_KEY="your-api-key-here"

### Process API

- **`process_video.py`** - Edit a local video with `lucy-clip`
- **`process_video.py`** - Edit a local video with `lucy-latest`
- **`process_image.py`** - Edit the bundled example image with `lucy-image-2`
- **`process_url.py`** - Transform videos from URLs

Expand Down
2 changes: 1 addition & 1 deletion examples/process_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async def main() -> None:
print("Transforming video from URL...")
result = await client.queue.submit_and_poll(
{
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Watercolor painting style",
"data": "https://docs.platform.decart.ai/assets/example-video.mp4",
"on_status_change": lambda job: print(f" Status: {job.status}"),
Expand Down
2 changes: 1 addition & 1 deletion examples/process_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async def main() -> None:
print("Editing video...")
result = await client.queue.submit_and_poll(
{
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Restyle this footage with anime shading, vibrant highlights, and crisp outlines",
"data": video_path,
"enhance_prompt": True,
Expand Down
6 changes: 2 additions & 4 deletions examples/queue_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ async def main() -> None:
print("Submitting job with automatic polling...")
result = await client.queue.submit_and_poll(
{
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Give this clip a cinematic dusk grade with cooler shadows and warm highlights",
"data": video_path,
"resolution": "480p",
"on_status_change": lambda job: print(f"Job {job.job_id}: {job.status}"),
}
)
Expand All @@ -35,10 +34,9 @@ async def main() -> None:
print("\nSubmitting job with manual polling...")
job = await client.queue.submit(
{
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": "Restyle the scene to feel like stop-motion miniatures with soft practical lighting",
"data": video_path,
"resolution": "480p",
}
)
print(f"Job submitted: {job.job_id}")
Expand Down
2 changes: 1 addition & 1 deletion test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async def process_video_v2v(
client = get_client(api_key)

options = {
"model": models.video("lucy-clip"),
"model": models.video("lucy-latest"),
"prompt": prompt,
"data": Path(input_video),
}
Expand Down
47 changes: 17 additions & 30 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def test_canonical_realtime_models() -> None:
model = models.realtime("lucy-2.1")
assert model.name == "lucy-2.1"
assert model.fps == 30
assert model.width == 1088
assert model.height == 624
assert model.width == 1280
assert model.height == 720

model = models.realtime("lucy-2.5")
assert model.name == "lucy-2.5"
Expand All @@ -43,16 +43,12 @@ def test_canonical_realtime_models() -> None:


def test_canonical_video_models() -> None:
model = models.video("lucy-clip")
assert model.name == "lucy-clip"
assert model.url_path == "/v1/jobs/lucy-clip"

model = models.video("lucy-2.1")
assert model.name == "lucy-2.1"
assert model.url_path == "/v1/jobs/lucy-2.1"
assert model.fps == 20
assert model.width == 1088
assert model.height == 624
assert model.width == 1280
assert model.height == 720

model = models.video("lucy-2.5")
assert model.name == "lucy-2.5"
Expand Down Expand Up @@ -83,15 +79,6 @@ def test_canonical_video_models() -> None:
def test_deprecated_video_models() -> None:
_warned_aliases.clear()

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
model = models.video("lucy-pro-v2v")
assert model.name == "lucy-pro-v2v"
assert len(w) == 1
assert "lucy-clip" in str(w[0].message)

_warned_aliases.clear()

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
model = models.video("lucy-restyle-v2v")
Expand Down Expand Up @@ -122,19 +109,22 @@ def test_deprecation_warning_only_once() -> None:

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
models.video("lucy-pro-v2v")
models.video("lucy-pro-v2v")
models.video("lucy-pro-v2v")
models.video("lucy-restyle-v2v")
models.video("lucy-restyle-v2v")
models.video("lucy-restyle-v2v")
assert len(w) == 1


def test_latest_realtime_models() -> None:
# Server-side alias currently resolves to lucy-2.5 (same geometry).
model = models.realtime("lucy-latest")
assert model.name == "lucy-latest"
assert model.url_path == "/v1/stream"
assert model.fps == 30
assert model.width == 1088
assert model.height == 624
assert model.width == 1280
assert model.height == 720
resolved = models.realtime("lucy-2.5")
assert (model.width, model.height) == (resolved.width, resolved.height)

# Server-side alias currently resolves to lucy-vton-3.6 (same geometry).
model = models.realtime("lucy-vton-latest")
Expand All @@ -153,12 +143,15 @@ def test_latest_realtime_models() -> None:


def test_latest_video_models() -> None:
# Server-side alias currently resolves to lucy-2.5 (same geometry).
model = models.video("lucy-latest")
assert model.name == "lucy-latest"
assert model.url_path == "/v1/jobs/lucy-latest"
assert model.fps == 20
assert model.width == 1088
assert model.height == 624
assert model.width == 1280
assert model.height == 720
resolved = models.video("lucy-2.5")
assert (model.width, model.height) == (resolved.width, resolved.height)

# Server-side alias currently resolves to lucy-vton-3.6 (same geometry).
model = models.video("lucy-vton-latest")
Expand All @@ -173,11 +166,6 @@ def test_latest_video_models() -> None:
assert model.url_path == "/v1/jobs/lucy-restyle-latest"
assert model.fps == 22

model = models.video("lucy-clip-latest")
assert model.name == "lucy-clip-latest"
assert model.url_path == "/v1/jobs/lucy-clip-latest"
assert model.fps == 25


def test_latest_image_models() -> None:
model = models.image("lucy-image-latest")
Expand All @@ -196,7 +184,6 @@ def test_latest_aliases_no_deprecation_warning() -> None:
models.video("lucy-latest")
models.video("lucy-vton-latest")
models.video("lucy-restyle-latest")
models.video("lucy-clip-latest")
models.image("lucy-image-latest")
assert len(w) == 0

Expand Down
Loading
Loading