diff --git a/README.md b/README.md
index 3a0bd59..ba2d77e 100644
--- a/README.md
+++ b/README.md
@@ -274,6 +274,7 @@ Valid values are: `none`, `error`, `warn`, `info`, `debug` (case insensitive). I
### [v1.audioToVideo](src/resources/v1/audio-to-video/README.md)
+- [generate](src/resources/v1/audio-to-video/README.md#generate) - Audio To Video Generate Workflow
- [create](src/resources/v1/audio-to-video/README.md#create) - Audio-to-Video
### [v1.autoSubtitleGenerator](src/resources/v1/auto-subtitle-generator/README.md)
diff --git a/src/resources/v1/audio-to-video/README.md b/src/resources/v1/audio-to-video/README.md
index f0c6cf9..41b2b6e 100644
--- a/src/resources/v1/audio-to-video/README.md
+++ b/src/resources/v1/audio-to-video/README.md
@@ -2,6 +2,55 @@
## 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 addition to the parameters listed in the `create` section below, `generate` introduces 3 new parameters:
+
+- `waitForCompletion` (boolean, default true): Whether to wait for the project to complete.
+- `downloadOutputs` (boolean, default true): Whether to download the generated files
+- `downloadDirectory` (string, optional): Directory to save downloaded files (defaults to current directory)
+
+#### Example Snippet
+
+```typescript
+import { Client } from "magic-hour";
+
+const client = new Client({ token: process.env["API_TOKEN"]!! });
+const res = await client.v1.audioToVideo.generate(
+ {
+ assets: {
+ audioFilePath: "/path/to/1234.mp3",
+ imageFilePath: "/path/to/1234.png",
+ },
+ endSeconds: 15.0,
+ name: "Audio To Video video",
+ resolution: "720p",
+ startSeconds: 0.0,
+ },
+ {
+ waitForCompletion: true,
+ downloadOutputs: true,
+ downloadDirectory: ".",
+ },
+);
+```
+
+
+
### Audio-to-Video
**What this API does**
diff --git a/src/resources/v1/audio-to-video/resource-client.ts b/src/resources/v1/audio-to-video/resource-client.ts
index d7a1109..fa3b3c1 100644
--- a/src/resources/v1/audio-to-video/resource-client.ts
+++ b/src/resources/v1/audio-to-video/resource-client.ts
@@ -6,16 +6,149 @@ import {
ResourceClientOptions,
} from "make-api-request-js";
+import { types } from "magic-hour";
+import {
+ GenerateOptions,
+ GenerateRequestType,
+} from "magic-hour/helpers/generate-type";
+import { getLogger } from "magic-hour/logger";
+import { FilesClient } from "magic-hour/resources/v1/files";
import * as requests from "magic-hour/resources/v1/audio-to-video/request-types";
-import * as types from "magic-hour/types";
+import { VideoProjectsClient } from "magic-hour/resources/v1/video-projects";
import { Schemas$V1AudioToVideoCreateBody } from "magic-hour/types/v1-audio-to-video-create-body";
import { Schemas$V1AudioToVideoCreateResponse } from "magic-hour/types/v1-audio-to-video-create-response";
+type GenerateRequest = GenerateRequestType<
+ requests.CreateRequest,
+ {
+ /**
+ * 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.
+ */
+ audioFilePath: string;
+ /**
+ * 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.
+ */
+ imageFilePath?: string | undefined;
+ }
+>;
+
export class AudioToVideoClient extends CoreResourceClient {
constructor(coreClient: CoreClient, opts: ResourceClientOptions) {
super(coreClient, opts);
}
+ /**
+ * Audio-to-Video
+ *
+ * Create a Audio To Video video
+ *
+ * This method provides a convenient way to create a request and automatically wait for completion and download outputs.
+ *
+ * @example
+ * ```typescript
+ * import { Client } from "magic-hour";
+ *
+ * const client = new Client({ token: process.env["API_TOKEN"]!! });
+ * const res = await client.v1.audioToVideo.generate(
+ * {
+ * assets: {
+ * audioFilePath: "/path/to/1234.mp3",
+ * imageFilePath: "/path/to/1234.png",
+ * },
+ * endSeconds: 15.0,
+ * name: "Audio To Video video",
+ * resolution: "720p",
+ * startSeconds: 0.0,
+ * },
+ * {
+ * waitForCompletion: true,
+ * downloadOutputs: true,
+ * downloadDirectory: ".",
+ * },
+ * );
+ * ```
+ */
+ async generate(request: GenerateRequest, opts: GenerateOptions = {}) {
+ const {
+ waitForCompletion = true,
+ downloadOutputs = true,
+ downloadDirectory = undefined,
+ ...createOpts
+ } = opts;
+
+ const fileClient = new FilesClient(this._client, this._opts);
+ const { audioFilePath, imageFilePath, ...restAssets } = request.assets;
+
+ getLogger().debug(
+ `Uploading file ${audioFilePath} to Magic Hour's storage`,
+ );
+ if (imageFilePath) {
+ getLogger().debug(
+ `Uploading file ${imageFilePath} to Magic Hour's storage`,
+ );
+ }
+
+ const [uploadedAudioFilePath, uploadedImageFilePath] = await Promise.all([
+ fileClient.uploadFile(audioFilePath),
+ imageFilePath
+ ? fileClient.uploadFile(imageFilePath)
+ : Promise.resolve(imageFilePath),
+ ]);
+
+ getLogger().info(
+ `Uploaded file ${audioFilePath} to Magic Hour's storage as ${uploadedAudioFilePath}`,
+ );
+ if (imageFilePath) {
+ getLogger().info(
+ `Uploaded file ${imageFilePath} to Magic Hour's storage as ${uploadedImageFilePath}`,
+ );
+ }
+
+ const createResponse = await this.create(
+ {
+ ...request,
+ assets: {
+ ...restAssets,
+ audioFilePath: uploadedAudioFilePath,
+ imageFilePath: imageFilePath
+ ? uploadedImageFilePath
+ : imageFilePath,
+ },
+ },
+ createOpts,
+ );
+
+ getLogger().info(
+ `Created AudioToVideoClient project ${createResponse.id}`,
+ );
+
+ const projectsClient = new VideoProjectsClient(this._client, this._opts);
+
+ getLogger().debug(
+ `Checking result for AudioToVideoClient project ${createResponse.id}`,
+ );
+
+ const result = await projectsClient.checkResult(
+ { id: createResponse.id },
+ {
+ waitForCompletion,
+ downloadOutputs,
+ downloadDirectory,
+ ...createOpts,
+ },
+ );
+
+ return result;
+ }
+
/**
* Audio-to-Video
*