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
18 changes: 18 additions & 0 deletions decart/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
"lucy-clip",
"lucy-2",
"lucy-2.1",
"lucy-2.1-vton",
"lucy-restyle-2",
"lucy-motion",
# Latest aliases (server-side resolution)
"lucy-latest",
"lucy-vton-latest",
"lucy-restyle-latest",
"lucy-clip-latest",
"lucy-motion-latest",
Expand Down Expand Up @@ -340,6 +342,14 @@ class ImageToImageInput(DecartBaseModel):
height=624,
input_schema=VideoEdit2Input,
),
"lucy-2.1-vton": ModelDefinition(
name="lucy-2.1-vton",
url_path="/v1/jobs/lucy-2.1-vton",
fps=20,
width=1088,
height=624,
input_schema=VideoEdit2Input,
),
"lucy-restyle-2": ModelDefinition(
name="lucy-restyle-2",
url_path="/v1/jobs/lucy-restyle-2",
Expand All @@ -365,6 +375,14 @@ class ImageToImageInput(DecartBaseModel):
height=624,
input_schema=VideoEdit2Input,
),
"lucy-vton-latest": ModelDefinition(
name="lucy-vton-latest",
url_path="/v1/jobs/lucy-vton-latest",
fps=20,
width=1088,
height=624,
input_schema=VideoEdit2Input,
),
"lucy-restyle-latest": ModelDefinition(
name="lucy-restyle-latest",
url_path="/v1/jobs/lucy-restyle-latest",
Expand Down
114 changes: 114 additions & 0 deletions examples/video_tryon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""
Virtual Try-On Example

This example demonstrates how to use the lucy-2.1-vton model to perform
virtual try-on on a video using a reference garment image.

Usage:
# With reference image and prompt:
DECART_API_KEY=your-key python video_tryon.py input.mp4 --reference garment.png --prompt "wear this outfit"

# With reference image only (empty prompt):
DECART_API_KEY=your-key python video_tryon.py input.mp4 --reference garment.png

Requirements:
pip install decart
"""

import asyncio
import argparse
import os
import sys
from pathlib import Path

from decart import DecartClient, models


async def main():
parser = argparse.ArgumentParser(
description="Virtual try-on: apply a garment from a reference image onto a person in a video"
)
parser.add_argument("video", help="Path to input video file")
parser.add_argument("--reference", "-r", required=True, help="Path to reference garment image")
parser.add_argument("--prompt", "-p", default="", help="Text prompt (default: empty string)")
parser.add_argument("--output", "-o", help="Output file path (default: output_tryon.mp4)")
parser.add_argument("--seed", "-s", type=int, help="Random seed for reproducibility")
parser.add_argument("--no-enhance", action="store_true", help="Disable prompt enhancement")

args = parser.parse_args()

api_key = os.getenv("DECART_API_KEY")
if not api_key:
print("Error: DECART_API_KEY environment variable not set")
sys.exit(1)

video_path = Path(args.video)
if not video_path.exists():
print(f"Error: Video file not found: {video_path}")
sys.exit(1)

ref_path = Path(args.reference)
if not ref_path.exists():
print(f"Error: Reference image not found: {ref_path}")
sys.exit(1)

output_path = args.output or f"output_tryon_{video_path.stem}.mp4"

print("=" * 50)
print("Virtual Try-On")
print("=" * 50)
print(f"Input video: {video_path}")
print(f"Reference image: {ref_path}")
if args.prompt:
print(f"Prompt: '{args.prompt}'")
print(f"Enhance prompt: {not args.no_enhance}")
print(f"Output: {output_path}")
if args.seed is not None:
print(f"Seed: {args.seed}")
print("=" * 50)

async with DecartClient(api_key=api_key) as client:
options = {
"model": models.video("lucy-2.1-vton"),
"data": video_path,
"prompt": args.prompt,
"reference_image": ref_path,
}

if args.prompt:
options["enhance_prompt"] = not args.no_enhance

if args.seed is not None:
options["seed"] = args.seed
Comment thread
cursor[bot] marked this conversation as resolved.

def on_status_change(job):
status_emoji = {
"pending": "⏳",
"processing": "🔄",
"completed": "✅",
"failed": "❌",
}
emoji = status_emoji.get(job.status, "•")
print(f"{emoji} Status: {job.status}")

options["on_status_change"] = on_status_change

print("\nSubmitting job...")
result = await client.queue.submit_and_poll(options)

if result.status == "failed":
print(f"\n❌ Job failed: {result.error}")
sys.exit(1)

print("\n✅ Job completed!")
print(f"💾 Saving to {output_path}...")

with open(output_path, "wb") as f:
f.write(result.data)

print(f"✓ Video saved to {output_path}")
print(f" Size: {len(result.data) / 1024 / 1024:.2f} MB")


if __name__ == "__main__":
asyncio.run(main())
15 changes: 15 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ def test_canonical_video_models() -> None:
assert model.width == 1088
assert model.height == 624

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

model = models.video("lucy-restyle-2")
assert model.name == "lucy-restyle-2"
assert model.url_path == "/v1/jobs/lucy-restyle-2"
Expand Down Expand Up @@ -195,6 +202,13 @@ def test_latest_video_models() -> None:
assert model.width == 1088
assert model.height == 624

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

model = models.video("lucy-restyle-latest")
assert model.name == "lucy-restyle-latest"
assert model.url_path == "/v1/jobs/lucy-restyle-latest"
Expand Down Expand Up @@ -226,6 +240,7 @@ def test_latest_aliases_no_deprecation_warning() -> None:
models.realtime("lucy-vton-latest")
models.realtime("lucy-restyle-latest")
models.video("lucy-latest")
models.video("lucy-vton-latest")
models.video("lucy-restyle-latest")
models.video("lucy-clip-latest")
models.video("lucy-motion-latest")
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading