diff --git a/README.md b/README.md index 45de31d..e1bc3c0 100644 --- a/README.md +++ b/README.md @@ -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}"), @@ -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"), }) diff --git a/decart/client.py b/decart/client.py index 20ceab7..d9ff075 100644 --- a/decart/client.py +++ b/decart/client.py @@ -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"), }) @@ -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"), }) diff --git a/decart/models.py b/decart/models.py index 23f20aa..f2cdf2b 100644 --- a/decart/models.py +++ b/decart/models.py @@ -18,7 +18,6 @@ ] VideoModels = Literal[ # Canonical names - "lucy-clip", "lucy-2.1", "lucy-2.5", "lucy-vton-3.5", @@ -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[ @@ -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", @@ -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. @@ -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, ), "lucy-2.5": ModelDefinition( name="lucy-2.5", @@ -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. @@ -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( @@ -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. @@ -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", @@ -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 diff --git a/decart/queue/client.py b/decart/queue/client.py index ef14a77..249b137 100644 --- a/decart/queue/client.py +++ b/decart/queue/client.py @@ -36,7 +36,7 @@ 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}"), @@ -44,7 +44,7 @@ class QueueClient: # 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"), }) diff --git a/examples/README.md b/examples/README.md index 022e37d..4c65b66 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 diff --git a/examples/process_url.py b/examples/process_url.py index bceece4..29a8b34 100644 --- a/examples/process_url.py +++ b/examples/process_url.py @@ -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}"), diff --git a/examples/process_video.py b/examples/process_video.py index 52ebcc3..30f4b0a 100644 --- a/examples/process_video.py +++ b/examples/process_video.py @@ -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, diff --git a/examples/queue_example.py b/examples/queue_example.py index e15eb6e..cf3408a 100644 --- a/examples/queue_example.py +++ b/examples/queue_example.py @@ -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}"), } ) @@ -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}") diff --git a/test_ui.py b/test_ui.py index 4cf8bf7..4e5e65c 100644 --- a/test_ui.py +++ b/test_ui.py @@ -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), } diff --git a/tests/test_models.py b/tests/test_models.py index 37dda90..9a22d9e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -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" @@ -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" @@ -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") @@ -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") @@ -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") @@ -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") @@ -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 diff --git a/tests/test_queue.py b/tests/test_queue.py index b45ed0c..bc8d338 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -21,7 +21,7 @@ async def test_queue_submit_video_to_video_basic() -> None: job = await client.queue.submit( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": "Restyle this clip with a cinematic dusk grade", "data": b"fake video data", "seed": 42, @@ -43,7 +43,7 @@ async def test_queue_submit_video_to_video() -> None: job = await client.queue.submit( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": "Anime style", "data": b"fake video data", "enhance_prompt": True, @@ -149,7 +149,7 @@ async def test_queue_submit_and_poll_completed() -> None: result = await client.queue.submit_and_poll( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": "Add anime shading and crisp outlines", "data": b"fake video data", } @@ -175,7 +175,7 @@ async def test_queue_submit_and_poll_failed() -> None: result = await client.queue.submit_and_poll( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": "Add anime shading and crisp outlines", "data": b"fake video data", } @@ -210,7 +210,7 @@ def on_status_change(job): await client.queue.submit_and_poll( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": "Add anime shading and crisp outlines", "data": b"fake video data", "on_status_change": on_status_change, @@ -230,7 +230,7 @@ async def test_queue_submit_missing_required_field() -> None: with pytest.raises(DecartSDKError): await client.queue.submit( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), # Missing 'prompt' and 'data' which are required for v2v } ) @@ -245,13 +245,13 @@ async def test_queue_submit_max_prompt_length() -> None: with pytest.raises(DecartSDKError) as exception: await client.queue.submit( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": prompt, "data": b"fake video data", } ) - assert "Invalid inputs for lucy-clip" in str(exception) + assert "Invalid inputs for lucy-latest" in str(exception) @pytest.mark.asyncio @@ -275,7 +275,7 @@ async def test_queue_includes_user_agent_header() -> None: await client.queue.submit( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": "Apply a cinematic grade", "data": b"fake video data", } @@ -287,7 +287,7 @@ async def test_queue_includes_user_agent_header() -> None: assert "User-Agent" in headers assert headers["User-Agent"].startswith("decart-python-sdk/") - assert mock_session.post.call_args.args[0] == "https://api.decart.ai/v1/jobs/lucy-clip" + assert mock_session.post.call_args.args[0] == "https://api.decart.ai/v1/jobs/lucy-latest" @pytest.mark.asyncio @@ -312,7 +312,7 @@ async def test_queue_submit_surfaces_backend_error() -> None: with pytest.raises(QueueSubmitError) as exc_info: await client.queue.submit( { - "model": models.video("lucy-clip"), + "model": models.video("lucy-latest"), "prompt": "Apply a cinematic grade", "data": b"fake video data", }