diff --git a/.sdk.json b/.sdk.json
index 6a69911..f998df6 100644
--- a/.sdk.json
+++ b/.sdk.json
@@ -1,5 +1,5 @@
{
- "id": "08756a67-c9aa-401d-9e38-feee98a19df4",
+ "id": "6058b2ba-30f8-46d6-9c9d-83938cbf7071",
"tracked_paths": [
{
"editable": true,
@@ -209,6 +209,22 @@
"editable": true,
"path": "src/resources/v1/ai-talking-photo/resource-client.ts"
},
+ {
+ "editable": true,
+ "path": "src/resources/v1/ai-video-editor/README.md"
+ },
+ {
+ "editable": true,
+ "path": "src/resources/v1/ai-video-editor/index.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/resources/v1/ai-video-editor/request-types.ts"
+ },
+ {
+ "editable": true,
+ "path": "src/resources/v1/ai-video-editor/resource-client.ts"
+ },
{
"editable": true,
"path": "src/resources/v1/ai-voice-cloner/README.md"
@@ -321,6 +337,22 @@
"editable": true,
"path": "src/resources/v1/body-swap/resource-client.ts"
},
+ {
+ "editable": true,
+ "path": "src/resources/v1/character-replace/README.md"
+ },
+ {
+ "editable": true,
+ "path": "src/resources/v1/character-replace/index.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/resources/v1/character-replace/request-types.ts"
+ },
+ {
+ "editable": true,
+ "path": "src/resources/v1/character-replace/resource-client.ts"
+ },
{
"editable": true,
"path": "src/resources/v1/face-detection/README.md"
@@ -765,6 +797,22 @@
"editable": false,
"path": "src/types/v1-ai-talking-photo-create-response.ts"
},
+ {
+ "editable": false,
+ "path": "src/types/v1-ai-video-editor-create-body-assets.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-ai-video-editor-create-body-style.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-ai-video-editor-create-body.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-ai-video-editor-create-response.ts"
+ },
{
"editable": false,
"path": "src/types/v1-ai-voice-cloner-create-body-assets.ts"
@@ -905,6 +953,38 @@
"editable": false,
"path": "src/types/v1-body-swap-create-response.ts"
},
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-body-assets.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-body-resolution-enum.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-body-style-mode-enum.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-body-style-points-item.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-body-style-selection-mode-enum.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-body-style.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-body.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-character-replace-create-response.ts"
+ },
{
"editable": false,
"path": "src/types/v1-face-detection-create-body-assets.ts"
@@ -1237,6 +1317,10 @@
"editable": false,
"path": "test/v1-ai-talking-photo.test.ts"
},
+ {
+ "editable": false,
+ "path": "test/v1-ai-video-editor.test.ts"
+ },
{
"editable": false,
"path": "test/v1-ai-voice-cloner.test.ts"
@@ -1265,6 +1349,10 @@
"editable": false,
"path": "test/v1-body-swap.test.ts"
},
+ {
+ "editable": false,
+ "path": "test/v1-character-replace.test.ts"
+ },
{
"editable": false,
"path": "test/v1-face-detection.test.ts"
diff --git a/README.md b/README.md
index c660b9e..ac828e4 100644
--- a/README.md
+++ b/README.md
@@ -251,6 +251,11 @@ Valid values are: `none`, `error`, `warn`, `info`, `debug` (case insensitive). I
- [create](src/resources/v1/ai-talking-photo/README.md#create) - AI Talking Photo
- [generate](src/resources/v1/ai-talking-photo/README.md#generate) - AI Talking Photo Generate Workflow
+### [v1.aiVideoEditor](src/resources/v1/ai-video-editor/README.md)
+
+- [create](src/resources/v1/ai-video-editor/README.md#create) - AI Video Editor
+- [generate](src/resources/v1/ai-video-editor/README.md#generate) - AI Video Editor Generate Workflow
+
### [v1.aiVoiceCloner](src/resources/v1/ai-voice-cloner/README.md)
- [create](src/resources/v1/ai-voice-cloner/README.md#create) - AI Voice Cloner
@@ -287,6 +292,11 @@ Valid values are: `none`, `error`, `warn`, `info`, `debug` (case insensitive). I
- [create](src/resources/v1/body-swap/README.md#create) - Body Swap
- [generate](src/resources/v1/body-swap/README.md#generate) - Body Swap Generate Workflow
+### [v1.characterReplace](src/resources/v1/character-replace/README.md)
+
+- [create](src/resources/v1/character-replace/README.md#create) - Character Replace
+- [generate](src/resources/v1/character-replace/README.md#generate) - Character Replace Generate Workflow
+
### [v1.faceDetection](src/resources/v1/face-detection/README.md)
- [create](src/resources/v1/face-detection/README.md#create) - Face Detection
diff --git a/package-lock.json b/package-lock.json
index e809444..73fb936 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "magic-hour",
- "version": "0.67.0",
+ "version": "0.68.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "magic-hour",
- "version": "0.67.0",
+ "version": "0.68.0",
"license": "MIT",
"dependencies": {
"make-api-request-js": "^0.2.1",
diff --git a/package.json b/package.json
index 7ff4e59..86ab01e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "magic-hour",
- "version": "0.67.0",
+ "version": "0.68.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
diff --git a/src/README.md b/src/README.md
index 6a8b85e..9da01a7 100644
--- a/src/README.md
+++ b/src/README.md
@@ -12,6 +12,7 @@
- [ai-meme-generator](resources/v1/ai-meme-generator/README.md) - ai-meme-generator
- [ai-qr-code-generator](resources/v1/ai-qr-code-generator/README.md) - ai-qr-code-generator
- [ai-talking-photo](resources/v1/ai-talking-photo/README.md) - ai-talking-photo
+- [ai-video-editor](resources/v1/ai-video-editor/README.md) - ai-video-editor
- [ai-voice-cloner](resources/v1/ai-voice-cloner/README.md) - ai-voice-cloner
- [ai-voice-generator](resources/v1/ai-voice-generator/README.md) - ai-voice-generator
- [animation](resources/v1/animation/README.md) - animation
@@ -19,6 +20,7 @@
- [audio-to-video](resources/v1/audio-to-video/README.md) - audio-to-video
- [auto-subtitle-generator](resources/v1/auto-subtitle-generator/README.md) - auto-subtitle-generator
- [body-swap](resources/v1/body-swap/README.md) - body-swap
+- [character-replace](resources/v1/character-replace/README.md) - character-replace
- [face-detection](resources/v1/face-detection/README.md) - face-detection
- [face-swap-photo](resources/v1/face-swap-photo/README.md) - face-swap-photo
- [face-swap](resources/v1/face-swap/README.md) - face-swap
diff --git a/src/environment.ts b/src/environment.ts
index e7419bb..08179dd 100644
--- a/src/environment.ts
+++ b/src/environment.ts
@@ -1,4 +1,4 @@
export enum Environment {
Environment = "https://api.magichour.ai",
- MockServer = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.68.0",
+ MockServer = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.69.0",
}
diff --git a/src/resources/v1/README.md b/src/resources/v1/README.md
index 3da9c60..adf38aa 100644
--- a/src/resources/v1/README.md
+++ b/src/resources/v1/README.md
@@ -12,6 +12,7 @@
- [ai-meme-generator](ai-meme-generator/README.md) - ai-meme-generator
- [ai-qr-code-generator](ai-qr-code-generator/README.md) - ai-qr-code-generator
- [ai-talking-photo](ai-talking-photo/README.md) - ai-talking-photo
+- [ai-video-editor](ai-video-editor/README.md) - ai-video-editor
- [ai-voice-cloner](ai-voice-cloner/README.md) - ai-voice-cloner
- [ai-voice-generator](ai-voice-generator/README.md) - ai-voice-generator
- [animation](animation/README.md) - animation
@@ -19,6 +20,7 @@
- [audio-to-video](audio-to-video/README.md) - audio-to-video
- [auto-subtitle-generator](auto-subtitle-generator/README.md) - auto-subtitle-generator
- [body-swap](body-swap/README.md) - body-swap
+- [character-replace](character-replace/README.md) - character-replace
- [face-detection](face-detection/README.md) - face-detection
- [face-swap](face-swap/README.md) - face-swap
- [face-swap-photo](face-swap-photo/README.md) - face-swap-photo
diff --git a/src/resources/v1/ai-video-editor/README.md b/src/resources/v1/ai-video-editor/README.md
new file mode 100644
index 0000000..7e2f227
--- /dev/null
+++ b/src/resources/v1/ai-video-editor/README.md
@@ -0,0 +1,118 @@
+# v1.ai-video-editor
+
+## Module Functions
+
+
+
+### AI Video Editor 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.aiVideoEditor.generate(
+ {
+ assets: { videoFilePath: "/path/to/1234.mp4" },
+ endSeconds: 5.0,
+ name: "My Video Editor video",
+ startSeconds: 0.0,
+ style: { prompt: "Change the car color to blue" },
+ },
+ {
+ waitForCompletion: true,
+ downloadOutputs: true,
+ downloadDirectory: ".",
+ },
+);
+```
+
+
+
+### AI Video Editor
+
+**What this API does**
+
+Create the same Video Editor you can make in the browser, but programmatically, so you can automate it, run it at scale, or connect it to your own app or workflow.
+
+**Good for**
+
+- Automation and batch processing
+- Adding video editor into apps, pipelines, or tools
+
+**How it works (3 steps)**
+
+1. Upload your inputs (video, image, or audio) with [Generate Upload URLs](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls) and copy the `file_path`.
+2. Send a request to create a video editor job with the basic fields.
+3. Check the job status until it's `complete`, then download the result from `downloads`.
+
+**Key options**
+
+- Inputs: usually a file, sometimes a YouTube link, depending on project type
+- Resolution: free users are limited to 576px; higher plans unlock HD and larger sizes
+- Extra fields: e.g. `face_swap_mode`, `start_seconds`/`end_seconds`, or a text prompt
+
+**Cost**\
+Credits are only charged for the frames that actually render. You'll see an estimate when the job is queued, and the final total after it's done.
+
+For detailed examples, see the [product page](https://magichour.ai/products/video-editor).
+
+**API Endpoint**: `POST /v1/ai-video-editor`
+
+#### Parameters
+
+| Parameter | Required | Description | Example |
+| ------------------ | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
+| `assets` | ✓ | Provide the assets for video editing. | `{"videoFilePath": "api-assets/id/1234.mp4"}` |
+| `└─ videoFilePath` | ✓ | The video to edit. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details. | `"api-assets/id/1234.mp4"` |
+| `endSeconds` | ✓ | End time of your clip in seconds. Must be greater than `start_seconds`. Duration must be between 3 and 10 seconds. | `5.0` |
+| `style` | ✓ | | `{"prompt": "Change the car color to blue"}` |
+| `└─ prompt` | ✓ | The prompt used to edit the video. | `"Change the car color to blue"` |
+| `name` | ✗ | Give your video a custom name for easy identification. | `"My Video Editor video"` |
+| `startSeconds` | ✗ | Start time of your clip (seconds). Must be ≥ 0. | `0.0` |
+
+#### Example Snippet
+
+```typescript
+import { Client } from "magic-hour";
+
+const client = new Client({ token: process.env["API_TOKEN"]!! });
+const res = await client.v1.aiVideoEditor.create({
+ assets: { videoFilePath: "api-assets/id/1234.mp4" },
+ endSeconds: 5.0,
+ name: "My Video Editor video",
+ startSeconds: 0.0,
+ style: { prompt: "Change the car color to blue" },
+});
+```
+
+#### Response
+
+##### Type
+
+[V1AiVideoEditorCreateResponse](/src/types/v1-ai-video-editor-create-response.ts)
+
+##### Example
+
+```typescript
+{"creditsCharged": 450, "estimatedFrameCost": 450, "id": "cuid-example"}
+```
diff --git a/src/resources/v1/ai-video-editor/index.ts b/src/resources/v1/ai-video-editor/index.ts
new file mode 100644
index 0000000..01a74ae
--- /dev/null
+++ b/src/resources/v1/ai-video-editor/index.ts
@@ -0,0 +1,2 @@
+export { CreateRequest } from "./request-types";
+export { AiVideoEditorClient } from "./resource-client";
diff --git a/src/resources/v1/ai-video-editor/request-types.ts b/src/resources/v1/ai-video-editor/request-types.ts
new file mode 100644
index 0000000..491a149
--- /dev/null
+++ b/src/resources/v1/ai-video-editor/request-types.ts
@@ -0,0 +1,105 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+import {
+ External$V1AiVideoEditorCreateBodyAssets,
+ Schemas$V1AiVideoEditorCreateBodyAssets,
+ V1AiVideoEditorCreateBodyAssets,
+} from "magic-hour/types/v1-ai-video-editor-create-body-assets";
+import {
+ External$V1AiVideoEditorCreateBodyStyle,
+ Schemas$V1AiVideoEditorCreateBodyStyle,
+ V1AiVideoEditorCreateBodyStyle,
+} from "magic-hour/types/v1-ai-video-editor-create-body-style";
+
+/**
+ * CreateRequest
+ */
+export type CreateRequest = {
+ /**
+ * Provide the assets for video editing.
+ */
+ assets: V1AiVideoEditorCreateBodyAssets;
+ /**
+ * End time of your clip in seconds. Must be greater than `start_seconds`. Duration must be between 3 and 10 seconds.
+ */
+ endSeconds: number;
+ /**
+ * Give your video a custom name for easy identification.
+ */
+ name?: string | undefined;
+ /**
+ * Start time of your clip (seconds). Must be ≥ 0.
+ */
+ startSeconds?: number | undefined;
+ style: V1AiVideoEditorCreateBodyStyle;
+};
+
+/**
+ * @internal
+ * CreateRequest without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$CreateRequest = {
+ assets: External$V1AiVideoEditorCreateBodyAssets;
+ end_seconds: number;
+ name?: string | undefined;
+ start_seconds?: number | undefined;
+ style: External$V1AiVideoEditorCreateBodyStyle;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object CreateRequest
+ */
+const SchemaIn$CreateRequest: z.ZodType<
+ CreateRequest, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ assets: Schemas$V1AiVideoEditorCreateBodyAssets.in,
+ end_seconds: z.number(),
+ name: z.string().optional(),
+ start_seconds: z.number().optional(),
+ style: Schemas$V1AiVideoEditorCreateBodyStyle.in,
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ end_seconds: "endSeconds",
+ name: "name",
+ start_seconds: "startSeconds",
+ style: "style",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$CreateRequest
+ */
+const SchemaOut$CreateRequest: z.ZodType<
+ External$CreateRequest, // output type of this zod object
+ z.ZodTypeDef,
+ CreateRequest // the object to be transformed
+> = z
+ .object({
+ assets: Schemas$V1AiVideoEditorCreateBodyAssets.out,
+ endSeconds: z.number(),
+ name: z.string().optional(),
+ startSeconds: z.number().optional(),
+ style: Schemas$V1AiVideoEditorCreateBodyStyle.out,
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ endSeconds: "end_seconds",
+ name: "name",
+ startSeconds: "start_seconds",
+ style: "style",
+ });
+ });
+
+export const Schemas$CreateRequest = {
+ in: SchemaIn$CreateRequest,
+ out: SchemaOut$CreateRequest,
+};
diff --git a/src/resources/v1/ai-video-editor/resource-client.ts b/src/resources/v1/ai-video-editor/resource-client.ts
new file mode 100644
index 0000000..bdc96f7
--- /dev/null
+++ b/src/resources/v1/ai-video-editor/resource-client.ts
@@ -0,0 +1,165 @@
+import {
+ ApiPromise,
+ CoreClient,
+ CoreResourceClient,
+ RequestOptions,
+ ResourceClientOptions,
+} from "make-api-request-js";
+
+import {
+ GenerateOptions,
+ GenerateRequestType,
+} from "magic-hour/helpers/generate-type";
+import { getLogger } from "magic-hour/logger";
+import * as requests from "magic-hour/resources/v1/ai-video-editor/request-types";
+import { FilesClient } from "magic-hour/resources/v1/files";
+import { VideoProjectsClient } from "magic-hour/resources/v1/video-projects";
+import * as types from "magic-hour/types";
+import { Schemas$V1AiVideoEditorCreateBody } from "magic-hour/types/v1-ai-video-editor-create-body";
+import { Schemas$V1AiVideoEditorCreateResponse } from "magic-hour/types/v1-ai-video-editor-create-response";
+
+type GenerateRequest = GenerateRequestType<
+ requests.CreateRequest,
+ {
+ /**
+ * The video to edit. This value is either
+ * - a direct URL to the video 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.
+ */
+ videoFilePath: string;
+ }
+>;
+
+export class AiVideoEditorClient extends CoreResourceClient {
+ constructor(coreClient: CoreClient, opts: ResourceClientOptions) {
+ super(coreClient, opts);
+ }
+
+ /**
+ * AI Video Editor
+ *
+ * Create a Video Editor 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.aiVideoEditor.generate(
+ * {
+ * assets: { videoFilePath: "/path/to/1234.mp4" },
+ * endSeconds: 5.0,
+ * name: "My Video Editor video",
+ * startSeconds: 0.0,
+ * style: { prompt: "Change the car color to blue" },
+ * },
+ * {
+ * 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 { videoFilePath, ...restAssets } = request.assets;
+
+ getLogger().debug(
+ `Uploading file ${videoFilePath} to Magic Hour's storage`,
+ );
+
+ const uploadedVideoFilePath = await fileClient.uploadFile(videoFilePath);
+
+ getLogger().info(
+ `Uploaded file ${videoFilePath} to Magic Hour's storage as ${uploadedVideoFilePath}`,
+ );
+
+ const createResponse = await this.create(
+ {
+ ...request,
+ assets: {
+ ...restAssets,
+ videoFilePath: uploadedVideoFilePath,
+ },
+ },
+ createOpts,
+ );
+
+ getLogger().info(
+ `Created AiVideoEditorClient project ${createResponse.id}`,
+ );
+
+ const projectsClient = new VideoProjectsClient(this._client, this._opts);
+
+ getLogger().debug(
+ `Checking result for AiVideoEditorClient project ${createResponse.id}`,
+ );
+
+ const result = await projectsClient.checkResult(
+ { id: createResponse.id },
+ {
+ waitForCompletion,
+ downloadOutputs,
+ downloadDirectory,
+ ...createOpts,
+ },
+ );
+
+ return result;
+ }
+
+ /**
+ * AI Video Editor
+ *
+ * **What this API does**
+ *
+ * Create the same Video Editor you can make in the browser, but programmatically, so you can automate it, run it at scale, or connect it to your own app or workflow.
+ *
+ * **Good for**
+ * - Automation and batch processing
+ * - Adding video editor into apps, pipelines, or tools
+ *
+ * **How it works (3 steps)**
+ * 1) Upload your inputs (video, image, or audio) with [Generate Upload URLs](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls) and copy the `file_path`.
+ * 2) Send a request to create a video editor job with the basic fields.
+ * 3) Check the job status until it's `complete`, then download the result from `downloads`.
+ *
+ * **Key options**
+ * - Inputs: usually a file, sometimes a YouTube link, depending on project type
+ * - Resolution: free users are limited to 576px; higher plans unlock HD and larger sizes
+ * - Extra fields: e.g. `face_swap_mode`, `start_seconds`/`end_seconds`, or a text prompt
+ *
+ * **Cost**
+ * Credits are only charged for the frames that actually render. You'll see an estimate when the job is queued, and the final total after it's done.
+ *
+ * For detailed examples, see the [product page](https://magichour.ai/products/video-editor).
+ *
+ * POST /v1/ai-video-editor
+ */
+ create(
+ request: requests.CreateRequest,
+ opts?: RequestOptions,
+ ): ApiPromise {
+ return this._client.makeRequest({
+ method: "post",
+ path: "/v1/ai-video-editor",
+ auth: ["bearerAuth"],
+ contentType: "application/json",
+ body: Schemas$V1AiVideoEditorCreateBody.out.parse(request),
+ responseSchema: Schemas$V1AiVideoEditorCreateResponse.in,
+ opts,
+ });
+ }
+}
diff --git a/src/resources/v1/body-swap/README.md b/src/resources/v1/body-swap/README.md
index 1e32cec..30afb82 100644
--- a/src/resources/v1/body-swap/README.md
+++ b/src/resources/v1/body-swap/README.md
@@ -51,7 +51,7 @@ const res = await client.v1.bodySwap.generate(
### Body Swap
-Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward).
+Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 50 credits at 640px upward).
**API Endpoint**: `POST /v1/body-swap`
@@ -90,5 +90,5 @@ const res = await client.v1.bodySwap.create({
##### Example
```typescript
-{"creditsCharged": 100, "frameCost": 100, "id": "cuid-example"}
+{"creditsCharged": 50, "frameCost": 50, "id": "cuid-example"}
```
diff --git a/src/resources/v1/body-swap/resource-client.ts b/src/resources/v1/body-swap/resource-client.ts
index 3d710da..3598b67 100644
--- a/src/resources/v1/body-swap/resource-client.ts
+++ b/src/resources/v1/body-swap/resource-client.ts
@@ -140,7 +140,7 @@ export class BodySwapClient extends CoreResourceClient {
/**
* Body Swap
*
- * Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward).
+ * Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 50 credits at 640px upward).
*
* POST /v1/body-swap
*/
diff --git a/src/resources/v1/character-replace/README.md b/src/resources/v1/character-replace/README.md
new file mode 100644
index 0000000..fc2c302
--- /dev/null
+++ b/src/resources/v1/character-replace/README.md
@@ -0,0 +1,127 @@
+# v1.character-replace
+
+## Module Functions
+
+
+
+### Character Replace 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.characterReplace.generate(
+ {
+ assets: {
+ imageFilePath: "/path/to/5678.png",
+ videoFilePath: "/path/to/1234.mp4",
+ },
+ endSeconds: 15.0,
+ name: "My Character Replace video",
+ resolution: "720p",
+ startSeconds: 0.0,
+ style: { mode: "replace", selectionMode: "auto" },
+ },
+ {
+ waitForCompletion: true,
+ downloadOutputs: true,
+ downloadDirectory: ".",
+ },
+);
+```
+
+
+
+### Character Replace
+
+**What this API does**
+
+Create the same Character Replace you can make in the browser, but programmatically, so you can automate it, run it at scale, or connect it to your own app or workflow.
+
+**Good for**
+
+- Automation and batch processing
+- Adding character replace into apps, pipelines, or tools
+
+**How it works (3 steps)**
+
+1. Upload your inputs (video, image, or audio) with [Generate Upload URLs](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls) and copy the `file_path`.
+2. Send a request to create a character replace job with the basic fields.
+3. Check the job status until it's `complete`, then download the result from `downloads`.
+
+**Key options**
+
+- Inputs: usually a file, sometimes a YouTube link, depending on project type
+- Resolution: free users are limited to 576px; higher plans unlock HD and larger sizes
+- Extra fields: e.g. `face_swap_mode`, `start_seconds`/`end_seconds`, or a text prompt
+
+**Cost**\
+Credits are only charged for the frames that actually render. You'll see an estimate when the job is queued, and the final total after it's done.
+
+For detailed examples, see the [product page](https://magichour.ai/products/character-replace).
+
+**API Endpoint**: `POST /v1/character-replace`
+
+#### Parameters
+
+| Parameter | Required | Description | Example |
+| ------------------ | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
+| `assets` | ✓ | Source video and reference character image for the job. | `{"imageFilePath": "api-assets/id/5678.png", "videoFilePath": "api-assets/id/1234.mp4"}` |
+| `└─ imageFilePath` | ✓ | Reference character image used as the replacement or animation target. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details. | `"api-assets/id/5678.png"` |
+| `└─ videoFilePath` | ✓ | Source video containing the subject to replace or animate. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details. | `"api-assets/id/1234.mp4"` |
+| `endSeconds` | ✓ | End time of your clip (seconds). Must be greater than start_seconds. | `15.0` |
+| `name` | ✗ | Give your video a custom name for easy identification. | `"My Character Replace video"` |
+| `resolution` | ✗ | Output video resolution. Defaults to 480p, the lowest resolution available on your plan. | `"720p"` |
+| `startSeconds` | ✗ | Start time of your clip (seconds). Must be ≥ 0. | `0.0` |
+| `style` | ✗ | Optional style controls for replace vs animate mode and subject selection. | `{"mode": "replace", "selectionMode": "auto"}` |
+
+#### Example Snippet
+
+```typescript
+import { Client } from "magic-hour";
+
+const client = new Client({ token: process.env["API_TOKEN"]!! });
+const res = await client.v1.characterReplace.create({
+ assets: {
+ imageFilePath: "api-assets/id/5678.png",
+ videoFilePath: "api-assets/id/1234.mp4",
+ },
+ endSeconds: 15.0,
+ name: "My Character Replace video",
+ resolution: "720p",
+ startSeconds: 0.0,
+ style: { mode: "replace", selectionMode: "auto" },
+});
+```
+
+#### Response
+
+##### Type
+
+[V1CharacterReplaceCreateResponse](/src/types/v1-character-replace-create-response.ts)
+
+##### Example
+
+```typescript
+{"creditsCharged": 450, "estimatedFrameCost": 450, "id": "cuid-example"}
+```
diff --git a/src/resources/v1/character-replace/index.ts b/src/resources/v1/character-replace/index.ts
new file mode 100644
index 0000000..77b5ce7
--- /dev/null
+++ b/src/resources/v1/character-replace/index.ts
@@ -0,0 +1,2 @@
+export { CreateRequest } from "./request-types";
+export { CharacterReplaceClient } from "./resource-client";
diff --git a/src/resources/v1/character-replace/request-types.ts b/src/resources/v1/character-replace/request-types.ts
new file mode 100644
index 0000000..deab5b0
--- /dev/null
+++ b/src/resources/v1/character-replace/request-types.ts
@@ -0,0 +1,117 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+import {
+ External$V1CharacterReplaceCreateBodyAssets,
+ Schemas$V1CharacterReplaceCreateBodyAssets,
+ V1CharacterReplaceCreateBodyAssets,
+} from "magic-hour/types/v1-character-replace-create-body-assets";
+import {
+ External$V1CharacterReplaceCreateBodyStyle,
+ Schemas$V1CharacterReplaceCreateBodyStyle,
+ V1CharacterReplaceCreateBodyStyle,
+} from "magic-hour/types/v1-character-replace-create-body-style";
+
+/**
+ * CreateRequest
+ */
+export type CreateRequest = {
+ /**
+ * Source video and reference character image for the job.
+ */
+ assets: V1CharacterReplaceCreateBodyAssets;
+ /**
+ * End time of your clip (seconds). Must be greater than start_seconds.
+ */
+ endSeconds: number;
+ /**
+ * Give your video a custom name for easy identification.
+ */
+ name?: string | undefined;
+ /**
+ * Output video resolution. Defaults to 480p, the lowest resolution available on your plan.
+ */
+ resolution?: ("480p" | "720p") | undefined;
+ /**
+ * Start time of your clip (seconds). Must be ≥ 0.
+ */
+ startSeconds?: number | undefined;
+ /**
+ * Optional style controls for replace vs animate mode and subject selection.
+ */
+ style?: V1CharacterReplaceCreateBodyStyle | undefined;
+};
+
+/**
+ * @internal
+ * CreateRequest without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$CreateRequest = {
+ assets: External$V1CharacterReplaceCreateBodyAssets;
+ end_seconds: number;
+ name?: string | undefined;
+ resolution?: ("480p" | "720p") | undefined;
+ start_seconds?: number | undefined;
+ style?: External$V1CharacterReplaceCreateBodyStyle | undefined;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object CreateRequest
+ */
+const SchemaIn$CreateRequest: z.ZodType<
+ CreateRequest, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ assets: Schemas$V1CharacterReplaceCreateBodyAssets.in,
+ end_seconds: z.number(),
+ name: z.string().optional(),
+ resolution: z.enum(["480p", "720p"]).optional(),
+ start_seconds: z.number().optional(),
+ style: Schemas$V1CharacterReplaceCreateBodyStyle.in.optional(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ end_seconds: "endSeconds",
+ name: "name",
+ resolution: "resolution",
+ start_seconds: "startSeconds",
+ style: "style",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$CreateRequest
+ */
+const SchemaOut$CreateRequest: z.ZodType<
+ External$CreateRequest, // output type of this zod object
+ z.ZodTypeDef,
+ CreateRequest // the object to be transformed
+> = z
+ .object({
+ assets: Schemas$V1CharacterReplaceCreateBodyAssets.out,
+ endSeconds: z.number(),
+ name: z.string().optional(),
+ resolution: z.enum(["480p", "720p"]).optional(),
+ startSeconds: z.number().optional(),
+ style: Schemas$V1CharacterReplaceCreateBodyStyle.out.optional(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ endSeconds: "end_seconds",
+ name: "name",
+ resolution: "resolution",
+ startSeconds: "start_seconds",
+ style: "style",
+ });
+ });
+
+export const Schemas$CreateRequest = {
+ in: SchemaIn$CreateRequest,
+ out: SchemaOut$CreateRequest,
+};
diff --git a/src/resources/v1/character-replace/resource-client.ts b/src/resources/v1/character-replace/resource-client.ts
new file mode 100644
index 0000000..b6d08d6
--- /dev/null
+++ b/src/resources/v1/character-replace/resource-client.ts
@@ -0,0 +1,187 @@
+import {
+ ApiPromise,
+ CoreClient,
+ CoreResourceClient,
+ RequestOptions,
+ ResourceClientOptions,
+} from "make-api-request-js";
+
+import {
+ GenerateOptions,
+ GenerateRequestType,
+} from "magic-hour/helpers/generate-type";
+import { getLogger } from "magic-hour/logger";
+import * as requests from "magic-hour/resources/v1/character-replace/request-types";
+import { FilesClient } from "magic-hour/resources/v1/files";
+import { VideoProjectsClient } from "magic-hour/resources/v1/video-projects";
+import * as types from "magic-hour/types";
+import { Schemas$V1CharacterReplaceCreateBody } from "magic-hour/types/v1-character-replace-create-body";
+import { Schemas$V1CharacterReplaceCreateResponse } from "magic-hour/types/v1-character-replace-create-response";
+
+type GenerateRequest = GenerateRequestType<
+ requests.CreateRequest,
+ {
+ /**
+ * Reference character image used as the replacement or animation target. 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;
+ /**
+ * Source video containing the subject to replace or animate. This value is either
+ * - a direct URL to the video 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.
+ */
+ videoFilePath: string;
+ }
+>;
+
+export class CharacterReplaceClient extends CoreResourceClient {
+ constructor(coreClient: CoreClient, opts: ResourceClientOptions) {
+ super(coreClient, opts);
+ }
+
+ /**
+ * Character Replace
+ *
+ * Create a Character Replace 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.characterReplace.generate(
+ * {
+ * assets: {
+ * imageFilePath: "/path/to/5678.png",
+ * videoFilePath: "/path/to/1234.mp4",
+ * },
+ * endSeconds: 15.0,
+ * name: "My Character Replace video",
+ * resolution: "720p",
+ * startSeconds: 0.0,
+ * style: { mode: "replace", selectionMode: "auto" },
+ * },
+ * {
+ * 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 { imageFilePath, videoFilePath, ...restAssets } = request.assets;
+
+ getLogger().debug(
+ `Uploading file ${imageFilePath} to Magic Hour's storage`,
+ );
+ getLogger().debug(
+ `Uploading file ${videoFilePath} to Magic Hour's storage`,
+ );
+
+ const [uploadedImageFilePath, uploadedVideoFilePath] = await Promise.all([
+ fileClient.uploadFile(imageFilePath),
+ fileClient.uploadFile(videoFilePath),
+ ]);
+
+ getLogger().info(
+ `Uploaded file ${imageFilePath} to Magic Hour's storage as ${uploadedImageFilePath}`,
+ );
+ getLogger().info(
+ `Uploaded file ${videoFilePath} to Magic Hour's storage as ${uploadedVideoFilePath}`,
+ );
+
+ const createResponse = await this.create(
+ {
+ ...request,
+ assets: {
+ ...restAssets,
+ imageFilePath: uploadedImageFilePath,
+ videoFilePath: uploadedVideoFilePath,
+ },
+ },
+ createOpts,
+ );
+
+ getLogger().info(
+ `Created CharacterReplaceClient project ${createResponse.id}`,
+ );
+
+ const projectsClient = new VideoProjectsClient(this._client, this._opts);
+
+ getLogger().debug(
+ `Checking result for CharacterReplaceClient project ${createResponse.id}`,
+ );
+
+ const result = await projectsClient.checkResult(
+ { id: createResponse.id },
+ {
+ waitForCompletion,
+ downloadOutputs,
+ downloadDirectory,
+ ...createOpts,
+ },
+ );
+
+ return result;
+ }
+
+ /**
+ * Character Replace
+ *
+ * **What this API does**
+ *
+ * Create the same Character Replace you can make in the browser, but programmatically, so you can automate it, run it at scale, or connect it to your own app or workflow.
+ *
+ * **Good for**
+ * - Automation and batch processing
+ * - Adding character replace into apps, pipelines, or tools
+ *
+ * **How it works (3 steps)**
+ * 1) Upload your inputs (video, image, or audio) with [Generate Upload URLs](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls) and copy the `file_path`.
+ * 2) Send a request to create a character replace job with the basic fields.
+ * 3) Check the job status until it's `complete`, then download the result from `downloads`.
+ *
+ * **Key options**
+ * - Inputs: usually a file, sometimes a YouTube link, depending on project type
+ * - Resolution: free users are limited to 576px; higher plans unlock HD and larger sizes
+ * - Extra fields: e.g. `face_swap_mode`, `start_seconds`/`end_seconds`, or a text prompt
+ *
+ * **Cost**
+ * Credits are only charged for the frames that actually render. You'll see an estimate when the job is queued, and the final total after it's done.
+ *
+ * For detailed examples, see the [product page](https://magichour.ai/products/character-replace).
+ *
+ * POST /v1/character-replace
+ */
+ create(
+ request: requests.CreateRequest,
+ opts?: RequestOptions,
+ ): ApiPromise {
+ return this._client.makeRequest({
+ method: "post",
+ path: "/v1/character-replace",
+ auth: ["bearerAuth"],
+ contentType: "application/json",
+ body: Schemas$V1CharacterReplaceCreateBody.out.parse(request),
+ responseSchema: Schemas$V1CharacterReplaceCreateResponse.in,
+ opts,
+ });
+ }
+}
diff --git a/src/resources/v1/index.ts b/src/resources/v1/index.ts
index 8ab8be0..ac5d7f3 100644
--- a/src/resources/v1/index.ts
+++ b/src/resources/v1/index.ts
@@ -8,6 +8,7 @@ export * as aiImageUpscaler from "./ai-image-upscaler";
export * as aiMemeGenerator from "./ai-meme-generator";
export * as aiQrCodeGenerator from "./ai-qr-code-generator";
export * as aiTalkingPhoto from "./ai-talking-photo";
+export * as aiVideoEditor from "./ai-video-editor";
export * as aiVoiceCloner from "./ai-voice-cloner";
export * as aiVoiceGenerator from "./ai-voice-generator";
export * as animation from "./animation";
@@ -15,6 +16,7 @@ export * as audioProjects from "./audio-projects";
export * as audioToVideo from "./audio-to-video";
export * as autoSubtitleGenerator from "./auto-subtitle-generator";
export * as bodySwap from "./body-swap";
+export * as characterReplace from "./character-replace";
export * as faceDetection from "./face-detection";
export * as faceSwap from "./face-swap";
export * as faceSwapPhoto from "./face-swap-photo";
diff --git a/src/resources/v1/resource-client.ts b/src/resources/v1/resource-client.ts
index 10ba16a..f829e3a 100644
--- a/src/resources/v1/resource-client.ts
+++ b/src/resources/v1/resource-client.ts
@@ -14,6 +14,7 @@ import { AiImageUpscalerClient } from "magic-hour/resources/v1/ai-image-upscaler
import { AiMemeGeneratorClient } from "magic-hour/resources/v1/ai-meme-generator";
import { AiQrCodeGeneratorClient } from "magic-hour/resources/v1/ai-qr-code-generator";
import { AiTalkingPhotoClient } from "magic-hour/resources/v1/ai-talking-photo";
+import { AiVideoEditorClient } from "magic-hour/resources/v1/ai-video-editor";
import { AiVoiceClonerClient } from "magic-hour/resources/v1/ai-voice-cloner";
import { AiVoiceGeneratorClient } from "magic-hour/resources/v1/ai-voice-generator";
import { AnimationClient } from "magic-hour/resources/v1/animation";
@@ -21,6 +22,7 @@ import { AudioProjectsClient } from "magic-hour/resources/v1/audio-projects";
import { AudioToVideoClient } from "magic-hour/resources/v1/audio-to-video";
import { AutoSubtitleGeneratorClient } from "magic-hour/resources/v1/auto-subtitle-generator";
import { BodySwapClient } from "magic-hour/resources/v1/body-swap";
+import { CharacterReplaceClient } from "magic-hour/resources/v1/character-replace";
import { FaceDetectionClient } from "magic-hour/resources/v1/face-detection";
import { FaceSwapClient } from "magic-hour/resources/v1/face-swap";
import { FaceSwapPhotoClient } from "magic-hour/resources/v1/face-swap-photo";
@@ -36,6 +38,8 @@ import { VideoProjectsClient } from "magic-hour/resources/v1/video-projects";
import { VideoToVideoClient } from "magic-hour/resources/v1/video-to-video";
export class V1Client extends CoreResourceClient {
+ private _characterReplaceLazy?: CharacterReplaceClient; // lazy-loading cache
+ private _aiVideoEditorLazy?: AiVideoEditorClient; // lazy-loading cache
private _audioToVideoLazy?: AudioToVideoClient; // lazy-loading cache
private _bodySwapLazy?: BodySwapClient; // lazy-loading cache
private _headSwapLazy?: HeadSwapClient; // lazy-loading cache
@@ -81,6 +85,7 @@ export class V1Client extends CoreResourceClient {
this.aiMemeGenerator;
this.aiQrCodeGenerator;
this.aiTalkingPhoto;
+ this.aiVideoEditor;
this.aiVoiceCloner;
this.aiVoiceGenerator;
this.animation;
@@ -88,6 +93,7 @@ export class V1Client extends CoreResourceClient {
this.audioToVideo;
this.autoSubtitleGenerator;
this.bodySwap;
+ this.characterReplace;
this.faceDetection;
this.faceSwap;
this.faceSwapPhoto;
@@ -427,4 +433,26 @@ export class V1Client extends CoreResourceClient {
))
);
}
+
+ get aiVideoEditor(): AiVideoEditorClient {
+ return (
+ this._aiVideoEditorLazy ??
+ (this._aiVideoEditorLazy =
+ new (require("./ai-video-editor").AiVideoEditorClient)(
+ this._client,
+ this._opts,
+ ))
+ );
+ }
+
+ get characterReplace(): CharacterReplaceClient {
+ return (
+ this._characterReplaceLazy ??
+ (this._characterReplaceLazy =
+ new (require("./character-replace").CharacterReplaceClient)(
+ this._client,
+ this._opts,
+ ))
+ );
+ }
}
diff --git a/src/types/index.ts b/src/types/index.ts
index d0e42d3..dd29689 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -49,6 +49,10 @@ export { V1AiTalkingPhotoCreateBodyAssets } from "./v1-ai-talking-photo-create-b
export { V1AiTalkingPhotoCreateBodyStyle } from "./v1-ai-talking-photo-create-body-style";
export { V1AiTalkingPhotoCreateBodyStyleGenerationModeEnum } from "./v1-ai-talking-photo-create-body-style-generation-mode-enum";
export { V1AiTalkingPhotoCreateResponse } from "./v1-ai-talking-photo-create-response";
+export { V1AiVideoEditorCreateBody } from "./v1-ai-video-editor-create-body";
+export { V1AiVideoEditorCreateBodyAssets } from "./v1-ai-video-editor-create-body-assets";
+export { V1AiVideoEditorCreateBodyStyle } from "./v1-ai-video-editor-create-body-style";
+export { V1AiVideoEditorCreateResponse } from "./v1-ai-video-editor-create-response";
export { V1AiVoiceClonerCreateBody } from "./v1-ai-voice-cloner-create-body";
export { V1AiVoiceClonerCreateBodyAssets } from "./v1-ai-voice-cloner-create-body-assets";
export { V1AiVoiceClonerCreateBodyStyle } from "./v1-ai-voice-cloner-create-body-style";
@@ -84,6 +88,14 @@ export { V1BodySwapCreateBody } from "./v1-body-swap-create-body";
export { V1BodySwapCreateBodyAssets } from "./v1-body-swap-create-body-assets";
export { V1BodySwapCreateBodyResolutionEnum } from "./v1-body-swap-create-body-resolution-enum";
export { V1BodySwapCreateResponse } from "./v1-body-swap-create-response";
+export { V1CharacterReplaceCreateBody } from "./v1-character-replace-create-body";
+export { V1CharacterReplaceCreateBodyAssets } from "./v1-character-replace-create-body-assets";
+export { V1CharacterReplaceCreateBodyResolutionEnum } from "./v1-character-replace-create-body-resolution-enum";
+export { V1CharacterReplaceCreateBodyStyle } from "./v1-character-replace-create-body-style";
+export { V1CharacterReplaceCreateBodyStyleModeEnum } from "./v1-character-replace-create-body-style-mode-enum";
+export { V1CharacterReplaceCreateBodyStylePointsItem } from "./v1-character-replace-create-body-style-points-item";
+export { V1CharacterReplaceCreateBodyStyleSelectionModeEnum } from "./v1-character-replace-create-body-style-selection-mode-enum";
+export { V1CharacterReplaceCreateResponse } from "./v1-character-replace-create-response";
export { V1FaceDetectionCreateBody } from "./v1-face-detection-create-body";
export { V1FaceDetectionCreateBodyAssets } from "./v1-face-detection-create-body-assets";
export { V1FaceDetectionCreateResponse } from "./v1-face-detection-create-response";
diff --git a/src/types/v1-ai-video-editor-create-body-assets.ts b/src/types/v1-ai-video-editor-create-body-assets.ts
new file mode 100644
index 0000000..7366f18
--- /dev/null
+++ b/src/types/v1-ai-video-editor-create-body-assets.ts
@@ -0,0 +1,66 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * Provide the assets for video editing.
+ */
+export type V1AiVideoEditorCreateBodyAssets = {
+ /**
+ * The video to edit. This value is either
+ * - a direct URL to the video file
+ * - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls).
+ *
+ * See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details.
+ *
+ */
+ videoFilePath: string;
+};
+
+/**
+ * @internal
+ * V1AiVideoEditorCreateBodyAssets without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1AiVideoEditorCreateBodyAssets = {
+ video_file_path: string;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1AiVideoEditorCreateBodyAssets
+ */
+const SchemaIn$V1AiVideoEditorCreateBodyAssets: z.ZodType<
+ V1AiVideoEditorCreateBodyAssets, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ video_file_path: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ video_file_path: "videoFilePath",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1AiVideoEditorCreateBodyAssets
+ */
+const SchemaOut$V1AiVideoEditorCreateBodyAssets: z.ZodType<
+ External$V1AiVideoEditorCreateBodyAssets, // output type of this zod object
+ z.ZodTypeDef,
+ V1AiVideoEditorCreateBodyAssets // the object to be transformed
+> = z
+ .object({
+ videoFilePath: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ videoFilePath: "video_file_path",
+ });
+ });
+
+export const Schemas$V1AiVideoEditorCreateBodyAssets = {
+ in: SchemaIn$V1AiVideoEditorCreateBodyAssets,
+ out: SchemaOut$V1AiVideoEditorCreateBodyAssets,
+};
diff --git a/src/types/v1-ai-video-editor-create-body-style.ts b/src/types/v1-ai-video-editor-create-body-style.ts
new file mode 100644
index 0000000..c030148
--- /dev/null
+++ b/src/types/v1-ai-video-editor-create-body-style.ts
@@ -0,0 +1,61 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * V1AiVideoEditorCreateBodyStyle
+ */
+export type V1AiVideoEditorCreateBodyStyle = {
+ /**
+ * The prompt used to edit the video.
+ */
+ prompt: string;
+};
+
+/**
+ * @internal
+ * V1AiVideoEditorCreateBodyStyle without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1AiVideoEditorCreateBodyStyle = {
+ prompt: string;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1AiVideoEditorCreateBodyStyle
+ */
+const SchemaIn$V1AiVideoEditorCreateBodyStyle: z.ZodType<
+ V1AiVideoEditorCreateBodyStyle, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ prompt: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ prompt: "prompt",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1AiVideoEditorCreateBodyStyle
+ */
+const SchemaOut$V1AiVideoEditorCreateBodyStyle: z.ZodType<
+ External$V1AiVideoEditorCreateBodyStyle, // output type of this zod object
+ z.ZodTypeDef,
+ V1AiVideoEditorCreateBodyStyle // the object to be transformed
+> = z
+ .object({
+ prompt: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ prompt: "prompt",
+ });
+ });
+
+export const Schemas$V1AiVideoEditorCreateBodyStyle = {
+ in: SchemaIn$V1AiVideoEditorCreateBodyStyle,
+ out: SchemaOut$V1AiVideoEditorCreateBodyStyle,
+};
diff --git a/src/types/v1-ai-video-editor-create-body.ts b/src/types/v1-ai-video-editor-create-body.ts
new file mode 100644
index 0000000..6d26e12
--- /dev/null
+++ b/src/types/v1-ai-video-editor-create-body.ts
@@ -0,0 +1,105 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+import {
+ External$V1AiVideoEditorCreateBodyAssets,
+ Schemas$V1AiVideoEditorCreateBodyAssets,
+ V1AiVideoEditorCreateBodyAssets,
+} from "./v1-ai-video-editor-create-body-assets";
+import {
+ External$V1AiVideoEditorCreateBodyStyle,
+ Schemas$V1AiVideoEditorCreateBodyStyle,
+ V1AiVideoEditorCreateBodyStyle,
+} from "./v1-ai-video-editor-create-body-style";
+
+/**
+ * V1AiVideoEditorCreateBody
+ */
+export type V1AiVideoEditorCreateBody = {
+ /**
+ * Provide the assets for video editing.
+ */
+ assets: V1AiVideoEditorCreateBodyAssets;
+ /**
+ * End time of your clip in seconds. Must be greater than `start_seconds`. Duration must be between 3 and 10 seconds.
+ */
+ endSeconds: number;
+ /**
+ * Give your video a custom name for easy identification.
+ */
+ name?: string | undefined;
+ /**
+ * Start time of your clip (seconds). Must be ≥ 0.
+ */
+ startSeconds?: number | undefined;
+ style: V1AiVideoEditorCreateBodyStyle;
+};
+
+/**
+ * @internal
+ * V1AiVideoEditorCreateBody without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1AiVideoEditorCreateBody = {
+ assets: External$V1AiVideoEditorCreateBodyAssets;
+ end_seconds: number;
+ name?: string | undefined;
+ start_seconds?: number | undefined;
+ style: External$V1AiVideoEditorCreateBodyStyle;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1AiVideoEditorCreateBody
+ */
+const SchemaIn$V1AiVideoEditorCreateBody: z.ZodType<
+ V1AiVideoEditorCreateBody, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ assets: Schemas$V1AiVideoEditorCreateBodyAssets.in,
+ end_seconds: z.number(),
+ name: z.string().optional(),
+ start_seconds: z.number().optional(),
+ style: Schemas$V1AiVideoEditorCreateBodyStyle.in,
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ end_seconds: "endSeconds",
+ name: "name",
+ start_seconds: "startSeconds",
+ style: "style",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1AiVideoEditorCreateBody
+ */
+const SchemaOut$V1AiVideoEditorCreateBody: z.ZodType<
+ External$V1AiVideoEditorCreateBody, // output type of this zod object
+ z.ZodTypeDef,
+ V1AiVideoEditorCreateBody // the object to be transformed
+> = z
+ .object({
+ assets: Schemas$V1AiVideoEditorCreateBodyAssets.out,
+ endSeconds: z.number(),
+ name: z.string().optional(),
+ startSeconds: z.number().optional(),
+ style: Schemas$V1AiVideoEditorCreateBodyStyle.out,
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ endSeconds: "end_seconds",
+ name: "name",
+ startSeconds: "start_seconds",
+ style: "style",
+ });
+ });
+
+export const Schemas$V1AiVideoEditorCreateBody = {
+ in: SchemaIn$V1AiVideoEditorCreateBody,
+ out: SchemaOut$V1AiVideoEditorCreateBody,
+};
diff --git a/src/types/v1-ai-video-editor-create-response.ts b/src/types/v1-ai-video-editor-create-response.ts
new file mode 100644
index 0000000..a610d35
--- /dev/null
+++ b/src/types/v1-ai-video-editor-create-response.ts
@@ -0,0 +1,83 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * Success
+ */
+export type V1AiVideoEditorCreateResponse = {
+ /**
+ * The amount of credits deducted from your account to generate the video. If the status is not 'complete', this value is an estimate and may be adjusted upon completion based on the actual FPS of the output video.
+ *
+ * If video generation fails, credits will be refunded, and this field will be updated to include the refund.
+ */
+ creditsCharged: number;
+ /**
+ * Deprecated: Previously represented the number of frames (original name of our credit system) used for video generation. Use 'credits_charged' instead.
+ *
+ * The amount of frames used to generate the video. If the status is not 'complete', the cost is an estimate and will be adjusted when the video completes.
+ */
+ estimatedFrameCost: number;
+ /**
+ * Unique ID of the video. Use it with the [Get video Project API](https://docs.magichour.ai/api-reference/video-projects/get-video-details) to fetch status and downloads.
+ */
+ id: string;
+};
+
+/**
+ * @internal
+ * V1AiVideoEditorCreateResponse without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1AiVideoEditorCreateResponse = {
+ credits_charged: number;
+ estimated_frame_cost: number;
+ id: string;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1AiVideoEditorCreateResponse
+ */
+const SchemaIn$V1AiVideoEditorCreateResponse: z.ZodType<
+ V1AiVideoEditorCreateResponse, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ credits_charged: z.number().int(),
+ estimated_frame_cost: z.number().int(),
+ id: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ credits_charged: "creditsCharged",
+ estimated_frame_cost: "estimatedFrameCost",
+ id: "id",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1AiVideoEditorCreateResponse
+ */
+const SchemaOut$V1AiVideoEditorCreateResponse: z.ZodType<
+ External$V1AiVideoEditorCreateResponse, // output type of this zod object
+ z.ZodTypeDef,
+ V1AiVideoEditorCreateResponse // the object to be transformed
+> = z
+ .object({
+ creditsCharged: z.number().int(),
+ estimatedFrameCost: z.number().int(),
+ id: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ creditsCharged: "credits_charged",
+ estimatedFrameCost: "estimated_frame_cost",
+ id: "id",
+ });
+ });
+
+export const Schemas$V1AiVideoEditorCreateResponse = {
+ in: SchemaIn$V1AiVideoEditorCreateResponse,
+ out: SchemaOut$V1AiVideoEditorCreateResponse,
+};
diff --git a/src/types/v1-character-replace-create-body-assets.ts b/src/types/v1-character-replace-create-body-assets.ts
new file mode 100644
index 0000000..773bd08
--- /dev/null
+++ b/src/types/v1-character-replace-create-body-assets.ts
@@ -0,0 +1,80 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * Source video and reference character image for the job.
+ */
+export type V1CharacterReplaceCreateBodyAssets = {
+ /**
+ * Reference character image used as the replacement or animation target. This value is either
+ * - a direct URL to the video file
+ * - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls).
+ *
+ * See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details.
+ *
+ */
+ imageFilePath: string;
+ /**
+ * Source video containing the subject to replace or animate. This value is either
+ * - a direct URL to the video file
+ * - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls).
+ *
+ * See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details.
+ *
+ */
+ videoFilePath: string;
+};
+
+/**
+ * @internal
+ * V1CharacterReplaceCreateBodyAssets without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1CharacterReplaceCreateBodyAssets = {
+ image_file_path: string;
+ video_file_path: string;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1CharacterReplaceCreateBodyAssets
+ */
+const SchemaIn$V1CharacterReplaceCreateBodyAssets: z.ZodType<
+ V1CharacterReplaceCreateBodyAssets, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ image_file_path: z.string(),
+ video_file_path: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ image_file_path: "imageFilePath",
+ video_file_path: "videoFilePath",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1CharacterReplaceCreateBodyAssets
+ */
+const SchemaOut$V1CharacterReplaceCreateBodyAssets: z.ZodType<
+ External$V1CharacterReplaceCreateBodyAssets, // output type of this zod object
+ z.ZodTypeDef,
+ V1CharacterReplaceCreateBodyAssets // the object to be transformed
+> = z
+ .object({
+ imageFilePath: z.string(),
+ videoFilePath: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ imageFilePath: "image_file_path",
+ videoFilePath: "video_file_path",
+ });
+ });
+
+export const Schemas$V1CharacterReplaceCreateBodyAssets = {
+ in: SchemaIn$V1CharacterReplaceCreateBodyAssets,
+ out: SchemaOut$V1CharacterReplaceCreateBodyAssets,
+};
diff --git a/src/types/v1-character-replace-create-body-resolution-enum.ts b/src/types/v1-character-replace-create-body-resolution-enum.ts
new file mode 100644
index 0000000..af3196c
--- /dev/null
+++ b/src/types/v1-character-replace-create-body-resolution-enum.ts
@@ -0,0 +1,4 @@
+/**
+ * Output video resolution. Defaults to 480p, the lowest resolution available on your plan.
+ */
+export type V1CharacterReplaceCreateBodyResolutionEnum = "480p" | "720p";
diff --git a/src/types/v1-character-replace-create-body-style-mode-enum.ts b/src/types/v1-character-replace-create-body-style-mode-enum.ts
new file mode 100644
index 0000000..f80d878
--- /dev/null
+++ b/src/types/v1-character-replace-create-body-style-mode-enum.ts
@@ -0,0 +1,4 @@
+/**
+ * Processing mode. `replace` swaps the detected subject with your reference character. `animate` transfers motion from the video onto your character image.
+ */
+export type V1CharacterReplaceCreateBodyStyleModeEnum = "animate" | "replace";
diff --git a/src/types/v1-character-replace-create-body-style-points-item.ts b/src/types/v1-character-replace-create-body-style-points-item.ts
new file mode 100644
index 0000000..fd596c9
--- /dev/null
+++ b/src/types/v1-character-replace-create-body-style-points-item.ts
@@ -0,0 +1,79 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * V1CharacterReplaceCreateBodyStylePointsItem
+ */
+export type V1CharacterReplaceCreateBodyStylePointsItem = {
+ /**
+ * Horizontal pixel coordinate in the source video frame at `time_seconds`, measured from the left edge.
+ */
+ positionX: number;
+ /**
+ * Vertical pixel coordinate in the source video frame at `time_seconds`, measured from the top edge.
+ */
+ positionY: number;
+ /**
+ * Timestamp on the source video timeline in seconds. Uses the same clock as `start_seconds` and `end_seconds`.
+ */
+ timeSeconds: number;
+};
+
+/**
+ * @internal
+ * V1CharacterReplaceCreateBodyStylePointsItem without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1CharacterReplaceCreateBodyStylePointsItem = {
+ position_x: number;
+ position_y: number;
+ time_seconds: number;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1CharacterReplaceCreateBodyStylePointsItem
+ */
+const SchemaIn$V1CharacterReplaceCreateBodyStylePointsItem: z.ZodType<
+ V1CharacterReplaceCreateBodyStylePointsItem, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ position_x: z.number().int(),
+ position_y: z.number().int(),
+ time_seconds: z.number(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ position_x: "positionX",
+ position_y: "positionY",
+ time_seconds: "timeSeconds",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1CharacterReplaceCreateBodyStylePointsItem
+ */
+const SchemaOut$V1CharacterReplaceCreateBodyStylePointsItem: z.ZodType<
+ External$V1CharacterReplaceCreateBodyStylePointsItem, // output type of this zod object
+ z.ZodTypeDef,
+ V1CharacterReplaceCreateBodyStylePointsItem // the object to be transformed
+> = z
+ .object({
+ positionX: z.number().int(),
+ positionY: z.number().int(),
+ timeSeconds: z.number(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ positionX: "position_x",
+ positionY: "position_y",
+ timeSeconds: "time_seconds",
+ });
+ });
+
+export const Schemas$V1CharacterReplaceCreateBodyStylePointsItem = {
+ in: SchemaIn$V1CharacterReplaceCreateBodyStylePointsItem,
+ out: SchemaOut$V1CharacterReplaceCreateBodyStylePointsItem,
+};
diff --git a/src/types/v1-character-replace-create-body-style-selection-mode-enum.ts b/src/types/v1-character-replace-create-body-style-selection-mode-enum.ts
new file mode 100644
index 0000000..7d45ef9
--- /dev/null
+++ b/src/types/v1-character-replace-create-body-style-selection-mode-enum.ts
@@ -0,0 +1,6 @@
+/**
+ * How to locate the subject in the source video. `auto` detects a person automatically. `point` uses your `points` to mark the subject. Defaults to `auto`.
+ */
+export type V1CharacterReplaceCreateBodyStyleSelectionModeEnum =
+ | "auto"
+ | "point";
diff --git a/src/types/v1-character-replace-create-body-style.ts b/src/types/v1-character-replace-create-body-style.ts
new file mode 100644
index 0000000..cdafcb8
--- /dev/null
+++ b/src/types/v1-character-replace-create-body-style.ts
@@ -0,0 +1,89 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+import {
+ External$V1CharacterReplaceCreateBodyStylePointsItem,
+ Schemas$V1CharacterReplaceCreateBodyStylePointsItem,
+ V1CharacterReplaceCreateBodyStylePointsItem,
+} from "./v1-character-replace-create-body-style-points-item";
+
+/**
+ * Optional style controls for replace vs animate mode and subject selection.
+ */
+export type V1CharacterReplaceCreateBodyStyle = {
+ /**
+ * Processing mode. `replace` swaps the detected subject with your reference character. `animate` transfers motion from the video onto your character image.
+ */
+ mode?: ("animate" | "replace") | undefined;
+ /**
+ * On-frame markers for manual subject selection. Required when `selection_mode` is `point`. Ignored when `selection_mode` is `auto` or omitted.
+ */
+ points?: V1CharacterReplaceCreateBodyStylePointsItem[] | undefined;
+ /**
+ * How to locate the subject in the source video. `auto` detects a person automatically. `point` uses your `points` to mark the subject. Defaults to `auto`.
+ */
+ selectionMode?: ("auto" | "point") | undefined;
+};
+
+/**
+ * @internal
+ * V1CharacterReplaceCreateBodyStyle without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1CharacterReplaceCreateBodyStyle = {
+ mode?: ("animate" | "replace") | undefined;
+ points?: External$V1CharacterReplaceCreateBodyStylePointsItem[] | undefined;
+ selection_mode?: ("auto" | "point") | undefined;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1CharacterReplaceCreateBodyStyle
+ */
+const SchemaIn$V1CharacterReplaceCreateBodyStyle: z.ZodType<
+ V1CharacterReplaceCreateBodyStyle, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ mode: z.enum(["animate", "replace"]).optional(),
+ points: z
+ .array(Schemas$V1CharacterReplaceCreateBodyStylePointsItem.in)
+ .optional(),
+ selection_mode: z.enum(["auto", "point"]).optional(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ mode: "mode",
+ points: "points",
+ selection_mode: "selectionMode",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1CharacterReplaceCreateBodyStyle
+ */
+const SchemaOut$V1CharacterReplaceCreateBodyStyle: z.ZodType<
+ External$V1CharacterReplaceCreateBodyStyle, // output type of this zod object
+ z.ZodTypeDef,
+ V1CharacterReplaceCreateBodyStyle // the object to be transformed
+> = z
+ .object({
+ mode: z.enum(["animate", "replace"]).optional(),
+ points: z
+ .array(Schemas$V1CharacterReplaceCreateBodyStylePointsItem.out)
+ .optional(),
+ selectionMode: z.enum(["auto", "point"]).optional(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ mode: "mode",
+ points: "points",
+ selectionMode: "selection_mode",
+ });
+ });
+
+export const Schemas$V1CharacterReplaceCreateBodyStyle = {
+ in: SchemaIn$V1CharacterReplaceCreateBodyStyle,
+ out: SchemaOut$V1CharacterReplaceCreateBodyStyle,
+};
diff --git a/src/types/v1-character-replace-create-body.ts b/src/types/v1-character-replace-create-body.ts
new file mode 100644
index 0000000..dbf0fa9
--- /dev/null
+++ b/src/types/v1-character-replace-create-body.ts
@@ -0,0 +1,117 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+import {
+ External$V1CharacterReplaceCreateBodyAssets,
+ Schemas$V1CharacterReplaceCreateBodyAssets,
+ V1CharacterReplaceCreateBodyAssets,
+} from "./v1-character-replace-create-body-assets";
+import {
+ External$V1CharacterReplaceCreateBodyStyle,
+ Schemas$V1CharacterReplaceCreateBodyStyle,
+ V1CharacterReplaceCreateBodyStyle,
+} from "./v1-character-replace-create-body-style";
+
+/**
+ * V1CharacterReplaceCreateBody
+ */
+export type V1CharacterReplaceCreateBody = {
+ /**
+ * Source video and reference character image for the job.
+ */
+ assets: V1CharacterReplaceCreateBodyAssets;
+ /**
+ * End time of your clip (seconds). Must be greater than start_seconds.
+ */
+ endSeconds: number;
+ /**
+ * Give your video a custom name for easy identification.
+ */
+ name?: string | undefined;
+ /**
+ * Output video resolution. Defaults to 480p, the lowest resolution available on your plan.
+ */
+ resolution?: ("480p" | "720p") | undefined;
+ /**
+ * Start time of your clip (seconds). Must be ≥ 0.
+ */
+ startSeconds?: number | undefined;
+ /**
+ * Optional style controls for replace vs animate mode and subject selection.
+ */
+ style?: V1CharacterReplaceCreateBodyStyle | undefined;
+};
+
+/**
+ * @internal
+ * V1CharacterReplaceCreateBody without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1CharacterReplaceCreateBody = {
+ assets: External$V1CharacterReplaceCreateBodyAssets;
+ end_seconds: number;
+ name?: string | undefined;
+ resolution?: ("480p" | "720p") | undefined;
+ start_seconds?: number | undefined;
+ style?: External$V1CharacterReplaceCreateBodyStyle | undefined;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1CharacterReplaceCreateBody
+ */
+const SchemaIn$V1CharacterReplaceCreateBody: z.ZodType<
+ V1CharacterReplaceCreateBody, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ assets: Schemas$V1CharacterReplaceCreateBodyAssets.in,
+ end_seconds: z.number(),
+ name: z.string().optional(),
+ resolution: z.enum(["480p", "720p"]).optional(),
+ start_seconds: z.number().optional(),
+ style: Schemas$V1CharacterReplaceCreateBodyStyle.in.optional(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ end_seconds: "endSeconds",
+ name: "name",
+ resolution: "resolution",
+ start_seconds: "startSeconds",
+ style: "style",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1CharacterReplaceCreateBody
+ */
+const SchemaOut$V1CharacterReplaceCreateBody: z.ZodType<
+ External$V1CharacterReplaceCreateBody, // output type of this zod object
+ z.ZodTypeDef,
+ V1CharacterReplaceCreateBody // the object to be transformed
+> = z
+ .object({
+ assets: Schemas$V1CharacterReplaceCreateBodyAssets.out,
+ endSeconds: z.number(),
+ name: z.string().optional(),
+ resolution: z.enum(["480p", "720p"]).optional(),
+ startSeconds: z.number().optional(),
+ style: Schemas$V1CharacterReplaceCreateBodyStyle.out.optional(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ endSeconds: "end_seconds",
+ name: "name",
+ resolution: "resolution",
+ startSeconds: "start_seconds",
+ style: "style",
+ });
+ });
+
+export const Schemas$V1CharacterReplaceCreateBody = {
+ in: SchemaIn$V1CharacterReplaceCreateBody,
+ out: SchemaOut$V1CharacterReplaceCreateBody,
+};
diff --git a/src/types/v1-character-replace-create-response.ts b/src/types/v1-character-replace-create-response.ts
new file mode 100644
index 0000000..ffae9d9
--- /dev/null
+++ b/src/types/v1-character-replace-create-response.ts
@@ -0,0 +1,83 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * Success
+ */
+export type V1CharacterReplaceCreateResponse = {
+ /**
+ * The amount of credits deducted from your account to generate the video. If the status is not 'complete', this value is an estimate and may be adjusted upon completion based on the actual FPS of the output video.
+ *
+ * If video generation fails, credits will be refunded, and this field will be updated to include the refund.
+ */
+ creditsCharged: number;
+ /**
+ * Deprecated: Previously represented the number of frames (original name of our credit system) used for video generation. Use 'credits_charged' instead.
+ *
+ * The amount of frames used to generate the video. If the status is not 'complete', the cost is an estimate and will be adjusted when the video completes.
+ */
+ estimatedFrameCost: number;
+ /**
+ * Unique ID of the video. Use it with the [Get video Project API](https://docs.magichour.ai/api-reference/video-projects/get-video-details) to fetch status and downloads.
+ */
+ id: string;
+};
+
+/**
+ * @internal
+ * V1CharacterReplaceCreateResponse without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1CharacterReplaceCreateResponse = {
+ credits_charged: number;
+ estimated_frame_cost: number;
+ id: string;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1CharacterReplaceCreateResponse
+ */
+const SchemaIn$V1CharacterReplaceCreateResponse: z.ZodType<
+ V1CharacterReplaceCreateResponse, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ credits_charged: z.number().int(),
+ estimated_frame_cost: z.number().int(),
+ id: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ credits_charged: "creditsCharged",
+ estimated_frame_cost: "estimatedFrameCost",
+ id: "id",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1CharacterReplaceCreateResponse
+ */
+const SchemaOut$V1CharacterReplaceCreateResponse: z.ZodType<
+ External$V1CharacterReplaceCreateResponse, // output type of this zod object
+ z.ZodTypeDef,
+ V1CharacterReplaceCreateResponse // the object to be transformed
+> = z
+ .object({
+ creditsCharged: z.number().int(),
+ estimatedFrameCost: z.number().int(),
+ id: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ creditsCharged: "credits_charged",
+ estimatedFrameCost: "estimated_frame_cost",
+ id: "id",
+ });
+ });
+
+export const Schemas$V1CharacterReplaceCreateResponse = {
+ in: SchemaIn$V1CharacterReplaceCreateResponse,
+ out: SchemaOut$V1CharacterReplaceCreateResponse,
+};
diff --git a/test/v1-ai-video-editor.test.ts b/test/v1-ai-video-editor.test.ts
new file mode 100644
index 0000000..f2fdd35
--- /dev/null
+++ b/test/v1-ai-video-editor.test.ts
@@ -0,0 +1,35 @@
+import { Client, Environment } from "magic-hour";
+
+describe("tests client.v1.aiVideoEditor.create", () => {
+ test.concurrent(
+ "POST /v1/ai-video-editor | testId: success_all_params | Success test with response schema validation. Expects status code 200",
+ async () => {
+ const client = new Client({
+ token: "API_TOKEN",
+ environment: Environment.MockServer,
+ });
+ // Get both raw response for status and parsed response for data
+ const [rawResponse, response] = await Promise.all([
+ client.v1.aiVideoEditor
+ .create({
+ assets: { videoFilePath: "api-assets/id/1234.mp4" },
+ endSeconds: 5.0,
+ name: "My Video Editor video",
+ startSeconds: 0.0,
+ style: { prompt: "Change the car color to blue" },
+ })
+ .asResponse(),
+ client.v1.aiVideoEditor.create({
+ assets: { videoFilePath: "api-assets/id/1234.mp4" },
+ endSeconds: 5.0,
+ name: "My Video Editor video",
+ startSeconds: 0.0,
+ style: { prompt: "Change the car color to blue" },
+ }),
+ ]);
+ expect(rawResponse.status).toBe(200); // Exact status code match
+ // Response body automatically validated by Zod schema during deserialization
+ expect(response).toBeDefined();
+ },
+ );
+});
diff --git a/test/v1-character-replace.test.ts b/test/v1-character-replace.test.ts
new file mode 100644
index 0000000..967d61e
--- /dev/null
+++ b/test/v1-character-replace.test.ts
@@ -0,0 +1,43 @@
+import { Client, Environment } from "magic-hour";
+
+describe("tests client.v1.characterReplace.create", () => {
+ test.concurrent(
+ "POST /v1/character-replace | testId: success_all_params | Success test with response schema validation. Expects status code 200",
+ async () => {
+ const client = new Client({
+ token: "API_TOKEN",
+ environment: Environment.MockServer,
+ });
+ // Get both raw response for status and parsed response for data
+ const [rawResponse, response] = await Promise.all([
+ client.v1.characterReplace
+ .create({
+ assets: {
+ imageFilePath: "api-assets/id/5678.png",
+ videoFilePath: "api-assets/id/1234.mp4",
+ },
+ endSeconds: 15.0,
+ name: "My Character Replace video",
+ resolution: "720p",
+ startSeconds: 0.0,
+ style: { mode: "replace", selectionMode: "auto" },
+ })
+ .asResponse(),
+ client.v1.characterReplace.create({
+ assets: {
+ imageFilePath: "api-assets/id/5678.png",
+ videoFilePath: "api-assets/id/1234.mp4",
+ },
+ endSeconds: 15.0,
+ name: "My Character Replace video",
+ resolution: "720p",
+ startSeconds: 0.0,
+ style: { mode: "replace", selectionMode: "auto" },
+ }),
+ ]);
+ expect(rawResponse.status).toBe(200); // Exact status code match
+ // Response body automatically validated by Zod schema during deserialization
+ expect(response).toBeDefined();
+ },
+ );
+});