diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4808d97..b29b3b6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.1.0" + ".": "5.2.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index dde3b6e..2ac9ca5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-b85d7ddcdd90c3009ccbad953a0f5c1015b6f4acc6196ce47dfaff89873e8831.yml -openapi_spec_hash: c660abf954cb61f065c4f957966776bb -config_hash: dbcc649d22e217f477258caee20c63d2 +configured_endpoints: 49 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-ae784dbc3407e86a6458d2848f2e9720eea6b3c534c3bbe101b614e3eb547dac.yml +openapi_spec_hash: e84013ce9a3d7c14175ba3050cbb5bc1 +config_hash: 702846e1d30f519e56e425ca5455febe diff --git a/CHANGELOG.md b/CHANGELOG.md index d0e513e..4889d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 5.2.0 (2026-06-17) + +Full Changelog: [v5.1.0...v5.2.0](https://github.com/runwayml/sdk-python/compare/v5.1.0...v5.2.0) + +### Features + +* **api:** Recipes, aleph2 range ([529fc1d](https://github.com/runwayml/sdk-python/commit/529fc1da7b279af30f5aef1314ab16a87a3916f1)) +* **client:** add recipe endpoints to config ([26725b1](https://github.com/runwayml/sdk-python/commit/26725b15a27fbf9437a323bb28238e7d4657ed53)) +* **client:** Make recipes waitable ([11dbef5](https://github.com/runwayml/sdk-python/commit/11dbef5fbf6bd4147d4f6b91f43e260c9b4b2318)) + + +### Bug Fixes + +* **api:** remove range param ([f67c02d](https://github.com/runwayml/sdk-python/commit/f67c02db8769d481109a7d033b8fa030abdeb56e)) + ## 5.1.0 (2026-06-12) Full Changelog: [v5.0.0...v5.1.0](https://github.com/runwayml/sdk-python/compare/v5.0.0...v5.1.0) diff --git a/api.md b/api.md index adfb5c7..c4eaded 100644 --- a/api.md +++ b/api.md @@ -235,6 +235,30 @@ Methods: - client.realtime_sessions.retrieve(id) -> RealtimeSessionRetrieveResponse - client.realtime_sessions.delete(id) -> None +# Recipes + +Types: + +```python +from runwayml.types import ( + RecipeMarketingStockImageResponse, + RecipeMultiShotVideoResponse, + RecipeProductAdResponse, + RecipeProductCampaignImageResponse, + RecipeProductSwapResponse, + RecipeProductUgcResponse, +) +``` + +Methods: + +- client.recipes.marketing_stock_image(\*\*params) -> RecipeMarketingStockImageResponse +- client.recipes.multi_shot_video(\*\*params) -> RecipeMultiShotVideoResponse +- client.recipes.product_ad(\*\*params) -> RecipeProductAdResponse +- client.recipes.product_campaign_image(\*\*params) -> RecipeProductCampaignImageResponse +- client.recipes.product_swap(\*\*params) -> RecipeProductSwapResponse +- client.recipes.product_ugc(\*\*params) -> RecipeProductUgcResponse + # Voices Types: diff --git a/pyproject.toml b/pyproject.toml index bea7473..6e7c386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runwayml" -version = "5.1.0" +version = "5.2.0" description = "The official Python library for the runwayml API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/runwayml/_client.py b/src/runwayml/_client.py index e4ce04c..b134578 100644 --- a/src/runwayml/_client.py +++ b/src/runwayml/_client.py @@ -39,6 +39,7 @@ tasks, voices, avatars, + recipes, uploads, documents, workflows, @@ -62,6 +63,7 @@ from .resources.tasks import TasksResource, AsyncTasksResource from .resources.voices import VoicesResource, AsyncVoicesResource from .resources.avatars import AvatarsResource, AsyncAvatarsResource + from .resources.recipes import RecipesResource, AsyncRecipesResource from .resources.uploads import UploadsResource, AsyncUploadsResource from .resources.documents import DocumentsResource, AsyncDocumentsResource from .resources.workflows import WorkflowsResource, AsyncWorkflowsResource @@ -290,6 +292,12 @@ def realtime_sessions(self) -> RealtimeSessionsResource: return RealtimeSessionsResource(self) + @cached_property + def recipes(self) -> RecipesResource: + from .resources.recipes import RecipesResource + + return RecipesResource(self) + @cached_property def voices(self) -> VoicesResource: from .resources.voices import VoicesResource @@ -620,6 +628,12 @@ def realtime_sessions(self) -> AsyncRealtimeSessionsResource: return AsyncRealtimeSessionsResource(self) + @cached_property + def recipes(self) -> AsyncRecipesResource: + from .resources.recipes import AsyncRecipesResource + + return AsyncRecipesResource(self) + @cached_property def voices(self) -> AsyncVoicesResource: from .resources.voices import AsyncVoicesResource @@ -886,6 +900,12 @@ def realtime_sessions(self) -> realtime_sessions.RealtimeSessionsResourceWithRaw return RealtimeSessionsResourceWithRawResponse(self._client.realtime_sessions) + @cached_property + def recipes(self) -> recipes.RecipesResourceWithRawResponse: + from .resources.recipes import RecipesResourceWithRawResponse + + return RecipesResourceWithRawResponse(self._client.recipes) + @cached_property def voices(self) -> voices.VoicesResourceWithRawResponse: from .resources.voices import VoicesResourceWithRawResponse @@ -1037,6 +1057,12 @@ def realtime_sessions(self) -> realtime_sessions.AsyncRealtimeSessionsResourceWi return AsyncRealtimeSessionsResourceWithRawResponse(self._client.realtime_sessions) + @cached_property + def recipes(self) -> recipes.AsyncRecipesResourceWithRawResponse: + from .resources.recipes import AsyncRecipesResourceWithRawResponse + + return AsyncRecipesResourceWithRawResponse(self._client.recipes) + @cached_property def voices(self) -> voices.AsyncVoicesResourceWithRawResponse: from .resources.voices import AsyncVoicesResourceWithRawResponse @@ -1188,6 +1214,12 @@ def realtime_sessions(self) -> realtime_sessions.RealtimeSessionsResourceWithStr return RealtimeSessionsResourceWithStreamingResponse(self._client.realtime_sessions) + @cached_property + def recipes(self) -> recipes.RecipesResourceWithStreamingResponse: + from .resources.recipes import RecipesResourceWithStreamingResponse + + return RecipesResourceWithStreamingResponse(self._client.recipes) + @cached_property def voices(self) -> voices.VoicesResourceWithStreamingResponse: from .resources.voices import VoicesResourceWithStreamingResponse @@ -1339,6 +1371,12 @@ def realtime_sessions(self) -> realtime_sessions.AsyncRealtimeSessionsResourceWi return AsyncRealtimeSessionsResourceWithStreamingResponse(self._client.realtime_sessions) + @cached_property + def recipes(self) -> recipes.AsyncRecipesResourceWithStreamingResponse: + from .resources.recipes import AsyncRecipesResourceWithStreamingResponse + + return AsyncRecipesResourceWithStreamingResponse(self._client.recipes) + @cached_property def voices(self) -> voices.AsyncVoicesResourceWithStreamingResponse: from .resources.voices import AsyncVoicesResourceWithStreamingResponse diff --git a/src/runwayml/_version.py b/src/runwayml/_version.py index fd68e66..dfda955 100644 --- a/src/runwayml/_version.py +++ b/src/runwayml/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "runwayml" -__version__ = "5.1.0" # x-release-please-version +__version__ = "5.2.0" # x-release-please-version diff --git a/src/runwayml/resources/__init__.py b/src/runwayml/resources/__init__.py index 257b9c5..01606f2 100644 --- a/src/runwayml/resources/__init__.py +++ b/src/runwayml/resources/__init__.py @@ -24,6 +24,14 @@ AvatarsResourceWithStreamingResponse, AsyncAvatarsResourceWithStreamingResponse, ) +from .recipes import ( + RecipesResource, + AsyncRecipesResource, + RecipesResourceWithRawResponse, + AsyncRecipesResourceWithRawResponse, + RecipesResourceWithStreamingResponse, + AsyncRecipesResourceWithStreamingResponse, +) from .uploads import ( UploadsResource, AsyncUploadsResource, @@ -292,6 +300,12 @@ "AsyncRealtimeSessionsResourceWithRawResponse", "RealtimeSessionsResourceWithStreamingResponse", "AsyncRealtimeSessionsResourceWithStreamingResponse", + "RecipesResource", + "AsyncRecipesResource", + "RecipesResourceWithRawResponse", + "AsyncRecipesResourceWithRawResponse", + "RecipesResourceWithStreamingResponse", + "AsyncRecipesResourceWithStreamingResponse", "VoicesResource", "AsyncVoicesResource", "VoicesResourceWithRawResponse", diff --git a/src/runwayml/resources/recipes.py b/src/runwayml/resources/recipes.py new file mode 100644 index 0000000..2fbb794 --- /dev/null +++ b/src/runwayml/resources/recipes.py @@ -0,0 +1,1121 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, overload + +import httpx + +from runwayml.lib.polling import ( + NewTaskCreatedResponse, + AsyncNewTaskCreatedResponse, + create_waitable_resource, + create_async_waitable_resource, +) + +from ..types import ( + recipe_product_ad_params, + recipe_product_ugc_params, + recipe_product_swap_params, + recipe_multi_shot_video_params, + recipe_marketing_stock_image_params, + recipe_product_campaign_image_params, +) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import required_args, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.recipe_product_ad_response import RecipeProductAdResponse +from ..types.recipe_product_ugc_response import RecipeProductUgcResponse +from ..types.recipe_product_swap_response import RecipeProductSwapResponse +from ..types.recipe_multi_shot_video_response import RecipeMultiShotVideoResponse +from ..types.recipe_marketing_stock_image_response import RecipeMarketingStockImageResponse +from ..types.recipe_product_campaign_image_response import RecipeProductCampaignImageResponse + +__all__ = ["RecipesResource", "AsyncRecipesResource"] + + +class RecipesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RecipesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers + """ + return RecipesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RecipesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response + """ + return RecipesResourceWithStreamingResponse(self) + + def marketing_stock_image( + self, + *, + prompt: str, + version: Literal["2026-06", "unsafe-latest"], + reference_image: recipe_marketing_stock_image_params.ReferenceImage | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + """ + Generate a polished marketing stock image from a text brief and optional brand + logo image. + + Args: + prompt: Marketing image brief. Describe the subject, audience, channel, desired mood, + setting, and any constraints. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + reference_image: Optional brand logo image to guide the generated marketing stock image. See + [our docs](/assets/inputs#images) on image inputs. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/recipes/marketing_stock_image", + body=maybe_transform( + { + "prompt": prompt, + "version": version, + "reference_image": reference_image, + }, + recipe_marketing_stock_image_params.RecipeMarketingStockImageParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_waitable_resource(RecipeMarketingStockImageResponse, self._client), + ) + + @overload + def multi_shot_video( + self, + *, + mode: Literal["auto"], + prompt: str, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: Literal[5, 10, 15] | Omit = omit, + first_frame: recipe_multi_shot_video_params.Variant0FirstFrame | Omit = omit, + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + """ + Generate a multi-cut video from a story prompt (auto mode) or a custom shot list + (custom mode). + + Args: + mode: Workflow mode. `auto` decomposes a story prompt into exactly 5 shots. + + prompt: Story prompt for auto mode. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Total duration of the output video in seconds. Defaults to 10 seconds. + + first_frame: Optional image used as the first frame of the output video. See + [our docs](/assets/inputs#images) on image inputs. + + ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`, + `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`, + `1440:1440`) use the pro tier. Defaults to `1280:720`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def multi_shot_video( + self, + *, + mode: Literal["custom"], + shots: Iterable[recipe_multi_shot_video_params.Variant1Shot], + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: Literal[5, 10, 15] | Omit = omit, + first_frame: recipe_multi_shot_video_params.Variant1FirstFrame | Omit = omit, + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + """ + Generate a multi-cut video from a story prompt (auto mode) or a custom shot list + (custom mode). + + Args: + mode: Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots. + + shots: Shot list for custom mode (3–5 shots). Per-shot durations must sum to + `duration`. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Total duration of the output video in seconds. Defaults to 10 seconds. + + first_frame: Optional image used as the first frame of the output video. See + [our docs](/assets/inputs#images) on image inputs. + + ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`, + `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`, + `1440:1440`) use the pro tier. Defaults to `1280:720`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["mode", "prompt", "version"], ["mode", "shots", "version"]) + def multi_shot_video( + self, + *, + mode: Literal["auto"] | Literal["custom"], + prompt: str | Omit = omit, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: Literal[5, 10, 15] | Omit = omit, + first_frame: recipe_multi_shot_video_params.Variant0FirstFrame + | recipe_multi_shot_video_params.Variant1FirstFrame + | Omit = omit, + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit, + shots: Iterable[recipe_multi_shot_video_params.Variant1Shot] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + return self._post( + "/v1/recipes/multi_shot_video", + body=maybe_transform( + { + "mode": mode, + "prompt": prompt, + "version": version, + "audio": audio, + "duration": duration, + "first_frame": first_frame, + "ratio": ratio, + "shots": shots, + }, + recipe_multi_shot_video_params.RecipeMultiShotVideoParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_waitable_resource(RecipeMultiShotVideoResponse, self._client), + ) + + def product_ad( + self, + *, + product_images: Iterable[recipe_product_ad_params.ProductImage], + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: int | Omit = omit, + product_info: str | Omit = omit, + ratio: Literal[ + "1280:720", "720:1280", "960:960", "834:1112", "1920:1080", "1080:1920", "1440:1440", "1248:1664" + ] + | Omit = omit, + style_images: Iterable[recipe_product_ad_params.StyleImage] | Omit = omit, + user_concept: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + """ + Generate a cinematic product ad from product images, optional style references, + product info, and creative direction. + + Args: + product_images: Product images (1–10). Multiple angles of the same product. All images inform + product analysis and reference generation; only the first image is used as the + primary product reference in the storyboard grid. See + [our docs](/assets/inputs#images) on image inputs. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds. + + product_info: Optional product description and specifications to inform creative direction and + which product elements to highlight. + + ratio: The resolution of the output video. + + style_images: Optional style reference images (0–4). Defines the visual treatment (lighting, + palette, mood). Treated as a moodboard when multiple are provided. + + user_concept: Optional creative direction describing brand voice, product framing, scene + specifics, lighting, camera motion, and narrative. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/recipes/product_ad", + body=maybe_transform( + { + "product_images": product_images, + "version": version, + "audio": audio, + "duration": duration, + "product_info": product_info, + "ratio": ratio, + "style_images": style_images, + "user_concept": user_concept, + }, + recipe_product_ad_params.RecipeProductAdParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_waitable_resource(RecipeProductAdResponse, self._client), + ) + + def product_campaign_image( + self, + *, + image: recipe_product_campaign_image_params.Image, + prompt: str, + version: Literal["2026-06", "unsafe-latest"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + """ + Generate four fashion campaign images from a product image and style brief. + + Args: + image: Product image to preserve across the generated campaign. See + [our docs](/assets/inputs#images) on image inputs. + + prompt: Style / creative brief for the fashion campaign, e.g. "High-key fashion + editorial, gorpcore-meets-blokecore-meets-Y2K". + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/recipes/product_campaign_image", + body=maybe_transform( + { + "image": image, + "prompt": prompt, + "version": version, + }, + recipe_product_campaign_image_params.RecipeProductCampaignImageParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_waitable_resource(RecipeProductCampaignImageResponse, self._client), + ) + + def product_swap( + self, + *, + new_product_images: Iterable[recipe_product_swap_params.NewProductImage], + original_product_image: recipe_product_swap_params.OriginalProductImage, + reference_video: recipe_product_swap_params.ReferenceVideo, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: int | Omit = omit, + resolution: Literal["720p", "1080p"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + """ + Replace the product in a reference video with a new product, preserving camera + motion, lighting, and scene composition. + + Args: + new_product_images: Reference images of the new product (1–10). Supply multiple angles when the + reference video shows the product from different views — optionally label each + with `view` ("front", "side", or "back"). A single pre-composed reference sheet + is also supported (omit `view`). See [our docs](/assets/inputs#images) on image + inputs. + + original_product_image: Image of the original product being swapped out. See + [our docs](/assets/inputs#images) on image inputs. + + reference_video: Reference video containing the product to swap. Duration must be between 1.8 and + 15 seconds. See [our docs](/assets/inputs#videos) on video inputs. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds. + + resolution: Output video resolution. Defaults to 720p. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/recipes/product_swap", + body=maybe_transform( + { + "new_product_images": new_product_images, + "original_product_image": original_product_image, + "reference_video": reference_video, + "version": version, + "audio": audio, + "duration": duration, + "resolution": resolution, + }, + recipe_product_swap_params.RecipeProductSwapParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_waitable_resource(RecipeProductSwapResponse, self._client), + ) + + def product_ugc( + self, + *, + character_image: recipe_product_ugc_params.CharacterImage, + product_image: recipe_product_ugc_params.ProductImage, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: int | Omit = omit, + product_info: str | Omit = omit, + ratio: Literal["720:1280", "1080:1920"] | Omit = omit, + user_concept: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NewTaskCreatedResponse: + """ + Generate a vertical user-generated content ad from a character image, product + image, product details, and optional creative direction. + + Args: + character_image: Image of the character who will appear on camera in the UGC video. Aspect ratio + (width / height) must be between 0.4 and 4. See + [our docs](/assets/inputs#images) for image input requirements. + + product_image: Image of the product being promoted. Aspect ratio (width / height) must be + between 0.4 and 4. See [our docs](/assets/inputs#images) for image input + requirements. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Duration of the output video in seconds (4–15). Defaults to 15 seconds. + + product_info: Product details and creative brief — what the product is, key benefits, and any + specifics the script should reference. + + ratio: The resolution of the output video. + + user_concept: Optional creative direction for the UGC video — tone, voice register, specific + message, or an entire dialog script. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v1/recipes/product_ugc", + body=maybe_transform( + { + "character_image": character_image, + "product_image": product_image, + "version": version, + "audio": audio, + "duration": duration, + "product_info": product_info, + "ratio": ratio, + "user_concept": user_concept, + }, + recipe_product_ugc_params.RecipeProductUgcParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_waitable_resource(RecipeProductUgcResponse, self._client), + ) + + +class AsyncRecipesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRecipesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncRecipesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRecipesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response + """ + return AsyncRecipesResourceWithStreamingResponse(self) + + async def marketing_stock_image( + self, + *, + prompt: str, + version: Literal["2026-06", "unsafe-latest"], + reference_image: recipe_marketing_stock_image_params.ReferenceImage | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + """ + Generate a polished marketing stock image from a text brief and optional brand + logo image. + + Args: + prompt: Marketing image brief. Describe the subject, audience, channel, desired mood, + setting, and any constraints. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + reference_image: Optional brand logo image to guide the generated marketing stock image. See + [our docs](/assets/inputs#images) on image inputs. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/recipes/marketing_stock_image", + body=await async_maybe_transform( + { + "prompt": prompt, + "version": version, + "reference_image": reference_image, + }, + recipe_marketing_stock_image_params.RecipeMarketingStockImageParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_async_waitable_resource(RecipeMarketingStockImageResponse, self._client), + ) + + @overload + async def multi_shot_video( + self, + *, + mode: Literal["auto"], + prompt: str, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: Literal[5, 10, 15] | Omit = omit, + first_frame: recipe_multi_shot_video_params.Variant0FirstFrame | Omit = omit, + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + """ + Generate a multi-cut video from a story prompt (auto mode) or a custom shot list + (custom mode). + + Args: + mode: Workflow mode. `auto` decomposes a story prompt into exactly 5 shots. + + prompt: Story prompt for auto mode. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Total duration of the output video in seconds. Defaults to 10 seconds. + + first_frame: Optional image used as the first frame of the output video. See + [our docs](/assets/inputs#images) on image inputs. + + ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`, + `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`, + `1440:1440`) use the pro tier. Defaults to `1280:720`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def multi_shot_video( + self, + *, + mode: Literal["custom"], + shots: Iterable[recipe_multi_shot_video_params.Variant1Shot], + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: Literal[5, 10, 15] | Omit = omit, + first_frame: recipe_multi_shot_video_params.Variant1FirstFrame | Omit = omit, + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + """ + Generate a multi-cut video from a story prompt (auto mode) or a custom shot list + (custom mode). + + Args: + mode: Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots. + + shots: Shot list for custom mode (3–5 shots). Per-shot durations must sum to + `duration`. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Total duration of the output video in seconds. Defaults to 10 seconds. + + first_frame: Optional image used as the first frame of the output video. See + [our docs](/assets/inputs#images) on image inputs. + + ratio: Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`, + `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`, + `1440:1440`) use the pro tier. Defaults to `1280:720`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["mode", "prompt", "version"], ["mode", "shots", "version"]) + async def multi_shot_video( + self, + *, + mode: Literal["auto"] | Literal["custom"], + prompt: str | Omit = omit, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: Literal[5, 10, 15] | Omit = omit, + first_frame: recipe_multi_shot_video_params.Variant0FirstFrame + | recipe_multi_shot_video_params.Variant1FirstFrame + | Omit = omit, + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] | Omit = omit, + shots: Iterable[recipe_multi_shot_video_params.Variant1Shot] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + return await self._post( + "/v1/recipes/multi_shot_video", + body=await async_maybe_transform( + { + "mode": mode, + "prompt": prompt, + "version": version, + "audio": audio, + "duration": duration, + "first_frame": first_frame, + "ratio": ratio, + "shots": shots, + }, + recipe_multi_shot_video_params.RecipeMultiShotVideoParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_async_waitable_resource(RecipeMultiShotVideoResponse, self._client), + ) + + async def product_ad( + self, + *, + product_images: Iterable[recipe_product_ad_params.ProductImage], + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: int | Omit = omit, + product_info: str | Omit = omit, + ratio: Literal[ + "1280:720", "720:1280", "960:960", "834:1112", "1920:1080", "1080:1920", "1440:1440", "1248:1664" + ] + | Omit = omit, + style_images: Iterable[recipe_product_ad_params.StyleImage] | Omit = omit, + user_concept: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + """ + Generate a cinematic product ad from product images, optional style references, + product info, and creative direction. + + Args: + product_images: Product images (1–10). Multiple angles of the same product. All images inform + product analysis and reference generation; only the first image is used as the + primary product reference in the storyboard grid. See + [our docs](/assets/inputs#images) on image inputs. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds. + + product_info: Optional product description and specifications to inform creative direction and + which product elements to highlight. + + ratio: The resolution of the output video. + + style_images: Optional style reference images (0–4). Defines the visual treatment (lighting, + palette, mood). Treated as a moodboard when multiple are provided. + + user_concept: Optional creative direction describing brand voice, product framing, scene + specifics, lighting, camera motion, and narrative. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/recipes/product_ad", + body=await async_maybe_transform( + { + "product_images": product_images, + "version": version, + "audio": audio, + "duration": duration, + "product_info": product_info, + "ratio": ratio, + "style_images": style_images, + "user_concept": user_concept, + }, + recipe_product_ad_params.RecipeProductAdParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_async_waitable_resource(RecipeProductAdResponse, self._client), + ) + + async def product_campaign_image( + self, + *, + image: recipe_product_campaign_image_params.Image, + prompt: str, + version: Literal["2026-06", "unsafe-latest"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + """ + Generate four fashion campaign images from a product image and style brief. + + Args: + image: Product image to preserve across the generated campaign. See + [our docs](/assets/inputs#images) on image inputs. + + prompt: Style / creative brief for the fashion campaign, e.g. "High-key fashion + editorial, gorpcore-meets-blokecore-meets-Y2K". + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/recipes/product_campaign_image", + body=await async_maybe_transform( + { + "image": image, + "prompt": prompt, + "version": version, + }, + recipe_product_campaign_image_params.RecipeProductCampaignImageParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_async_waitable_resource(RecipeProductCampaignImageResponse, self._client), + ) + + async def product_swap( + self, + *, + new_product_images: Iterable[recipe_product_swap_params.NewProductImage], + original_product_image: recipe_product_swap_params.OriginalProductImage, + reference_video: recipe_product_swap_params.ReferenceVideo, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: int | Omit = omit, + resolution: Literal["720p", "1080p"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + """ + Replace the product in a reference video with a new product, preserving camera + motion, lighting, and scene composition. + + Args: + new_product_images: Reference images of the new product (1–10). Supply multiple angles when the + reference video shows the product from different views — optionally label each + with `view` ("front", "side", or "back"). A single pre-composed reference sheet + is also supported (omit `view`). See [our docs](/assets/inputs#images) on image + inputs. + + original_product_image: Image of the original product being swapped out. See + [our docs](/assets/inputs#images) on image inputs. + + reference_video: Reference video containing the product to swap. Duration must be between 1.8 and + 15 seconds. See [our docs](/assets/inputs#videos) on video inputs. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Duration of the output video in seconds (4–15). Defaults to 10 seconds. + + resolution: Output video resolution. Defaults to 720p. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/recipes/product_swap", + body=await async_maybe_transform( + { + "new_product_images": new_product_images, + "original_product_image": original_product_image, + "reference_video": reference_video, + "version": version, + "audio": audio, + "duration": duration, + "resolution": resolution, + }, + recipe_product_swap_params.RecipeProductSwapParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_async_waitable_resource(RecipeProductSwapResponse, self._client), + ) + + async def product_ugc( + self, + *, + character_image: recipe_product_ugc_params.CharacterImage, + product_image: recipe_product_ugc_params.ProductImage, + version: Literal["2026-06", "unsafe-latest"], + audio: bool | Omit = omit, + duration: int | Omit = omit, + product_info: str | Omit = omit, + ratio: Literal["720:1280", "1080:1920"] | Omit = omit, + user_concept: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncNewTaskCreatedResponse: + """ + Generate a vertical user-generated content ad from a character image, product + image, product details, and optional creative direction. + + Args: + character_image: Image of the character who will appear on camera in the UGC video. Aspect ratio + (width / height) must be between 0.4 and 4. See + [our docs](/assets/inputs#images) for image input requirements. + + product_image: Image of the product being promoted. Aspect ratio (width / height) must be + between 0.4 and 4. See [our docs](/assets/inputs#images) for image input + requirements. + + version: Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + "unsafe-latest" to track the newest stable version (may break without notice). + + audio: Whether to generate audio for the video. + + duration: Duration of the output video in seconds (4–15). Defaults to 15 seconds. + + product_info: Product details and creative brief — what the product is, key benefits, and any + specifics the script should reference. + + ratio: The resolution of the output video. + + user_concept: Optional creative direction for the UGC video — tone, voice register, specific + message, or an entire dialog script. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v1/recipes/product_ugc", + body=await async_maybe_transform( + { + "character_image": character_image, + "product_image": product_image, + "version": version, + "audio": audio, + "duration": duration, + "product_info": product_info, + "ratio": ratio, + "user_concept": user_concept, + }, + recipe_product_ugc_params.RecipeProductUgcParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=create_async_waitable_resource(RecipeProductUgcResponse, self._client), + ) + + +class RecipesResourceWithRawResponse: + def __init__(self, recipes: RecipesResource) -> None: + self._recipes = recipes + + self.marketing_stock_image = to_raw_response_wrapper( + recipes.marketing_stock_image, + ) + self.multi_shot_video = to_raw_response_wrapper( + recipes.multi_shot_video, + ) + self.product_ad = to_raw_response_wrapper( + recipes.product_ad, + ) + self.product_campaign_image = to_raw_response_wrapper( + recipes.product_campaign_image, + ) + self.product_swap = to_raw_response_wrapper( + recipes.product_swap, + ) + self.product_ugc = to_raw_response_wrapper( + recipes.product_ugc, + ) + + +class AsyncRecipesResourceWithRawResponse: + def __init__(self, recipes: AsyncRecipesResource) -> None: + self._recipes = recipes + + self.marketing_stock_image = async_to_raw_response_wrapper( + recipes.marketing_stock_image, + ) + self.multi_shot_video = async_to_raw_response_wrapper( + recipes.multi_shot_video, + ) + self.product_ad = async_to_raw_response_wrapper( + recipes.product_ad, + ) + self.product_campaign_image = async_to_raw_response_wrapper( + recipes.product_campaign_image, + ) + self.product_swap = async_to_raw_response_wrapper( + recipes.product_swap, + ) + self.product_ugc = async_to_raw_response_wrapper( + recipes.product_ugc, + ) + + +class RecipesResourceWithStreamingResponse: + def __init__(self, recipes: RecipesResource) -> None: + self._recipes = recipes + + self.marketing_stock_image = to_streamed_response_wrapper( + recipes.marketing_stock_image, + ) + self.multi_shot_video = to_streamed_response_wrapper( + recipes.multi_shot_video, + ) + self.product_ad = to_streamed_response_wrapper( + recipes.product_ad, + ) + self.product_campaign_image = to_streamed_response_wrapper( + recipes.product_campaign_image, + ) + self.product_swap = to_streamed_response_wrapper( + recipes.product_swap, + ) + self.product_ugc = to_streamed_response_wrapper( + recipes.product_ugc, + ) + + +class AsyncRecipesResourceWithStreamingResponse: + def __init__(self, recipes: AsyncRecipesResource) -> None: + self._recipes = recipes + + self.marketing_stock_image = async_to_streamed_response_wrapper( + recipes.marketing_stock_image, + ) + self.multi_shot_video = async_to_streamed_response_wrapper( + recipes.multi_shot_video, + ) + self.product_ad = async_to_streamed_response_wrapper( + recipes.product_ad, + ) + self.product_campaign_image = async_to_streamed_response_wrapper( + recipes.product_campaign_image, + ) + self.product_swap = async_to_streamed_response_wrapper( + recipes.product_swap, + ) + self.product_ugc = async_to_streamed_response_wrapper( + recipes.product_ugc, + ) diff --git a/src/runwayml/resources/video_to_video.py b/src/runwayml/resources/video_to_video.py index 8a41f07..ac68f45 100644 --- a/src/runwayml/resources/video_to_video.py +++ b/src/runwayml/resources/video_to_video.py @@ -57,10 +57,11 @@ def create( self, *, model: Literal["aleph2"], - prompt_text: str, video_uri: str, content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit, keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit, + prompt_text: str | Omit = omit, + ratio: str | Omit = omit, seed: int | Omit = omit, target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -74,9 +75,6 @@ def create( This endpoint will start a new task to generate a video from a video. Args: - prompt_text: A non-empty string up to 1000 characters describing what should appear in the - output. - video_uri: A HTTPS URL. content_moderation: Settings that affect the behavior of the content moderation system. @@ -84,6 +82,9 @@ def create( keyframes: Timed guidance images placed at specific points in the input video. Up to 5 keyframes. + prompt_text: An optional string up to 1000 characters describing what should appear in the + output. + seed: If unspecified, a random number is chosen. Varying the seed integer is a way to get different results for the same other request parameters. Using the same seed integer for an identical request will produce similar results. @@ -246,21 +247,17 @@ def create( """ ... - @required_args(["model", "prompt_text", "video_uri"], ["model", "prompt_video"]) + @required_args(["model", "video_uri"], ["model", "prompt_video"]) def create( self, *, model: Literal["aleph2"] | Literal["seedance2"] | Literal["seedance2_fast"], - prompt_text: str | Omit = omit, video_uri: str | Omit = omit, content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit, keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit, - seed: int | Omit = omit, - target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit, - prompt_video: str | Omit = omit, - audio: bool | Omit = omit, - duration: int | Omit = omit, - ratio: Literal[ + prompt_text: str | Omit = omit, + ratio: str + | Literal[ "992:432", "864:496", "752:560", @@ -295,6 +292,11 @@ def create( "720:1280", ] | Omit = omit, + seed: int | Omit = omit, + target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit, + prompt_video: str | Omit = omit, + audio: bool | Omit = omit, + duration: int | Omit = omit, reference_audio: Iterable[video_to_video_create_params.Seedance2ReferenceAudio] | Iterable[video_to_video_create_params.Seedance2FastReferenceAudio] | Omit = omit, @@ -316,16 +318,16 @@ def create( body=maybe_transform( { "model": model, - "prompt_text": prompt_text, "video_uri": video_uri, "content_moderation": content_moderation, "keyframes": keyframes, + "prompt_text": prompt_text, + "ratio": ratio, "seed": seed, "target_aspect_ratio": target_aspect_ratio, "prompt_video": prompt_video, "audio": audio, "duration": duration, - "ratio": ratio, "reference_audio": reference_audio, "references": references, "reference_videos": reference_videos, @@ -366,10 +368,11 @@ async def create( self, *, model: Literal["aleph2"], - prompt_text: str, video_uri: str, content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit, keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit, + prompt_text: str | Omit = omit, + ratio: str | Omit = omit, seed: int | Omit = omit, target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -383,9 +386,6 @@ async def create( This endpoint will start a new task to generate a video from a video. Args: - prompt_text: A non-empty string up to 1000 characters describing what should appear in the - output. - video_uri: A HTTPS URL. content_moderation: Settings that affect the behavior of the content moderation system. @@ -393,6 +393,9 @@ async def create( keyframes: Timed guidance images placed at specific points in the input video. Up to 5 keyframes. + prompt_text: An optional string up to 1000 characters describing what should appear in the + output. + seed: If unspecified, a random number is chosen. Varying the seed integer is a way to get different results for the same other request parameters. Using the same seed integer for an identical request will produce similar results. @@ -555,21 +558,17 @@ async def create( """ ... - @required_args(["model", "prompt_text", "video_uri"], ["model", "prompt_video"]) + @required_args(["model", "video_uri"], ["model", "prompt_video"]) async def create( self, *, model: Literal["aleph2"] | Literal["seedance2"] | Literal["seedance2_fast"], - prompt_text: str | Omit = omit, video_uri: str | Omit = omit, content_moderation: video_to_video_create_params.Variant0ContentModeration | Omit = omit, keyframes: Iterable[video_to_video_create_params.Variant0Keyframe] | Omit = omit, - seed: int | Omit = omit, - target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit, - prompt_video: str | Omit = omit, - audio: bool | Omit = omit, - duration: int | Omit = omit, - ratio: Literal[ + prompt_text: str | Omit = omit, + ratio: str + | Literal[ "992:432", "864:496", "752:560", @@ -604,6 +603,11 @@ async def create( "720:1280", ] | Omit = omit, + seed: int | Omit = omit, + target_aspect_ratio: Literal["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "21:9"] | Omit = omit, + prompt_video: str | Omit = omit, + audio: bool | Omit = omit, + duration: int | Omit = omit, reference_audio: Iterable[video_to_video_create_params.Seedance2ReferenceAudio] | Iterable[video_to_video_create_params.Seedance2FastReferenceAudio] | Omit = omit, @@ -625,16 +629,16 @@ async def create( body=await async_maybe_transform( { "model": model, - "prompt_text": prompt_text, "video_uri": video_uri, "content_moderation": content_moderation, "keyframes": keyframes, + "prompt_text": prompt_text, + "ratio": ratio, "seed": seed, "target_aspect_ratio": target_aspect_ratio, "prompt_video": prompt_video, "audio": audio, "duration": duration, - "ratio": ratio, "reference_audio": reference_audio, "references": references, "reference_videos": reference_videos, diff --git a/src/runwayml/types/__init__.py b/src/runwayml/types/__init__.py index dc527e6..f51f5af 100644 --- a/src/runwayml/types/__init__.py +++ b/src/runwayml/types/__init__.py @@ -28,17 +28,23 @@ from .voice_retrieve_response import VoiceRetrieveResponse as VoiceRetrieveResponse from .avatar_retrieve_response import AvatarRetrieveResponse as AvatarRetrieveResponse from .document_create_response import DocumentCreateResponse as DocumentCreateResponse +from .recipe_product_ad_params import RecipeProductAdParams as RecipeProductAdParams from .avatar_get_usage_response import AvatarGetUsageResponse as AvatarGetUsageResponse +from .recipe_product_ugc_params import RecipeProductUgcParams as RecipeProductUgcParams from .avatar_video_create_params import AvatarVideoCreateParams as AvatarVideoCreateParams from .document_retrieve_response import DocumentRetrieveResponse as DocumentRetrieveResponse +from .recipe_product_ad_response import RecipeProductAdResponse as RecipeProductAdResponse +from .recipe_product_swap_params import RecipeProductSwapParams as RecipeProductSwapParams from .sound_effect_create_params import SoundEffectCreateParams as SoundEffectCreateParams from .workflow_retrieve_response import WorkflowRetrieveResponse as WorkflowRetrieveResponse from .image_upscale_create_params import ImageUpscaleCreateParams as ImageUpscaleCreateParams +from .recipe_product_ugc_response import RecipeProductUgcResponse as RecipeProductUgcResponse from .text_to_image_create_params import TextToImageCreateParams as TextToImageCreateParams from .text_to_video_create_params import TextToVideoCreateParams as TextToVideoCreateParams from .voice_dubbing_create_params import VoiceDubbingCreateParams as VoiceDubbingCreateParams from .avatar_video_create_response import AvatarVideoCreateResponse as AvatarVideoCreateResponse from .image_to_video_create_params import ImageToVideoCreateParams as ImageToVideoCreateParams +from .recipe_product_swap_response import RecipeProductSwapResponse as RecipeProductSwapResponse from .sound_effect_create_response import SoundEffectCreateResponse as SoundEffectCreateResponse from .text_to_speech_create_params import TextToSpeechCreateParams as TextToSpeechCreateParams from .video_to_video_create_params import VideoToVideoCreateParams as VideoToVideoCreateParams @@ -50,24 +56,34 @@ from .image_to_video_create_response import ImageToVideoCreateResponse as ImageToVideoCreateResponse from .organization_retrieve_response import OrganizationRetrieveResponse as OrganizationRetrieveResponse from .realtime_session_create_params import RealtimeSessionCreateParams as RealtimeSessionCreateParams +from .recipe_multi_shot_video_params import RecipeMultiShotVideoParams as RecipeMultiShotVideoParams from .speech_to_speech_create_params import SpeechToSpeechCreateParams as SpeechToSpeechCreateParams from .text_to_speech_create_response import TextToSpeechCreateResponse as TextToSpeechCreateResponse from .video_to_video_create_response import VideoToVideoCreateResponse as VideoToVideoCreateResponse from .avatar_conversation_list_params import AvatarConversationListParams as AvatarConversationListParams from .voice_isolation_create_response import VoiceIsolationCreateResponse as VoiceIsolationCreateResponse from .realtime_session_create_response import RealtimeSessionCreateResponse as RealtimeSessionCreateResponse +from .recipe_multi_shot_video_response import RecipeMultiShotVideoResponse as RecipeMultiShotVideoResponse from .speech_to_speech_create_response import SpeechToSpeechCreateResponse as SpeechToSpeechCreateResponse from .avatar_conversation_list_response import AvatarConversationListResponse as AvatarConversationListResponse from .organization_retrieve_usage_params import OrganizationRetrieveUsageParams as OrganizationRetrieveUsageParams from .realtime_session_retrieve_response import RealtimeSessionRetrieveResponse as RealtimeSessionRetrieveResponse from .character_performance_create_params import CharacterPerformanceCreateParams as CharacterPerformanceCreateParams +from .recipe_marketing_stock_image_params import RecipeMarketingStockImageParams as RecipeMarketingStockImageParams from .organization_retrieve_usage_response import OrganizationRetrieveUsageResponse as OrganizationRetrieveUsageResponse +from .recipe_product_campaign_image_params import RecipeProductCampaignImageParams as RecipeProductCampaignImageParams from .avatar_conversation_retrieve_response import ( AvatarConversationRetrieveResponse as AvatarConversationRetrieveResponse, ) from .character_performance_create_response import ( CharacterPerformanceCreateResponse as CharacterPerformanceCreateResponse, ) +from .recipe_marketing_stock_image_response import ( + RecipeMarketingStockImageResponse as RecipeMarketingStockImageResponse, +) from .workflow_invocation_retrieve_response import ( WorkflowInvocationRetrieveResponse as WorkflowInvocationRetrieveResponse, ) +from .recipe_product_campaign_image_response import ( + RecipeProductCampaignImageResponse as RecipeProductCampaignImageResponse, +) diff --git a/src/runwayml/types/organization_retrieve_usage_response.py b/src/runwayml/types/organization_retrieve_usage_response.py index bea62de..e077547 100644 --- a/src/runwayml/types/organization_retrieve_usage_response.py +++ b/src/runwayml/types/organization_retrieve_usage_response.py @@ -57,6 +57,11 @@ class ResultUsedCredit(BaseModel): "happyhorse_1_0", "aleph2", "product_swap", + "product_ad", + "multi_shot_video", + "product_ugc", + "marketing_stock_image", + "product_campaign_image", ] """The model that credits were spent on.""" @@ -113,6 +118,11 @@ class OrganizationRetrieveUsageResponse(BaseModel): "happyhorse_1_0", "aleph2", "product_swap", + "product_ad", + "multi_shot_video", + "product_ugc", + "marketing_stock_image", + "product_campaign_image", ] ] """The list of models with usage during the queried time range.""" diff --git a/src/runwayml/types/recipe_marketing_stock_image_params.py b/src/runwayml/types/recipe_marketing_stock_image_params.py new file mode 100644 index 0000000..c316f87 --- /dev/null +++ b/src/runwayml/types/recipe_marketing_stock_image_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["RecipeMarketingStockImageParams", "ReferenceImage"] + + +class RecipeMarketingStockImageParams(TypedDict, total=False): + prompt: Required[str] + """Marketing image brief. + + Describe the subject, audience, channel, desired mood, setting, and any + constraints. + """ + + version: Required[Literal["2026-06", "unsafe-latest"]] + """Workflow version. + + Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to + track the newest stable version (may break without notice). + """ + + reference_image: Annotated[ReferenceImage, PropertyInfo(alias="referenceImage")] + """Optional brand logo image to guide the generated marketing stock image. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + +class ReferenceImage(TypedDict, total=False): + """Optional brand logo image to guide the generated marketing stock image. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + uri: Required[str] + """A HTTPS URL.""" diff --git a/src/runwayml/types/recipe_marketing_stock_image_response.py b/src/runwayml/types/recipe_marketing_stock_image_response.py new file mode 100644 index 0000000..ecb1cdc --- /dev/null +++ b/src/runwayml/types/recipe_marketing_stock_image_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["RecipeMarketingStockImageResponse"] + + +class RecipeMarketingStockImageResponse(BaseModel): + id: str + """The ID of the task that was created. Use this to retrieve the task later.""" diff --git a/src/runwayml/types/recipe_multi_shot_video_params.py b/src/runwayml/types/recipe_multi_shot_video_params.py new file mode 100644 index 0000000..9e1e738 --- /dev/null +++ b/src/runwayml/types/recipe_multi_shot_video_params.py @@ -0,0 +1,121 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict + +from .._utils import PropertyInfo + +__all__ = [ + "RecipeMultiShotVideoParams", + "Variant0", + "Variant0FirstFrame", + "Variant1", + "Variant1Shot", + "Variant1FirstFrame", +] + + +class Variant0(TypedDict, total=False): + mode: Required[Literal["auto"]] + """Workflow mode. `auto` decomposes a story prompt into exactly 5 shots.""" + + prompt: Required[str] + """Story prompt for auto mode.""" + + version: Required[Literal["2026-06", "unsafe-latest"]] + """Workflow version. + + Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to + track the newest stable version (may break without notice). + """ + + audio: bool + """Whether to generate audio for the video.""" + + duration: Literal[5, 10, 15] + """Total duration of the output video in seconds. Defaults to 10 seconds.""" + + first_frame: Annotated[Variant0FirstFrame, PropertyInfo(alias="firstFrame")] + """Optional image used as the first frame of the output video. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] + """Output dimensions as width:height. + + 720p ratios (`1280:720`, `720:1280`, `960:960`) use the standard tier; 1080p + ratios (`1920:1080`, `1080:1920`, `1440:1440`) use the pro tier. Defaults to + `1280:720`. + """ + + +class Variant0FirstFrame(TypedDict, total=False): + """Optional image used as the first frame of the output video. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + uri: Required[str] + """A HTTPS URL.""" + + +class Variant1(TypedDict, total=False): + mode: Required[Literal["custom"]] + """Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots.""" + + shots: Required[Iterable[Variant1Shot]] + """Shot list for custom mode (3–5 shots). + + Per-shot durations must sum to `duration`. + """ + + version: Required[Literal["2026-06", "unsafe-latest"]] + """Workflow version. + + Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to + track the newest stable version (may break without notice). + """ + + audio: bool + """Whether to generate audio for the video.""" + + duration: Literal[5, 10, 15] + """Total duration of the output video in seconds. Defaults to 10 seconds.""" + + first_frame: Annotated[Variant1FirstFrame, PropertyInfo(alias="firstFrame")] + """Optional image used as the first frame of the output video. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + ratio: Literal["1280:720", "720:1280", "960:960", "1920:1080", "1080:1920", "1440:1440"] + """Output dimensions as width:height. + + 720p ratios (`1280:720`, `720:1280`, `960:960`) use the standard tier; 1080p + ratios (`1920:1080`, `1080:1920`, `1440:1440`) use the pro tier. Defaults to + `1280:720`. + """ + + +class Variant1Shot(TypedDict, total=False): + duration: Required[int] + """Duration of this shot in seconds.""" + + prompt: Required[str] + """Shot description prompt.""" + + +class Variant1FirstFrame(TypedDict, total=False): + """Optional image used as the first frame of the output video. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + uri: Required[str] + """A HTTPS URL.""" + + +RecipeMultiShotVideoParams: TypeAlias = Union[Variant0, Variant1] diff --git a/src/runwayml/types/recipe_multi_shot_video_response.py b/src/runwayml/types/recipe_multi_shot_video_response.py new file mode 100644 index 0000000..bf74b03 --- /dev/null +++ b/src/runwayml/types/recipe_multi_shot_video_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["RecipeMultiShotVideoResponse"] + + +class RecipeMultiShotVideoResponse(BaseModel): + id: str + """The ID of the task that was created. Use this to retrieve the task later.""" diff --git a/src/runwayml/types/recipe_product_ad_params.py b/src/runwayml/types/recipe_product_ad_params.py new file mode 100644 index 0000000..3b72e39 --- /dev/null +++ b/src/runwayml/types/recipe_product_ad_params.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["RecipeProductAdParams", "ProductImage", "StyleImage"] + + +class RecipeProductAdParams(TypedDict, total=False): + product_images: Required[Annotated[Iterable[ProductImage], PropertyInfo(alias="productImages")]] + """Product images (1–10). + + Multiple angles of the same product. All images inform product analysis and + reference generation; only the first image is used as the primary product + reference in the storyboard grid. See [our docs](/assets/inputs#images) on image + inputs. + """ + + version: Required[Literal["2026-06", "unsafe-latest"]] + """Workflow version. + + Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to + track the newest stable version (may break without notice). + """ + + audio: bool + """Whether to generate audio for the video.""" + + duration: int + """Duration of the output video in seconds (4–15). Defaults to 10 seconds.""" + + product_info: Annotated[str, PropertyInfo(alias="productInfo")] + """ + Optional product description and specifications to inform creative direction and + which product elements to highlight. + """ + + ratio: Literal["1280:720", "720:1280", "960:960", "834:1112", "1920:1080", "1080:1920", "1440:1440", "1248:1664"] + """The resolution of the output video.""" + + style_images: Annotated[Iterable[StyleImage], PropertyInfo(alias="styleImages")] + """Optional style reference images (0–4). + + Defines the visual treatment (lighting, palette, mood). Treated as a moodboard + when multiple are provided. + """ + + user_concept: Annotated[str, PropertyInfo(alias="userConcept")] + """ + Optional creative direction describing brand voice, product framing, scene + specifics, lighting, camera motion, and narrative. + """ + + +class ProductImage(TypedDict, total=False): + uri: Required[str] + """A HTTPS URL.""" + + +class StyleImage(TypedDict, total=False): + uri: Required[str] + """A HTTPS URL.""" diff --git a/src/runwayml/types/recipe_product_ad_response.py b/src/runwayml/types/recipe_product_ad_response.py new file mode 100644 index 0000000..5e1e45e --- /dev/null +++ b/src/runwayml/types/recipe_product_ad_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["RecipeProductAdResponse"] + + +class RecipeProductAdResponse(BaseModel): + id: str + """The ID of the task that was created. Use this to retrieve the task later.""" diff --git a/src/runwayml/types/recipe_product_campaign_image_params.py b/src/runwayml/types/recipe_product_campaign_image_params.py new file mode 100644 index 0000000..e5ff539 --- /dev/null +++ b/src/runwayml/types/recipe_product_campaign_image_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["RecipeProductCampaignImageParams", "Image"] + + +class RecipeProductCampaignImageParams(TypedDict, total=False): + image: Required[Image] + """Product image to preserve across the generated campaign. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + prompt: Required[str] + """Style / creative brief for the fashion campaign, e.g. + + "High-key fashion editorial, gorpcore-meets-blokecore-meets-Y2K". + """ + + version: Required[Literal["2026-06", "unsafe-latest"]] + """Workflow version. + + Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to + track the newest stable version (may break without notice). + """ + + +class Image(TypedDict, total=False): + """Product image to preserve across the generated campaign. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + uri: Required[str] + """A HTTPS URL.""" diff --git a/src/runwayml/types/recipe_product_campaign_image_response.py b/src/runwayml/types/recipe_product_campaign_image_response.py new file mode 100644 index 0000000..5a4dee4 --- /dev/null +++ b/src/runwayml/types/recipe_product_campaign_image_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["RecipeProductCampaignImageResponse"] + + +class RecipeProductCampaignImageResponse(BaseModel): + id: str + """The ID of the task that was created. Use this to retrieve the task later.""" diff --git a/src/runwayml/types/recipe_product_swap_params.py b/src/runwayml/types/recipe_product_swap_params.py new file mode 100644 index 0000000..21382e7 --- /dev/null +++ b/src/runwayml/types/recipe_product_swap_params.py @@ -0,0 +1,81 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["RecipeProductSwapParams", "NewProductImage", "OriginalProductImage", "ReferenceVideo"] + + +class RecipeProductSwapParams(TypedDict, total=False): + new_product_images: Required[Annotated[Iterable[NewProductImage], PropertyInfo(alias="newProductImages")]] + """Reference images of the new product (1–10). + + Supply multiple angles when the reference video shows the product from different + views — optionally label each with `view` ("front", "side", or "back"). A single + pre-composed reference sheet is also supported (omit `view`). See + [our docs](/assets/inputs#images) on image inputs. + """ + + original_product_image: Required[Annotated[OriginalProductImage, PropertyInfo(alias="originalProductImage")]] + """Image of the original product being swapped out. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + reference_video: Required[Annotated[ReferenceVideo, PropertyInfo(alias="referenceVideo")]] + """Reference video containing the product to swap. + + Duration must be between 1.8 and 15 seconds. See + [our docs](/assets/inputs#videos) on video inputs. + """ + + version: Required[Literal["2026-06", "unsafe-latest"]] + """Workflow version. + + Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to + track the newest stable version (may break without notice). + """ + + audio: bool + """Whether to generate audio for the video.""" + + duration: int + """Duration of the output video in seconds (4–15). Defaults to 10 seconds.""" + + resolution: Literal["720p", "1080p"] + """Output video resolution. Defaults to 720p.""" + + +class NewProductImage(TypedDict, total=False): + uri: Required[str] + """A HTTPS URL.""" + + view: Literal["front", "side", "back"] + """Optional view label for this reference (front, side, or back). + + Omit when supplying a single reference sheet or when view labels are unknown. + """ + + +class OriginalProductImage(TypedDict, total=False): + """Image of the original product being swapped out. + + See [our docs](/assets/inputs#images) on image inputs. + """ + + uri: Required[str] + """A HTTPS URL.""" + + +class ReferenceVideo(TypedDict, total=False): + """Reference video containing the product to swap. + + Duration must be between 1.8 and 15 seconds. See [our docs](/assets/inputs#videos) on video inputs. + """ + + uri: Required[str] + """A HTTPS URL.""" diff --git a/src/runwayml/types/recipe_product_swap_response.py b/src/runwayml/types/recipe_product_swap_response.py new file mode 100644 index 0000000..2152bc6 --- /dev/null +++ b/src/runwayml/types/recipe_product_swap_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["RecipeProductSwapResponse"] + + +class RecipeProductSwapResponse(BaseModel): + id: str + """The ID of the task that was created. Use this to retrieve the task later.""" diff --git a/src/runwayml/types/recipe_product_ugc_params.py b/src/runwayml/types/recipe_product_ugc_params.py new file mode 100644 index 0000000..c45bed9 --- /dev/null +++ b/src/runwayml/types/recipe_product_ugc_params.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["RecipeProductUgcParams", "CharacterImage", "ProductImage"] + + +class RecipeProductUgcParams(TypedDict, total=False): + character_image: Required[Annotated[CharacterImage, PropertyInfo(alias="characterImage")]] + """Image of the character who will appear on camera in the UGC video. + + Aspect ratio (width / height) must be between 0.4 and 4. See + [our docs](/assets/inputs#images) for image input requirements. + """ + + product_image: Required[Annotated[ProductImage, PropertyInfo(alias="productImage")]] + """Image of the product being promoted. + + Aspect ratio (width / height) must be between 0.4 and 4. See + [our docs](/assets/inputs#images) for image input requirements. + """ + + version: Required[Literal["2026-06", "unsafe-latest"]] + """Workflow version. + + Use a dated version (e.g. "2026-06") to pin behavior, or "unsafe-latest" to + track the newest stable version (may break without notice). + """ + + audio: bool + """Whether to generate audio for the video.""" + + duration: int + """Duration of the output video in seconds (4–15). Defaults to 15 seconds.""" + + product_info: Annotated[str, PropertyInfo(alias="productInfo")] + """ + Product details and creative brief — what the product is, key benefits, and any + specifics the script should reference. + """ + + ratio: Literal["720:1280", "1080:1920"] + """The resolution of the output video.""" + + user_concept: Annotated[str, PropertyInfo(alias="userConcept")] + """ + Optional creative direction for the UGC video — tone, voice register, specific + message, or an entire dialog script. + """ + + +class CharacterImage(TypedDict, total=False): + """Image of the character who will appear on camera in the UGC video. + + Aspect ratio (width / height) must be between 0.4 and 4. See [our docs](/assets/inputs#images) for image input requirements. + """ + + uri: Required[str] + """A HTTPS URL.""" + + +class ProductImage(TypedDict, total=False): + """Image of the product being promoted. + + Aspect ratio (width / height) must be between 0.4 and 4. See [our docs](/assets/inputs#images) for image input requirements. + """ + + uri: Required[str] + """A HTTPS URL.""" diff --git a/src/runwayml/types/recipe_product_ugc_response.py b/src/runwayml/types/recipe_product_ugc_response.py new file mode 100644 index 0000000..fc0d0bc --- /dev/null +++ b/src/runwayml/types/recipe_product_ugc_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["RecipeProductUgcResponse"] + + +class RecipeProductUgcResponse(BaseModel): + id: str + """The ID of the task that was created. Use this to retrieve the task later.""" diff --git a/src/runwayml/types/video_to_video_create_params.py b/src/runwayml/types/video_to_video_create_params.py index cea862a..4156d31 100644 --- a/src/runwayml/types/video_to_video_create_params.py +++ b/src/runwayml/types/video_to_video_create_params.py @@ -28,12 +28,6 @@ class Variant0(TypedDict, total=False): model: Required[Literal["aleph2"]] - prompt_text: Required[Annotated[str, PropertyInfo(alias="promptText")]] - """ - A non-empty string up to 1000 characters describing what should appear in the - output. - """ - video_uri: Required[Annotated[str, PropertyInfo(alias="videoUri")]] """A HTTPS URL.""" @@ -46,6 +40,14 @@ class Variant0(TypedDict, total=False): Up to 5 keyframes. """ + prompt_text: Annotated[str, PropertyInfo(alias="promptText")] + """ + An optional string up to 1000 characters describing what should appear in the + output. + """ + + ratio: str + seed: int """If unspecified, a random number is chosen. diff --git a/tests/api_resources/test_recipes.py b/tests/api_resources/test_recipes.py new file mode 100644 index 0000000..6288565 --- /dev/null +++ b/tests/api_resources/test_recipes.py @@ -0,0 +1,809 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from runwayml import RunwayML, AsyncRunwayML +from tests.utils import assert_matches_type +from runwayml.types import ( + RecipeProductAdResponse, + RecipeProductUgcResponse, + RecipeProductSwapResponse, + RecipeMultiShotVideoResponse, + RecipeMarketingStockImageResponse, + RecipeProductCampaignImageResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRecipes: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_marketing_stock_image(self, client: RunwayML) -> None: + recipe = client.recipes.marketing_stock_image( + prompt="x", + version="2026-06", + ) + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + @parametrize + def test_method_marketing_stock_image_with_all_params(self, client: RunwayML) -> None: + recipe = client.recipes.marketing_stock_image( + prompt="x", + version="2026-06", + reference_image={"uri": "https://example.com/file"}, + ) + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + @parametrize + def test_raw_response_marketing_stock_image(self, client: RunwayML) -> None: + response = client.recipes.with_raw_response.marketing_stock_image( + prompt="x", + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = response.parse() + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + @parametrize + def test_streaming_response_marketing_stock_image(self, client: RunwayML) -> None: + with client.recipes.with_streaming_response.marketing_stock_image( + prompt="x", + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = response.parse() + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_multi_shot_video_overload_1(self, client: RunwayML) -> None: + recipe = client.recipes.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + def test_method_multi_shot_video_with_all_params_overload_1(self, client: RunwayML) -> None: + recipe = client.recipes.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + audio=True, + duration=5, + first_frame={"uri": "https://example.com/file"}, + ratio="1280:720", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + def test_raw_response_multi_shot_video_overload_1(self, client: RunwayML) -> None: + response = client.recipes.with_raw_response.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + def test_streaming_response_multi_shot_video_overload_1(self, client: RunwayML) -> None: + with client.recipes.with_streaming_response.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_multi_shot_video_overload_2(self, client: RunwayML) -> None: + recipe = client.recipes.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + def test_method_multi_shot_video_with_all_params_overload_2(self, client: RunwayML) -> None: + recipe = client.recipes.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + audio=True, + duration=5, + first_frame={"uri": "https://example.com/file"}, + ratio="1280:720", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + def test_raw_response_multi_shot_video_overload_2(self, client: RunwayML) -> None: + response = client.recipes.with_raw_response.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + def test_streaming_response_multi_shot_video_overload_2(self, client: RunwayML) -> None: + with client.recipes.with_streaming_response.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_product_ad(self, client: RunwayML) -> None: + recipe = client.recipes.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + ) + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + @parametrize + def test_method_product_ad_with_all_params(self, client: RunwayML) -> None: + recipe = client.recipes.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + audio=True, + duration=4, + product_info="productInfo", + ratio="1280:720", + style_images=[{"uri": "https://example.com/file"}], + user_concept="userConcept", + ) + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + @parametrize + def test_raw_response_product_ad(self, client: RunwayML) -> None: + response = client.recipes.with_raw_response.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = response.parse() + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + @parametrize + def test_streaming_response_product_ad(self, client: RunwayML) -> None: + with client.recipes.with_streaming_response.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = response.parse() + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_product_campaign_image(self, client: RunwayML) -> None: + recipe = client.recipes.product_campaign_image( + image={"uri": "https://example.com/file"}, + prompt="x", + version="2026-06", + ) + assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"]) + + @parametrize + def test_raw_response_product_campaign_image(self, client: RunwayML) -> None: + response = client.recipes.with_raw_response.product_campaign_image( + image={"uri": "https://example.com/file"}, + prompt="x", + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = response.parse() + assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"]) + + @parametrize + def test_streaming_response_product_campaign_image(self, client: RunwayML) -> None: + with client.recipes.with_streaming_response.product_campaign_image( + image={"uri": "https://example.com/file"}, + prompt="x", + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = response.parse() + assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_product_swap(self, client: RunwayML) -> None: + recipe = client.recipes.product_swap( + new_product_images=[{"uri": "https://example.com/file"}], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + ) + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + @parametrize + def test_method_product_swap_with_all_params(self, client: RunwayML) -> None: + recipe = client.recipes.product_swap( + new_product_images=[ + { + "uri": "https://example.com/file", + "view": "front", + } + ], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + audio=True, + duration=4, + resolution="720p", + ) + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + @parametrize + def test_raw_response_product_swap(self, client: RunwayML) -> None: + response = client.recipes.with_raw_response.product_swap( + new_product_images=[{"uri": "https://example.com/file"}], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = response.parse() + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + @parametrize + def test_streaming_response_product_swap(self, client: RunwayML) -> None: + with client.recipes.with_streaming_response.product_swap( + new_product_images=[{"uri": "https://example.com/file"}], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = response.parse() + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_product_ugc(self, client: RunwayML) -> None: + recipe = client.recipes.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + ) + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + @parametrize + def test_method_product_ugc_with_all_params(self, client: RunwayML) -> None: + recipe = client.recipes.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + audio=True, + duration=4, + product_info="productInfo", + ratio="720:1280", + user_concept="userConcept", + ) + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + @parametrize + def test_raw_response_product_ugc(self, client: RunwayML) -> None: + response = client.recipes.with_raw_response.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = response.parse() + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + @parametrize + def test_streaming_response_product_ugc(self, client: RunwayML) -> None: + with client.recipes.with_streaming_response.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = response.parse() + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncRecipes: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_marketing_stock_image(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.marketing_stock_image( + prompt="x", + version="2026-06", + ) + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + @parametrize + async def test_method_marketing_stock_image_with_all_params(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.marketing_stock_image( + prompt="x", + version="2026-06", + reference_image={"uri": "https://example.com/file"}, + ) + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + @parametrize + async def test_raw_response_marketing_stock_image(self, async_client: AsyncRunwayML) -> None: + response = await async_client.recipes.with_raw_response.marketing_stock_image( + prompt="x", + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = await response.parse() + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + @parametrize + async def test_streaming_response_marketing_stock_image(self, async_client: AsyncRunwayML) -> None: + async with async_client.recipes.with_streaming_response.marketing_stock_image( + prompt="x", + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = await response.parse() + assert_matches_type(RecipeMarketingStockImageResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_multi_shot_video_overload_1(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + async def test_method_multi_shot_video_with_all_params_overload_1(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + audio=True, + duration=5, + first_frame={"uri": "https://example.com/file"}, + ratio="1280:720", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + async def test_raw_response_multi_shot_video_overload_1(self, async_client: AsyncRunwayML) -> None: + response = await async_client.recipes.with_raw_response.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = await response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + async def test_streaming_response_multi_shot_video_overload_1(self, async_client: AsyncRunwayML) -> None: + async with async_client.recipes.with_streaming_response.multi_shot_video( + mode="auto", + prompt="x", + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = await response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_multi_shot_video_overload_2(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + async def test_method_multi_shot_video_with_all_params_overload_2(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + audio=True, + duration=5, + first_frame={"uri": "https://example.com/file"}, + ratio="1280:720", + ) + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + async def test_raw_response_multi_shot_video_overload_2(self, async_client: AsyncRunwayML) -> None: + response = await async_client.recipes.with_raw_response.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = await response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + @parametrize + async def test_streaming_response_multi_shot_video_overload_2(self, async_client: AsyncRunwayML) -> None: + async with async_client.recipes.with_streaming_response.multi_shot_video( + mode="custom", + shots=[ + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + { + "duration": 1, + "prompt": "xxx", + }, + ], + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = await response.parse() + assert_matches_type(RecipeMultiShotVideoResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_product_ad(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + ) + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + @parametrize + async def test_method_product_ad_with_all_params(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + audio=True, + duration=4, + product_info="productInfo", + ratio="1280:720", + style_images=[{"uri": "https://example.com/file"}], + user_concept="userConcept", + ) + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + @parametrize + async def test_raw_response_product_ad(self, async_client: AsyncRunwayML) -> None: + response = await async_client.recipes.with_raw_response.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = await response.parse() + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + @parametrize + async def test_streaming_response_product_ad(self, async_client: AsyncRunwayML) -> None: + async with async_client.recipes.with_streaming_response.product_ad( + product_images=[{"uri": "https://example.com/file"}], + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = await response.parse() + assert_matches_type(RecipeProductAdResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_product_campaign_image(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.product_campaign_image( + image={"uri": "https://example.com/file"}, + prompt="x", + version="2026-06", + ) + assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"]) + + @parametrize + async def test_raw_response_product_campaign_image(self, async_client: AsyncRunwayML) -> None: + response = await async_client.recipes.with_raw_response.product_campaign_image( + image={"uri": "https://example.com/file"}, + prompt="x", + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = await response.parse() + assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"]) + + @parametrize + async def test_streaming_response_product_campaign_image(self, async_client: AsyncRunwayML) -> None: + async with async_client.recipes.with_streaming_response.product_campaign_image( + image={"uri": "https://example.com/file"}, + prompt="x", + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = await response.parse() + assert_matches_type(RecipeProductCampaignImageResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_product_swap(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.product_swap( + new_product_images=[{"uri": "https://example.com/file"}], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + ) + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + @parametrize + async def test_method_product_swap_with_all_params(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.product_swap( + new_product_images=[ + { + "uri": "https://example.com/file", + "view": "front", + } + ], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + audio=True, + duration=4, + resolution="720p", + ) + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + @parametrize + async def test_raw_response_product_swap(self, async_client: AsyncRunwayML) -> None: + response = await async_client.recipes.with_raw_response.product_swap( + new_product_images=[{"uri": "https://example.com/file"}], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = await response.parse() + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + @parametrize + async def test_streaming_response_product_swap(self, async_client: AsyncRunwayML) -> None: + async with async_client.recipes.with_streaming_response.product_swap( + new_product_images=[{"uri": "https://example.com/file"}], + original_product_image={"uri": "https://example.com/file"}, + reference_video={"uri": "https://example.com/file"}, + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = await response.parse() + assert_matches_type(RecipeProductSwapResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_product_ugc(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + ) + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + @parametrize + async def test_method_product_ugc_with_all_params(self, async_client: AsyncRunwayML) -> None: + recipe = await async_client.recipes.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + audio=True, + duration=4, + product_info="productInfo", + ratio="720:1280", + user_concept="userConcept", + ) + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + @parametrize + async def test_raw_response_product_ugc(self, async_client: AsyncRunwayML) -> None: + response = await async_client.recipes.with_raw_response.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + recipe = await response.parse() + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + @parametrize + async def test_streaming_response_product_ugc(self, async_client: AsyncRunwayML) -> None: + async with async_client.recipes.with_streaming_response.product_ugc( + character_image={"uri": "https://example.com/file"}, + product_image={"uri": "https://example.com/file"}, + version="2026-06", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + recipe = await response.parse() + assert_matches_type(RecipeProductUgcResponse, recipe, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_video_to_video.py b/tests/api_resources/test_video_to_video.py index 151169a..10a6894 100644 --- a/tests/api_resources/test_video_to_video.py +++ b/tests/api_resources/test_video_to_video.py @@ -21,7 +21,6 @@ class TestVideoToVideo: def test_method_create_overload_1(self, client: RunwayML) -> None: video_to_video = client.video_to_video.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", ) assert_matches_type(VideoToVideoCreateResponse, video_to_video, path=["response"]) @@ -30,7 +29,6 @@ def test_method_create_overload_1(self, client: RunwayML) -> None: def test_method_create_with_all_params_overload_1(self, client: RunwayML) -> None: video_to_video = client.video_to_video.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", content_moderation={"public_figure_threshold": "auto"}, keyframes=[ @@ -39,6 +37,8 @@ def test_method_create_with_all_params_overload_1(self, client: RunwayML) -> Non "uri": "https://example.com/file", } ], + prompt_text="x", + ratio="ratio", seed=0, target_aspect_ratio="16:9", ) @@ -48,7 +48,6 @@ def test_method_create_with_all_params_overload_1(self, client: RunwayML) -> Non def test_raw_response_create_overload_1(self, client: RunwayML) -> None: response = client.video_to_video.with_raw_response.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", ) @@ -61,7 +60,6 @@ def test_raw_response_create_overload_1(self, client: RunwayML) -> None: def test_streaming_response_create_overload_1(self, client: RunwayML) -> None: with client.video_to_video.with_streaming_response.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", ) as response: assert not response.is_closed @@ -210,7 +208,6 @@ class TestAsyncVideoToVideo: async def test_method_create_overload_1(self, async_client: AsyncRunwayML) -> None: video_to_video = await async_client.video_to_video.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", ) assert_matches_type(VideoToVideoCreateResponse, video_to_video, path=["response"]) @@ -219,7 +216,6 @@ async def test_method_create_overload_1(self, async_client: AsyncRunwayML) -> No async def test_method_create_with_all_params_overload_1(self, async_client: AsyncRunwayML) -> None: video_to_video = await async_client.video_to_video.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", content_moderation={"public_figure_threshold": "auto"}, keyframes=[ @@ -228,6 +224,8 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn "uri": "https://example.com/file", } ], + prompt_text="x", + ratio="ratio", seed=0, target_aspect_ratio="16:9", ) @@ -237,7 +235,6 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn async def test_raw_response_create_overload_1(self, async_client: AsyncRunwayML) -> None: response = await async_client.video_to_video.with_raw_response.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", ) @@ -250,7 +247,6 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncRunwayML) async def test_streaming_response_create_overload_1(self, async_client: AsyncRunwayML) -> None: async with async_client.video_to_video.with_streaming_response.create( model="aleph2", - prompt_text="x", video_uri="https://example.com/video.mp4", ) as response: assert not response.is_closed diff --git a/uv.lock b/uv.lock index c6c62ac..ee1b946 100644 --- a/uv.lock +++ b/uv.lock @@ -886,7 +886,7 @@ wheels = [ [[package]] name = "runwayml" -version = "4.18.0" +version = "5.1.0" source = { editable = "." } dependencies = [ { name = "anyio", version = "4.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },