diff --git a/.sdk.json b/.sdk.json index 726a695..66a784a 100644 --- a/.sdk.json +++ b/.sdk.json @@ -1,5 +1,5 @@ { - "id": "8edb506f-a1f6-47a0-bbd1-318c71e84e5a", + "id": "88e585d6-f705-42ee-a9cf-1e80c832f4c3", "tracked_paths": [ { "editable": true, @@ -289,6 +289,22 @@ "editable": true, "path": "src/resources/v1/auto-subtitle-generator/resource-client.ts" }, + { + "editable": true, + "path": "src/resources/v1/body-swap/README.md" + }, + { + "editable": true, + "path": "src/resources/v1/body-swap/index.ts" + }, + { + "editable": false, + "path": "src/resources/v1/body-swap/request-types.ts" + }, + { + "editable": true, + "path": "src/resources/v1/body-swap/resource-client.ts" + }, { "editable": true, "path": "src/resources/v1/face-detection/README.md" @@ -833,6 +849,22 @@ "editable": false, "path": "src/types/v1-auto-subtitle-generator-create-response.ts" }, + { + "editable": false, + "path": "src/types/v1-body-swap-create-body-assets.ts" + }, + { + "editable": false, + "path": "src/types/v1-body-swap-create-body-resolution-enum.ts" + }, + { + "editable": false, + "path": "src/types/v1-body-swap-create-body.ts" + }, + { + "editable": false, + "path": "src/types/v1-body-swap-create-response.ts" + }, { "editable": false, "path": "src/types/v1-face-detection-create-body-assets.ts" @@ -1185,6 +1217,10 @@ "editable": false, "path": "test/v1-auto-subtitle-generator.test.ts" }, + { + "editable": false, + "path": "test/v1-body-swap.test.ts" + }, { "editable": false, "path": "test/v1-face-detection.test.ts" diff --git a/README.md b/README.md index b6fa617..e1bb4b1 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,11 @@ Valid values are: `none`, `error`, `warn`, `info`, `debug` (case insensitive). I - [create](src/resources/v1/auto-subtitle-generator/README.md#create) - Auto Subtitle Generator - [generate](src/resources/v1/auto-subtitle-generator/README.md#generate) - Auto Subtitle Generator Generate Workflow +### [v1.bodySwap](src/resources/v1/body-swap/README.md) + +- [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.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 59c26d8..f9d6578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "magic-hour", - "version": "0.60.0", + "version": "0.61.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "magic-hour", - "version": "0.60.0", + "version": "0.61.0", "license": "MIT", "dependencies": { "make-api-request-js": "^0.2.1", diff --git a/package.json b/package.json index 2fbcd75..a4e3ce5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "magic-hour", - "version": "0.60.0", + "version": "0.61.0", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { diff --git a/src/README.md b/src/README.md index 8ea3422..031c9f2 100644 --- a/src/README.md +++ b/src/README.md @@ -17,6 +17,7 @@ - [animation](resources/v1/animation/README.md) - animation - [audio-projects](resources/v1/audio-projects/README.md) - audio-projects - [auto-subtitle-generator](resources/v1/auto-subtitle-generator/README.md) - auto-subtitle-generator +- [body-swap](resources/v1/body-swap/README.md) - body-swap - [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 e5f8405..9278f1e 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.61.1", + MockServer = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.62.0", } diff --git a/src/resources/v1/README.md b/src/resources/v1/README.md index 5e5986c..8bac326 100644 --- a/src/resources/v1/README.md +++ b/src/resources/v1/README.md @@ -17,6 +17,7 @@ - [animation](animation/README.md) - animation - [audio-projects](audio-projects/README.md) - audio-projects - [auto-subtitle-generator](auto-subtitle-generator/README.md) - auto-subtitle-generator +- [body-swap](body-swap/README.md) - body-swap - [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-image-generator/README.md b/src/resources/v1/ai-image-generator/README.md index a4913a6..75e36a0 100644 --- a/src/resources/v1/ai-image-generator/README.md +++ b/src/resources/v1/ai-image-generator/README.md @@ -57,18 +57,18 @@ Create an AI image with advanced model selection and quality controls. #### Parameters -| Parameter | Required | Deprecated | Description | Example | -| ---------------- | :------: | :--------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -| `imageCount` | ✓ | ✗ | Number of images to generate. Maximum varies by model. | `1` | -| `style` | ✓ | ✗ | The art style to use for image generation. | `{"prompt": "Cool image", "tool": "ai-anime-generator"}` | -| `└─ prompt` | ✓ | — | The prompt used for the image(s). | `"Cool image"` | -| `└─ qualityMode` | ✗ | ✓ | DEPRECATED: Use `model` field instead for explicit model selection. Legacy quality mode mapping: - `standard` → `z-image-turbo` model - `pro` → `seedream-v4` model If model is specified, it will take precedence over the legacy quality_mode field. | `"pro"` | -| `└─ tool` | ✗ | — | The art style to use for image generation. Defaults to 'general' if not provided. | `"ai-anime-generator"` | -| `aspectRatio` | ✗ | ✗ | The aspect ratio of the output image(s). If not specified, defaults to `1:1` (square). | `"1:1"` | -| `model` | ✗ | ✗ | The AI model to use for image generation. Each model has different capabilities and costs. **Models:** - `default` - Use the model we recommend, which will change over time. This is recommended unless you need a specific model. This is the default behavior. - `flux-schnell` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `z-image-turbo` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `seedream-v4` - from 40 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana` - from 50 credits/image - Supported resolutions: 640px, 1k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana-2` - from 100 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana-pro` - from 150 credits/image - Supported resolutions: 1k, 2k, 4k - Available for tiers: creator, pro, business - Image count allowed: 1, 4, 9, 16 **Deprecated Enum Values:** - `seedream` - Use `seedream-v4` instead. | `"default"` | -| `name` | ✗ | ✗ | Give your image a custom name for easy identification. | `"My Ai Image image"` | -| `orientation` | ✗ | ✓ | DEPRECATED: Use `aspect_ratio` instead. The orientation of the output image(s). `aspect_ratio` takes precedence when `orientation` if both are provided. | `"landscape"` | -| `resolution` | ✗ | ✗ | Maximum resolution (longest edge) for the output image. **Options:** - `640px` — up to 640px - `1k` — up to 1024px - `2k` — up to 2048px - `4k` — up to 4096px - `auto` — **Deprecated.** Mapped server-side from your subscription tier to the best matching resolution the model supports **Per-model support:** - `flux-schnell` - 640px, 1k, 2k - `z-image-turbo` - 640px, 1k, 2k - `seedream-v4` - 640px, 1k, 2k, 4k - `nano-banana` - 640px, 1k - `nano-banana-2` - 640px, 1k, 2k, 4k - `nano-banana-pro` - 1k, 2k, 4k Note: Resolution availability depends on the model and your subscription tier. | `"auto"` | +| Parameter | Required | Deprecated | Description | Example | +| ---------------- | :------: | :--------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | +| `imageCount` | ✓ | ✗ | Number of images to generate. Maximum varies by model. | `1` | +| `style` | ✓ | ✗ | The art style to use for image generation. | `{"prompt": "Cool image", "tool": "ai-anime-generator"}` | +| `└─ prompt` | ✓ | — | The prompt used for the image(s). | `"Cool image"` | +| `└─ qualityMode` | ✗ | ✓ | DEPRECATED: Use `model` field instead for explicit model selection. Legacy quality mode mapping: - `standard` → `z-image-turbo` model - `pro` → `seedream-v4` model If model is specified, it will take precedence over the legacy quality_mode field. | `"pro"` | +| `└─ tool` | ✗ | — | The art style to use for image generation. Defaults to 'general' if not provided. | `"ai-anime-generator"` | +| `aspectRatio` | ✗ | ✗ | The aspect ratio of the output image(s). If not specified, defaults to `1:1` (square). | `"1:1"` | +| `model` | ✗ | ✗ | The AI model to use for image generation. Each model has different capabilities and costs. **Models:** - `default` - Use the model we recommend, which will change over time. This is recommended unless you need a specific model. This is the default behavior. - `flux-schnell` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `z-image-turbo` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `seedream-v4` - from 40 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana` - from 50 credits/image - Supported resolutions: 640px, 1k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana-2` - from 100 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 4, 9, 16 - `nano-banana-pro` - from 150 credits/image - Supported resolutions: 1k, 2k, 4k - Available for tiers: creator, pro, business - Image count allowed: 1, 4, 9, 16 **Deprecated Enum Values:** - `seedream` - Use `seedream-v4` instead. | `"default"` | +| `name` | ✗ | ✗ | Give your image a custom name for easy identification. | `"My Ai Image image"` | +| `orientation` | ✗ | ✓ | DEPRECATED: Use `aspect_ratio` instead. The orientation of the output image(s). `aspect_ratio` takes precedence when `orientation` if both are provided. | `"landscape"` | +| `resolution` | ✗ | ✗ | Maximum resolution (longest edge) for the output image. **Options:** - `640px` — up to 640px - `1k` — up to 1024px - `2k` — up to 2048px - `4k` — up to 4096px - `auto` — **Deprecated.** Mapped server-side from your subscription tier to the best matching resolution the model supports **Per-model support:** - `flux-schnell` - 640px, 1k, 2k - `z-image-turbo` - 640px, 1k, 2k - `seedream-v4` - 640px, 1k, 2k, 4k - `nano-banana` - 640px, 1k - `nano-banana-2` - 640px, 1k, 2k, 4k - `nano-banana-pro` - 1k, 2k, 4k Note: Resolution availability depends on the model and your subscription tier. | `"auto"` | #### Example Snippet diff --git a/src/resources/v1/ai-image-generator/request-types.ts b/src/resources/v1/ai-image-generator/request-types.ts index 595ef8c..41a80b8 100644 --- a/src/resources/v1/ai-image-generator/request-types.ts +++ b/src/resources/v1/ai-image-generator/request-types.ts @@ -43,7 +43,7 @@ export type CreateRequest = { * - `nano-banana-2` - from 100 credits/image * - Supported resolutions: 640px, 1k, 2k, 4k * - Available for tiers: free, creator, pro, business - * - Image count allowed: 1, 2, 3, 4 + * - Image count allowed: 1, 4, 9, 16 * - `nano-banana-pro` - from 150 credits/image * - Supported resolutions: 1k, 2k, 4k * - Available for tiers: creator, pro, business diff --git a/src/resources/v1/body-swap/README.md b/src/resources/v1/body-swap/README.md new file mode 100644 index 0000000..929a4e5 --- /dev/null +++ b/src/resources/v1/body-swap/README.md @@ -0,0 +1,94 @@ +# v1.body-swap + +## Module Functions + + + +### Body Swap 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.bodySwap.generate( + { + assets: { + personFilePath: "/path/to/person.png", + sceneFilePath: "/path/to/scene.png", + }, + name: "My Body Swap image", + resolution: "1k", + }, + { + waitForCompletion: true, + downloadOutputs: true, + downloadDirectory: ".", + }, +); +``` + + + +### Body Swap + +Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward). + +**API Endpoint**: `POST /v1/body-swap` + +#### Parameters + +| Parameter | Required | Description | Example | +| ------------------- | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `assets` | ✓ | Person image and scene image for body swap | `{"personFilePath": "api-assets/id/1234.png", "sceneFilePath": "api-assets/id/5678.png"}` | +| `└─ personFilePath` | ✓ | Image of the person to place into the scene. 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.png"` | +| `└─ sceneFilePath` | ✓ | Target scene image (background). 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"` | +| `resolution` | ✓ | Output resolution. Determines credits charged for the run. | `"1k"` | +| `name` | ✗ | Give your image a custom name for easy identification. | `"My Body Swap image"` | + +#### Example Snippet + +```typescript +import { Client } from "magic-hour"; + +const client = new Client({ token: process.env["API_TOKEN"]!! }); +const res = await client.v1.bodySwap.create({ + assets: { + personFilePath: "api-assets/id/1234.png", + sceneFilePath: "api-assets/id/5678.png", + }, + name: "My Body Swap image", + resolution: "1k", +}); +``` + +#### Response + +##### Type + +[V1BodySwapCreateResponse](/src/types/v1-body-swap-create-response.ts) + +##### Example + +```typescript +{"creditsCharged": 100, "frameCost": 100, "id": "cuid-example"} +``` diff --git a/src/resources/v1/body-swap/index.ts b/src/resources/v1/body-swap/index.ts new file mode 100644 index 0000000..673a665 --- /dev/null +++ b/src/resources/v1/body-swap/index.ts @@ -0,0 +1,2 @@ +export { CreateRequest } from "./request-types"; +export { BodySwapClient } from "./resource-client"; diff --git a/src/resources/v1/body-swap/request-types.ts b/src/resources/v1/body-swap/request-types.ts new file mode 100644 index 0000000..fbdbca7 --- /dev/null +++ b/src/resources/v1/body-swap/request-types.ts @@ -0,0 +1,85 @@ +import { zodTransform } from "make-api-request-js"; +import * as z from "zod"; + +import { + External$V1BodySwapCreateBodyAssets, + Schemas$V1BodySwapCreateBodyAssets, + V1BodySwapCreateBodyAssets, +} from "magic-hour/types/v1-body-swap-create-body-assets"; + +/** + * CreateRequest + */ +export type CreateRequest = { + /** + * Person image and scene image for body swap + */ + assets: V1BodySwapCreateBodyAssets; + /** + * Give your image a custom name for easy identification. + */ + name?: string | undefined; + /** + * Output resolution. Determines credits charged for the run. + */ + resolution: "1k" | "2k" | "4k" | "640px"; +}; + +/** + * @internal + * CreateRequest without any key transformation, this is what + * we expect to come in as network data + */ +export type External$CreateRequest = { + assets: External$V1BodySwapCreateBodyAssets; + name?: string | undefined; + resolution: "1k" | "2k" | "4k" | "640px"; +}; + +/** + * 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$V1BodySwapCreateBodyAssets.in, + name: z.string().optional(), + resolution: z.enum(["1k", "2k", "4k", "640px"]), + }) + .transform((obj) => { + return zodTransform(obj, { + assets: "assets", + name: "name", + resolution: "resolution", + }); + }); + +/** + * @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$V1BodySwapCreateBodyAssets.out, + name: z.string().optional(), + resolution: z.enum(["1k", "2k", "4k", "640px"]), + }) + .transform((obj) => { + return zodTransform(obj, { + assets: "assets", + name: "name", + resolution: "resolution", + }); + }); + +export const Schemas$CreateRequest = { + in: SchemaIn$CreateRequest, + out: SchemaOut$CreateRequest, +}; diff --git a/src/resources/v1/body-swap/resource-client.ts b/src/resources/v1/body-swap/resource-client.ts new file mode 100644 index 0000000..dcfed20 --- /dev/null +++ b/src/resources/v1/body-swap/resource-client.ts @@ -0,0 +1,161 @@ +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 { FilesClient } from "magic-hour/resources/v1/files"; +import { ImageProjectsClient } from "magic-hour/resources/v1/image-projects"; +import * as requests from "magic-hour/resources/v1/body-swap/request-types"; +import * as types from "magic-hour/types"; +import { Schemas$V1BodySwapCreateBody } from "magic-hour/types/v1-body-swap-create-body"; +import { Schemas$V1BodySwapCreateResponse } from "magic-hour/types/v1-body-swap-create-response"; + +type GenerateRequest = GenerateRequestType< + requests.CreateRequest, + { + /** + * Image of the person to place into the scene. 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. + */ + personFilePath: string; + /** + * Target scene image (background). 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. + */ + sceneFilePath: string; + } +>; + +export class BodySwapClient extends CoreResourceClient { + constructor(coreClient: CoreClient, opts: ResourceClientOptions) { + super(coreClient, opts); + } + + /** + * Body Swap + * + * Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward). + * + * 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.bodySwap.generate( + * { + * assets: { + * personFilePath: "/path/to/person.png", + * sceneFilePath: "/path/to/scene.png", + * }, + * name: "My Body Swap image", + * resolution: "1k", + * }, + * { + * 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 { personFilePath, sceneFilePath, ...restAssets } = request.assets; + + getLogger().debug( + `Uploading file ${personFilePath} to Magic Hour's storage`, + ); + getLogger().debug( + `Uploading file ${sceneFilePath} to Magic Hour's storage`, + ); + + const [uploadedPersonFilePath, uploadedSceneFilePath] = await Promise.all([ + fileClient.uploadFile(personFilePath), + fileClient.uploadFile(sceneFilePath), + ]); + + getLogger().info( + `Uploaded file ${personFilePath} to Magic Hour's storage as ${uploadedPersonFilePath}`, + ); + getLogger().info( + `Uploaded file ${sceneFilePath} to Magic Hour's storage as ${uploadedSceneFilePath}`, + ); + + const createResponse = await this.create( + { + ...request, + assets: { + ...restAssets, + personFilePath: uploadedPersonFilePath, + sceneFilePath: uploadedSceneFilePath, + }, + }, + createOpts, + ); + + getLogger().info(`Created BodySwapClient project ${createResponse.id}`); + + const projectsClient = new ImageProjectsClient(this._client, this._opts); + + getLogger().debug( + `Checking result for BodySwapClient project ${createResponse.id}`, + ); + + const result = await projectsClient.checkResult( + { id: createResponse.id }, + { + waitForCompletion, + downloadOutputs, + downloadDirectory, + ...createOpts, + }, + ); + + return result; + } + + /** + * Body Swap + * + * Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward). + * + * POST /v1/body-swap + */ + create( + request: requests.CreateRequest, + opts?: RequestOptions, + ): ApiPromise { + return this._client.makeRequest({ + method: "post", + path: "/v1/body-swap", + auth: ["bearerAuth"], + contentType: "application/json", + body: Schemas$V1BodySwapCreateBody.out.parse(request), + responseSchema: Schemas$V1BodySwapCreateResponse.in, + opts, + }); + } +} diff --git a/src/resources/v1/image-to-video/README.md b/src/resources/v1/image-to-video/README.md index fa5c6d6..f4b4922 100644 --- a/src/resources/v1/image-to-video/README.md +++ b/src/resources/v1/image-to-video/README.md @@ -85,7 +85,7 @@ For detailed examples, see the [product page](https://magichour.ai/products/imag | `└─ endImageFilePath` | ✗ | — | The image to use as the last frame of the video. * **`ltx-2`**: Not supported * **`wan-2.2`**: Not supported * **`seedance`**: Supports 480p, 720p, 1080p. * **`seedance-2.0`**: Supports 480p, 720p. * **`kling-2.5`**: Supports 1080p. * **`kling-3.0`**: Supports 1080p. * **`sora-2`**: Not supported * **`veo3.1`**: Not supported * **`veo3.1-lite`**: Not supported Legacy models: * **`kling-1.6`**: Not supported | `"api-assets/id/1234.png"` | | `└─ imageFilePath` | ✓ | — | The path of the image file. 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.png"` | | `endSeconds` | ✓ | ✗ | The total duration of the output video in seconds. Supported durations depend on the chosen model: * **`ltx-2`**: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30 * **`wan-2.2`**: 3, 4, 5, 6, 7, 8, 9, 10, 15 * **`seedance`**: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 * **`seedance-2.0`**: 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 * **`kling-2.5`**: 5, 10 * **`kling-3.0`**: 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 * **`sora-2`**: 4, 8, 12, 24, 36, 48, 60 * **`veo3.1`**: 4, 6, 8, 16, 24, 32, 40, 48, 56 * **`veo3.1-lite`**: 8, 16, 24, 32, 40, 48, 56 Legacy models: * **`kling-1.6`**: 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 | `5.0` | -| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Not supported * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` | +| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Automatically included with no extra credits * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` | | `height` | ✗ | ✓ | `height` is deprecated and no longer influences the output video's resolution. This field is retained only for backward compatibility and will be removed in a future release. | `123` | | `model` | ✗ | ✗ | The AI model to use for video generation. * `default`: uses our currently recommended model for general use. For paid tiers, defaults to `kling-3.0`. For free tiers, it defaults to `ltx-2`. * `ltx-2`: Fast iteration with audio and lip-sync * `wan-2.2`: Fast, strong visuals with effects * `seedance`: Fast iteration and start/end frames * `seedance-2.0`: State-of-the-art quality and consistency * `kling-2.5`: Motion, action, and camera control * `kling-3.0`: Cinematic, multi-scene storytelling * `sora-2`: Story-first concepts and creativity * `veo3.1`: Realistic visuals and prompt adherence * `veo3.1-lite`: Good for fast, affordable, high-quality daily generation. Legacy models: * `kling-1.6`: Reliable baseline with smooth motion If you specify the deprecated model value that includes the `-audio` suffix, this will be the same as included `audio` as `true`. | `"kling-3.0"` | | `name` | ✗ | ✗ | Give your video a custom name for easy identification. | `"My Image To Video video"` | diff --git a/src/resources/v1/image-to-video/request-types.ts b/src/resources/v1/image-to-video/request-types.ts index b4a2fad..492e2ae 100644 --- a/src/resources/v1/image-to-video/request-types.ts +++ b/src/resources/v1/image-to-video/request-types.ts @@ -27,7 +27,7 @@ export type CreateRequest = { * * **`ltx-2`**: Automatically included with no extra credits * * **`wan-2.2`**: Not supported * * **`seedance`**: Not supported - * * **`seedance-2.0`**: Not supported + * * **`seedance-2.0`**: Automatically included with no extra credits * * **`kling-2.5`**: Automatically included with no extra credits * * **`kling-3.0`**: Toggle-able (can enable/disable) * * **`sora-2`**: Automatically included with no extra credits diff --git a/src/resources/v1/index.ts b/src/resources/v1/index.ts index a273ee2..39ad6dd 100644 --- a/src/resources/v1/index.ts +++ b/src/resources/v1/index.ts @@ -13,6 +13,7 @@ export * as aiVoiceGenerator from "./ai-voice-generator"; export * as animation from "./animation"; export * as audioProjects from "./audio-projects"; export * as autoSubtitleGenerator from "./auto-subtitle-generator"; +export * as bodySwap from "./body-swap"; 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 eb047b6..7403bc2 100644 --- a/src/resources/v1/resource-client.ts +++ b/src/resources/v1/resource-client.ts @@ -19,6 +19,7 @@ import { AiVoiceGeneratorClient } from "magic-hour/resources/v1/ai-voice-generat import { AnimationClient } from "magic-hour/resources/v1/animation"; import { AudioProjectsClient } from "magic-hour/resources/v1/audio-projects"; import { AutoSubtitleGeneratorClient } from "magic-hour/resources/v1/auto-subtitle-generator"; +import { BodySwapClient } from "magic-hour/resources/v1/body-swap"; 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"; @@ -34,6 +35,7 @@ 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 _bodySwapLazy?: BodySwapClient; // lazy-loading cache private _headSwapLazy?: HeadSwapClient; // lazy-loading cache private _aiVoiceClonerLazy?: AiVoiceClonerClient; // lazy-loading cache private _audioProjectsLazy?: AudioProjectsClient; // lazy-loading cache @@ -82,6 +84,7 @@ export class V1Client extends CoreResourceClient { this.animation; this.audioProjects; this.autoSubtitleGenerator; + this.bodySwap; this.faceDetection; this.faceSwap; this.faceSwapPhoto; @@ -400,4 +403,14 @@ export class V1Client extends CoreResourceClient { )) ); } + + get bodySwap(): BodySwapClient { + return ( + this._bodySwapLazy ?? + (this._bodySwapLazy = new (require("./body-swap").BodySwapClient)( + this._client, + this._opts, + )) + ); + } } diff --git a/src/resources/v1/text-to-video/README.md b/src/resources/v1/text-to-video/README.md index 7e41865..202b2b0 100644 --- a/src/resources/v1/text-to-video/README.md +++ b/src/resources/v1/text-to-video/README.md @@ -87,7 +87,7 @@ For detailed examples, see the [product page](https://magichour.ai/products/text | `└─ prompt` | ✓ | — | The prompt used for the video. | `"a dog running"` | | `└─ qualityMode` | ✗ | ✓ | DEPRECATED: Please use `resolution` field instead. For backward compatibility: * `quick` maps to 720p resolution * `studio` maps to 1080p resolution This field will be removed in a future version. Use the `resolution` field to directly to specify the resolution. | `"quick"` | | `aspectRatio` | ✗ | ✗ | Determines the aspect ratio of the output video. * **`ltx-2`**: Supports 9:16, 16:9, 1:1. * **`wan-2.2`**: Supports 9:16, 16:9, 1:1. * **`seedance`**: Supports 9:16, 16:9, 1:1. * **`seedance-2.0`**: Supports 9:16, 16:9, 1:1. * **`kling-2.5`**: Supports 9:16, 16:9, 1:1. * **`kling-3.0`**: Supports 9:16, 16:9, 1:1. * **`sora-2`**: Supports 9:16, 16:9. * **`veo3.1`**: Supports 9:16, 16:9. * **`veo3.1-lite`**: Supports 9:16, 16:9. Legacy models: * **`kling-1.6`**: Supports 9:16, 16:9, 1:1. | `"16:9"` | -| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Not supported * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` | +| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Automatically included with no extra credits * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` | | `model` | ✗ | ✗ | The AI model to use for video generation. * `default`: uses our currently recommended model for general use. For paid tiers, defaults to `kling-3.0`. For free tiers, it defaults to `ltx-2`. * `ltx-2`: Fast iteration with audio and lip-sync * `wan-2.2`: Fast, strong visuals with effects * `seedance`: Fast iteration and start/end frames * `seedance-2.0`: State-of-the-art quality and consistency * `kling-2.5`: Motion, action, and camera control * `kling-3.0`: Cinematic, multi-scene storytelling * `sora-2`: Story-first concepts and creativity * `veo3.1`: Realistic visuals and prompt adherence * `veo3.1-lite`: Good for fast, affordable, high-quality daily generation. Legacy models: * `kling-1.6`: Reliable baseline with smooth motion If you specify the deprecated model value that includes the `-audio` suffix, this will be the same as included `audio` as `true`. | `"kling-3.0"` | | `name` | ✗ | ✗ | Give your video a custom name for easy identification. | `"My Text To Video video"` | | `orientation` | ✗ | ✓ | Deprecated. Use `aspect_ratio` instead. | `"landscape"` | diff --git a/src/resources/v1/text-to-video/request-types.ts b/src/resources/v1/text-to-video/request-types.ts index 39c09a6..c79ca9f 100644 --- a/src/resources/v1/text-to-video/request-types.ts +++ b/src/resources/v1/text-to-video/request-types.ts @@ -35,7 +35,7 @@ export type CreateRequest = { * * **`ltx-2`**: Automatically included with no extra credits * * **`wan-2.2`**: Not supported * * **`seedance`**: Not supported - * * **`seedance-2.0`**: Not supported + * * **`seedance-2.0`**: Automatically included with no extra credits * * **`kling-2.5`**: Automatically included with no extra credits * * **`kling-3.0`**: Toggle-able (can enable/disable) * * **`sora-2`**: Automatically included with no extra credits diff --git a/src/types/index.ts b/src/types/index.ts index c3b1b99..cee8407 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -74,6 +74,10 @@ export { V1AutoSubtitleGeneratorCreateBodyStyle } from "./v1-auto-subtitle-gener export { V1AutoSubtitleGeneratorCreateBodyStyleCustomConfig } from "./v1-auto-subtitle-generator-create-body-style-custom-config"; export { V1AutoSubtitleGeneratorCreateBodyStyleTemplateEnum } from "./v1-auto-subtitle-generator-create-body-style-template-enum"; export { V1AutoSubtitleGeneratorCreateResponse } from "./v1-auto-subtitle-generator-create-response"; +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 { 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-image-generator-create-body-model-enum.ts b/src/types/v1-ai-image-generator-create-body-model-enum.ts index 27b071c..c828f9b 100644 --- a/src/types/v1-ai-image-generator-create-body-model-enum.ts +++ b/src/types/v1-ai-image-generator-create-body-model-enum.ts @@ -22,7 +22,7 @@ * - `nano-banana-2` - from 100 credits/image * - Supported resolutions: 640px, 1k, 2k, 4k * - Available for tiers: free, creator, pro, business - * - Image count allowed: 1, 2, 3, 4 + * - Image count allowed: 1, 4, 9, 16 * - `nano-banana-pro` - from 150 credits/image * - Supported resolutions: 1k, 2k, 4k * - Available for tiers: creator, pro, business diff --git a/src/types/v1-ai-image-generator-create-body.ts b/src/types/v1-ai-image-generator-create-body.ts index f6ad3ae..80c648a 100644 --- a/src/types/v1-ai-image-generator-create-body.ts +++ b/src/types/v1-ai-image-generator-create-body.ts @@ -43,7 +43,7 @@ export type V1AiImageGeneratorCreateBody = { * - `nano-banana-2` - from 100 credits/image * - Supported resolutions: 640px, 1k, 2k, 4k * - Available for tiers: free, creator, pro, business - * - Image count allowed: 1, 2, 3, 4 + * - Image count allowed: 1, 4, 9, 16 * - `nano-banana-pro` - from 150 credits/image * - Supported resolutions: 1k, 2k, 4k * - Available for tiers: creator, pro, business diff --git a/src/types/v1-body-swap-create-body-assets.ts b/src/types/v1-body-swap-create-body-assets.ts new file mode 100644 index 0000000..436814c --- /dev/null +++ b/src/types/v1-body-swap-create-body-assets.ts @@ -0,0 +1,80 @@ +import { zodTransform } from "make-api-request-js"; +import * as z from "zod"; + +/** + * Person image and scene image for body swap + */ +export type V1BodySwapCreateBodyAssets = { + /** + * Image of the person to place into the scene. 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. + * + */ + personFilePath: string; + /** + * Target scene image (background). 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. + * + */ + sceneFilePath: string; +}; + +/** + * @internal + * V1BodySwapCreateBodyAssets without any key transformation, this is what + * we expect to come in as network data + */ +export type External$V1BodySwapCreateBodyAssets = { + person_file_path: string; + scene_file_path: string; +}; + +/** + * Takes network data, validates it, and transforms keys to match typescript object V1BodySwapCreateBodyAssets + */ +const SchemaIn$V1BodySwapCreateBodyAssets: z.ZodType< + V1BodySwapCreateBodyAssets, // output type of this zod object + z.ZodTypeDef, + unknown +> = z + .object({ + person_file_path: z.string(), + scene_file_path: z.string(), + }) + .transform((obj) => { + return zodTransform(obj, { + person_file_path: "personFilePath", + scene_file_path: "sceneFilePath", + }); + }); + +/** + * @internal + * Takes typescript data, validates it, and maps keys to match the expected external object External$V1BodySwapCreateBodyAssets + */ +const SchemaOut$V1BodySwapCreateBodyAssets: z.ZodType< + External$V1BodySwapCreateBodyAssets, // output type of this zod object + z.ZodTypeDef, + V1BodySwapCreateBodyAssets // the object to be transformed +> = z + .object({ + personFilePath: z.string(), + sceneFilePath: z.string(), + }) + .transform((obj) => { + return zodTransform(obj, { + personFilePath: "person_file_path", + sceneFilePath: "scene_file_path", + }); + }); + +export const Schemas$V1BodySwapCreateBodyAssets = { + in: SchemaIn$V1BodySwapCreateBodyAssets, + out: SchemaOut$V1BodySwapCreateBodyAssets, +}; diff --git a/src/types/v1-body-swap-create-body-resolution-enum.ts b/src/types/v1-body-swap-create-body-resolution-enum.ts new file mode 100644 index 0000000..aff29d3 --- /dev/null +++ b/src/types/v1-body-swap-create-body-resolution-enum.ts @@ -0,0 +1,4 @@ +/** + * Output resolution. Determines credits charged for the run. + */ +export type V1BodySwapCreateBodyResolutionEnum = "1k" | "2k" | "4k" | "640px"; diff --git a/src/types/v1-body-swap-create-body.ts b/src/types/v1-body-swap-create-body.ts new file mode 100644 index 0000000..143e0a2 --- /dev/null +++ b/src/types/v1-body-swap-create-body.ts @@ -0,0 +1,85 @@ +import { zodTransform } from "make-api-request-js"; +import * as z from "zod"; + +import { + External$V1BodySwapCreateBodyAssets, + Schemas$V1BodySwapCreateBodyAssets, + V1BodySwapCreateBodyAssets, +} from "./v1-body-swap-create-body-assets"; + +/** + * V1BodySwapCreateBody + */ +export type V1BodySwapCreateBody = { + /** + * Person image and scene image for body swap + */ + assets: V1BodySwapCreateBodyAssets; + /** + * Give your image a custom name for easy identification. + */ + name?: string | undefined; + /** + * Output resolution. Determines credits charged for the run. + */ + resolution: "1k" | "2k" | "4k" | "640px"; +}; + +/** + * @internal + * V1BodySwapCreateBody without any key transformation, this is what + * we expect to come in as network data + */ +export type External$V1BodySwapCreateBody = { + assets: External$V1BodySwapCreateBodyAssets; + name?: string | undefined; + resolution: "1k" | "2k" | "4k" | "640px"; +}; + +/** + * Takes network data, validates it, and transforms keys to match typescript object V1BodySwapCreateBody + */ +const SchemaIn$V1BodySwapCreateBody: z.ZodType< + V1BodySwapCreateBody, // output type of this zod object + z.ZodTypeDef, + unknown +> = z + .object({ + assets: Schemas$V1BodySwapCreateBodyAssets.in, + name: z.string().optional(), + resolution: z.enum(["1k", "2k", "4k", "640px"]), + }) + .transform((obj) => { + return zodTransform(obj, { + assets: "assets", + name: "name", + resolution: "resolution", + }); + }); + +/** + * @internal + * Takes typescript data, validates it, and maps keys to match the expected external object External$V1BodySwapCreateBody + */ +const SchemaOut$V1BodySwapCreateBody: z.ZodType< + External$V1BodySwapCreateBody, // output type of this zod object + z.ZodTypeDef, + V1BodySwapCreateBody // the object to be transformed +> = z + .object({ + assets: Schemas$V1BodySwapCreateBodyAssets.out, + name: z.string().optional(), + resolution: z.enum(["1k", "2k", "4k", "640px"]), + }) + .transform((obj) => { + return zodTransform(obj, { + assets: "assets", + name: "name", + resolution: "resolution", + }); + }); + +export const Schemas$V1BodySwapCreateBody = { + in: SchemaIn$V1BodySwapCreateBody, + out: SchemaOut$V1BodySwapCreateBody, +}; diff --git a/src/types/v1-body-swap-create-response.ts b/src/types/v1-body-swap-create-response.ts new file mode 100644 index 0000000..5d8d852 --- /dev/null +++ b/src/types/v1-body-swap-create-response.ts @@ -0,0 +1,81 @@ +import { zodTransform } from "make-api-request-js"; +import * as z from "zod"; + +/** + * Success + */ +export type V1BodySwapCreateResponse = { + /** + * The amount of credits deducted from your account to generate the image. We charge credits right when the request is made. + * + * If an error occurred while generating the image(s), 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 image generation. Use 'credits_charged' instead. + */ + frameCost: number; + /** + * Unique ID of the image. Use it with the [Get image Project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch status and downloads. + */ + id: string; +}; + +/** + * @internal + * V1BodySwapCreateResponse without any key transformation, this is what + * we expect to come in as network data + */ +export type External$V1BodySwapCreateResponse = { + credits_charged: number; + frame_cost: number; + id: string; +}; + +/** + * Takes network data, validates it, and transforms keys to match typescript object V1BodySwapCreateResponse + */ +const SchemaIn$V1BodySwapCreateResponse: z.ZodType< + V1BodySwapCreateResponse, // output type of this zod object + z.ZodTypeDef, + unknown +> = z + .object({ + credits_charged: z.number().int(), + frame_cost: z.number().int(), + id: z.string(), + }) + .transform((obj) => { + return zodTransform(obj, { + credits_charged: "creditsCharged", + frame_cost: "frameCost", + id: "id", + }); + }); + +/** + * @internal + * Takes typescript data, validates it, and maps keys to match the expected external object External$V1BodySwapCreateResponse + */ +const SchemaOut$V1BodySwapCreateResponse: z.ZodType< + External$V1BodySwapCreateResponse, // output type of this zod object + z.ZodTypeDef, + V1BodySwapCreateResponse // the object to be transformed +> = z + .object({ + creditsCharged: z.number().int(), + frameCost: z.number().int(), + id: z.string(), + }) + .transform((obj) => { + return zodTransform(obj, { + creditsCharged: "credits_charged", + frameCost: "frame_cost", + id: "id", + }); + }); + +export const Schemas$V1BodySwapCreateResponse = { + in: SchemaIn$V1BodySwapCreateResponse, + out: SchemaOut$V1BodySwapCreateResponse, +}; diff --git a/src/types/v1-image-to-video-create-body.ts b/src/types/v1-image-to-video-create-body.ts index 8a501f1..a3a3a7a 100644 --- a/src/types/v1-image-to-video-create-body.ts +++ b/src/types/v1-image-to-video-create-body.ts @@ -27,7 +27,7 @@ export type V1ImageToVideoCreateBody = { * * **`ltx-2`**: Automatically included with no extra credits * * **`wan-2.2`**: Not supported * * **`seedance`**: Not supported - * * **`seedance-2.0`**: Not supported + * * **`seedance-2.0`**: Automatically included with no extra credits * * **`kling-2.5`**: Automatically included with no extra credits * * **`kling-3.0`**: Toggle-able (can enable/disable) * * **`sora-2`**: Automatically included with no extra credits diff --git a/src/types/v1-text-to-video-create-body.ts b/src/types/v1-text-to-video-create-body.ts index f33f5a2..54e692f 100644 --- a/src/types/v1-text-to-video-create-body.ts +++ b/src/types/v1-text-to-video-create-body.ts @@ -35,7 +35,7 @@ export type V1TextToVideoCreateBody = { * * **`ltx-2`**: Automatically included with no extra credits * * **`wan-2.2`**: Not supported * * **`seedance`**: Not supported - * * **`seedance-2.0`**: Not supported + * * **`seedance-2.0`**: Automatically included with no extra credits * * **`kling-2.5`**: Automatically included with no extra credits * * **`kling-3.0`**: Toggle-able (can enable/disable) * * **`sora-2`**: Automatically included with no extra credits diff --git a/test/v1-body-swap.test.ts b/test/v1-body-swap.test.ts new file mode 100644 index 0000000..9938cbd --- /dev/null +++ b/test/v1-body-swap.test.ts @@ -0,0 +1,37 @@ +import { Client, Environment } from "magic-hour"; + +describe("tests client.v1.bodySwap.create", () => { + test.concurrent( + "POST /v1/body-swap | 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.bodySwap + .create({ + assets: { + personFilePath: "api-assets/id/1234.png", + sceneFilePath: "api-assets/id/5678.png", + }, + name: "My Body Swap image", + resolution: "1k", + }) + .asResponse(), + client.v1.bodySwap.create({ + assets: { + personFilePath: "api-assets/id/1234.png", + sceneFilePath: "api-assets/id/5678.png", + }, + name: "My Body Swap image", + resolution: "1k", + }), + ]); + expect(rawResponse.status).toBe(200); // Exact status code match + // Response body automatically validated by Zod schema during deserialization + expect(response).toBeDefined(); + }, + ); +});