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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions magic_hour/resources/v1/audio_to_video/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,76 @@

## Module Functions

<!-- CUSTOM DOCS START -->

### Audio To Video Generate Workflow <a name="generate"></a>

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="."
)
```

<!-- CUSTOM DOCS END -->

### Audio-to-Video <a name="create"></a>

**What this API does**
Expand Down
193 changes: 193 additions & 0 deletions magic_hour/resources/v1/audio_to_video/client.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
*,
Expand Down Expand Up @@ -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,
*,
Expand Down
2 changes: 2 additions & 0 deletions magic_hour/types/params/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -358,6 +359,7 @@
"V1AudioToVideoCreateBody",
"V1AudioToVideoCreateBodyAssets",
"V1AudioToVideoCreateBodyStyle",
"V1AudioToVideoGenerateBodyAssets",
"V1AutoSubtitleGeneratorCreateBody",
"V1AutoSubtitleGeneratorCreateBodyAssets",
"V1AutoSubtitleGeneratorCreateBodyStyle",
Expand Down
25 changes: 25 additions & 0 deletions magic_hour/types/params/v1_audio_to_video_generate_body_assets.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Loading