diff --git a/magic_hour/resources/v1/audio_to_video/README.md b/magic_hour/resources/v1/audio_to_video/README.md
index 63baee1..920a028 100644
--- a/magic_hour/resources/v1/audio_to_video/README.md
+++ b/magic_hour/resources/v1/audio_to_video/README.md
@@ -2,6 +2,76 @@
## Module Functions
+
+
+### Audio To Video Generate Workflow
+
+The workflow performs the following action
+
+1. upload local assets to Magic Hour storage. So you can pass in a local path instead of having to upload files yourself
+2. trigger a generation
+3. poll for a completion status. This is configurable
+4. if success, download the output to local directory
+
+> [!TIP]
+> This is the recommended way to use the SDK unless you have specific needs where it is necessary to split up the actions.
+
+#### Parameters
+
+In Additional to the parameters listed in the `.create` section below, `.generate` introduces 3 new parameters:
+
+- `wait_for_completion` (bool, default True): Whether to wait for the project to complete.
+- `download_outputs` (bool, default True): Whether to download the generated files
+- `download_directory` (str, optional): Directory to save downloaded files (defaults to current directory)
+
+#### Synchronous Client
+
+```python
+from magic_hour import Client
+from os import getenv
+
+client = Client(token=getenv("API_TOKEN"))
+res = client.v1.audio_to_video.generate(
+ assets={
+ "audio_file_path": "/path/to/1234.mp3",
+ "image_file_path": "/path/to/1234.png",
+ },
+ end_seconds=15.0,
+ name="My Audio To Video video",
+ resolution="720p",
+ start_seconds=0.0,
+ style={"prompt": "Car driving through a city"},
+ wait_for_completion=True,
+ download_outputs=True,
+ download_directory="."
+)
+```
+
+#### Asynchronous Client
+
+```python
+from magic_hour import AsyncClient
+from os import getenv
+
+client = AsyncClient(token=getenv("API_TOKEN"))
+res = await client.v1.audio_to_video.generate(
+ assets={
+ "audio_file_path": "/path/to/1234.mp3",
+ "image_file_path": "/path/to/1234.png",
+ },
+ end_seconds=15.0,
+ name="My Audio To Video video",
+ resolution="720p",
+ start_seconds=0.0,
+ style={"prompt": "Car driving through a city"},
+ wait_for_completion=True,
+ download_outputs=True,
+ download_directory="."
+)
+```
+
+
+
### Audio-to-Video
**What this API does**
diff --git a/magic_hour/resources/v1/audio_to_video/client.py b/magic_hour/resources/v1/audio_to_video/client.py
index 78c6a0a..770d4b6 100644
--- a/magic_hour/resources/v1/audio_to_video/client.py
+++ b/magic_hour/resources/v1/audio_to_video/client.py
@@ -1,6 +1,12 @@
import typing
import typing_extensions
+from magic_hour.helpers.logger import get_sdk_logger
+from magic_hour.resources.v1.files.client import AsyncFilesClient, FilesClient
+from magic_hour.resources.v1.video_projects.client import (
+ AsyncVideoProjectsClient,
+ VideoProjectsClient,
+)
from magic_hour.types import models, params
from make_api_request import (
AsyncBaseClient,
@@ -12,10 +18,104 @@
)
+logger = get_sdk_logger(__name__)
+
+
class AudioToVideoClient:
def __init__(self, *, base_client: SyncBaseClient):
self._base_client = base_client
+ def generate(
+ self,
+ *,
+ assets: params.V1AudioToVideoGenerateBodyAssets,
+ end_seconds: float,
+ name: typing.Union[
+ typing.Optional[str], type_utils.NotGiven
+ ] = type_utils.NOT_GIVEN,
+ resolution: typing.Union[
+ typing.Optional[typing_extensions.Literal["1080p", "480p", "720p"]],
+ type_utils.NotGiven,
+ ] = type_utils.NOT_GIVEN,
+ start_seconds: typing.Union[
+ typing.Optional[float], type_utils.NotGiven
+ ] = type_utils.NOT_GIVEN,
+ style: typing.Union[
+ typing.Optional[params.V1AudioToVideoCreateBodyStyle], type_utils.NotGiven
+ ] = type_utils.NOT_GIVEN,
+ wait_for_completion: bool = True,
+ download_outputs: bool = True,
+ download_directory: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ):
+ """
+ Generate audio-to-video (alias for create with additional functionality).
+
+ Create an Audio To Video video. Credits are only charged for the frames that actually render.
+
+ Args:
+ name: Give your video a custom name for easy identification.
+ resolution: Output video resolution. Defaults to `720p` on paid tiers and `480p` on free tiers.
+ start_seconds: Start time of your clip (seconds). Must be ≥ 0.
+ style: Attributes used to dictate the style of the output
+ assets: Provide the audio file and an optional reference image.
+ end_seconds: End time of your clip (seconds). Must be greater than start_seconds.
+ wait_for_completion: Whether to wait for the video project to complete
+ download_outputs: Whether to download the outputs
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
+ request_options: Additional options to customize the HTTP request
+
+ Returns:
+ V1VideoProjectsGetResponseWithDownloads: The response from the Audio-to-Video API with the downloaded paths if `download_outputs` is True.
+
+ Examples:
+ ```py
+ response = client.v1.audio_to_video.generate(
+ assets={
+ "audio_file_path": "path/to/audio.mp3",
+ "image_file_path": "path/to/image.png",
+ },
+ end_seconds=15.0,
+ name="My Audio To Video video",
+ resolution="720p",
+ start_seconds=0.0,
+ wait_for_completion=True,
+ download_outputs=True,
+ download_directory=".",
+ )
+ ```
+ """
+
+ file_client = FilesClient(base_client=self._base_client)
+
+ audio_file_path = assets["audio_file_path"]
+ assets["audio_file_path"] = file_client.upload_file(file=audio_file_path)
+
+ if "image_file_path" in assets and assets["image_file_path"]:
+ image_file_path = assets["image_file_path"]
+ assets["image_file_path"] = file_client.upload_file(file=image_file_path)
+
+ create_response = self.create(
+ assets=assets,
+ end_seconds=end_seconds,
+ name=name,
+ resolution=resolution,
+ start_seconds=start_seconds,
+ style=style,
+ request_options=request_options,
+ )
+ logger.info(f"Audio-to-Video response: {create_response}")
+
+ video_projects_client = VideoProjectsClient(base_client=self._base_client)
+ response = video_projects_client.check_result(
+ id=create_response.id,
+ wait_for_completion=wait_for_completion,
+ download_outputs=download_outputs,
+ download_directory=download_directory,
+ )
+
+ return response
+
def create(
self,
*,
@@ -119,6 +219,99 @@ class AsyncAudioToVideoClient:
def __init__(self, *, base_client: AsyncBaseClient):
self._base_client = base_client
+ async def generate(
+ self,
+ *,
+ assets: params.V1AudioToVideoGenerateBodyAssets,
+ end_seconds: float,
+ name: typing.Union[
+ typing.Optional[str], type_utils.NotGiven
+ ] = type_utils.NOT_GIVEN,
+ resolution: typing.Union[
+ typing.Optional[typing_extensions.Literal["1080p", "480p", "720p"]],
+ type_utils.NotGiven,
+ ] = type_utils.NOT_GIVEN,
+ start_seconds: typing.Union[
+ typing.Optional[float], type_utils.NotGiven
+ ] = type_utils.NOT_GIVEN,
+ style: typing.Union[
+ typing.Optional[params.V1AudioToVideoCreateBodyStyle], type_utils.NotGiven
+ ] = type_utils.NOT_GIVEN,
+ wait_for_completion: bool = True,
+ download_outputs: bool = True,
+ download_directory: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ):
+ """
+ Generate audio-to-video (alias for create with additional functionality).
+
+ Create an Audio To Video video. Credits are only charged for the frames that actually render.
+
+ Args:
+ name: Give your video a custom name for easy identification.
+ resolution: Output video resolution. Defaults to `720p` on paid tiers and `480p` on free tiers.
+ start_seconds: Start time of your clip (seconds). Must be ≥ 0.
+ style: Attributes used to dictate the style of the output
+ assets: Provide the audio file and an optional reference image.
+ end_seconds: End time of your clip (seconds). Must be greater than start_seconds.
+ wait_for_completion: Whether to wait for the video project to complete
+ download_outputs: Whether to download the outputs
+ download_directory: The directory to download the outputs to. If not provided, the outputs will be downloaded to the current working directory
+ request_options: Additional options to customize the HTTP request
+
+ Returns:
+ V1VideoProjectsGetResponseWithDownloads: The response from the Audio-to-Video API with the downloaded paths if `download_outputs` is True.
+
+ Examples:
+ ```py
+ response = await client.v1.audio_to_video.generate(
+ assets={
+ "audio_file_path": "path/to/audio.mp3",
+ "image_file_path": "path/to/image.png",
+ },
+ end_seconds=15.0,
+ name="My Audio To Video video",
+ resolution="720p",
+ start_seconds=0.0,
+ wait_for_completion=True,
+ download_outputs=True,
+ download_directory=".",
+ )
+ ```
+ """
+
+ file_client = AsyncFilesClient(base_client=self._base_client)
+
+ audio_file_path = assets["audio_file_path"]
+ assets["audio_file_path"] = await file_client.upload_file(file=audio_file_path)
+
+ if "image_file_path" in assets and assets["image_file_path"]:
+ image_file_path = assets["image_file_path"]
+ assets["image_file_path"] = await file_client.upload_file(
+ file=image_file_path
+ )
+
+ create_response = await self.create(
+ assets=assets,
+ end_seconds=end_seconds,
+ name=name,
+ resolution=resolution,
+ start_seconds=start_seconds,
+ style=style,
+ request_options=request_options,
+ )
+ logger.info(f"Audio-to-Video response: {create_response}")
+
+ video_projects_client = AsyncVideoProjectsClient(base_client=self._base_client)
+ response = await video_projects_client.check_result(
+ id=create_response.id,
+ wait_for_completion=wait_for_completion,
+ download_outputs=download_outputs,
+ download_directory=download_directory,
+ )
+
+ return response
+
async def create(
self,
*,
diff --git a/magic_hour/types/params/__init__.py b/magic_hour/types/params/__init__.py
index a3f0d89..adaefec 100644
--- a/magic_hour/types/params/__init__.py
+++ b/magic_hour/types/params/__init__.py
@@ -157,6 +157,7 @@
V1AudioToVideoCreateBodyStyle,
_SerializerV1AudioToVideoCreateBodyStyle,
)
+from .v1_audio_to_video_generate_body_assets import V1AudioToVideoGenerateBodyAssets
from .v1_auto_subtitle_generator_create_body import (
V1AutoSubtitleGeneratorCreateBody,
_SerializerV1AutoSubtitleGeneratorCreateBody,
@@ -358,6 +359,7 @@
"V1AudioToVideoCreateBody",
"V1AudioToVideoCreateBodyAssets",
"V1AudioToVideoCreateBodyStyle",
+ "V1AudioToVideoGenerateBodyAssets",
"V1AutoSubtitleGeneratorCreateBody",
"V1AutoSubtitleGeneratorCreateBodyAssets",
"V1AutoSubtitleGeneratorCreateBodyStyle",
diff --git a/magic_hour/types/params/v1_audio_to_video_generate_body_assets.py b/magic_hour/types/params/v1_audio_to_video_generate_body_assets.py
new file mode 100644
index 0000000..850de00
--- /dev/null
+++ b/magic_hour/types/params/v1_audio_to_video_generate_body_assets.py
@@ -0,0 +1,25 @@
+import typing_extensions
+
+
+class V1AudioToVideoGenerateBodyAssets(typing_extensions.TypedDict):
+ """
+ Provide the audio file and an optional reference image.
+ """
+
+ audio_file_path: typing_extensions.Required[str]
+ """
+ The path of the audio file. This value is either
+ - a direct URL to the audio file
+ - a path to a local file
+
+ Note: if the path begins with `api-assets`, it will be assumed to already be uploaded to Magic Hour's storage, and will not be uploaded again.
+ """
+
+ image_file_path: typing_extensions.NotRequired[str]
+ """
+ Reference image for the initial frame of the video. This value is either
+ - a direct URL to the image file
+ - a path to a local file
+
+ Note: if the path begins with `api-assets`, it will be assumed to already be uploaded to Magic Hour's storage, and will not be uploaded again.
+ """