movoa-ai 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +82 -2
- package/dist/mod.d.ts +122 -0
- package/dist/mod.js +193 -0
- package/package.json +21 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Movoa (https://movoa.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,83 @@
|
|
|
1
|
-
#
|
|
1
|
+
# movoa
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Unlimited AI video generator with sound: on paid plans the tenth take of a shot costs what the first did, and your first clip is free to make right now. This is the official TypeScript / JavaScript SDK for [Movoa](https://movoa.ai/?utm_source=npm&utm_medium=package), for Node 18+, Deno and Bun: one API key turns a prompt into [text to video](https://movoa.ai/video/text-to-video), or a still into [photo to video](https://movoa.ai/video/photo-to-video), and every clip comes back as [AI video with audio](https://movoa.ai/video/ai-video-with-audio).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install movoa-ai
|
|
7
|
+
# Deno / JSR
|
|
8
|
+
deno add jsr:@movoa/movoa
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { generateVideo } from "movoa-ai";
|
|
13
|
+
|
|
14
|
+
const clip = await generateVideo("a paper boat drifting down a rain gutter, low angle, slow push-in");
|
|
15
|
+
console.log(clip); // URL of the finished MP4, sound included
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Uses the global `fetch`, no dependencies. `generateVideo` submits the run, waits for it and resolves to the URL of the finished file.
|
|
19
|
+
|
|
20
|
+
## API key
|
|
21
|
+
|
|
22
|
+
Sign in on [movoa.ai](https://movoa.ai/?utm_source=npm&utm_medium=package), create a key at [movoa.ai/settings/apikeys](https://movoa.ai/settings/apikeys), then pass it as `apiKey` or set it in the environment:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
export MOVOA_API_KEY=sk-...
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Runs cost credits on your Movoa account, at the prices on [movoa.ai/pricing](https://movoa.ai/pricing).
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { credits } from "movoa-ai";
|
|
32
|
+
console.log(await credits()); // remaining credits on the account
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Text to video and photo to video
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// text to video, vertical, 10 seconds
|
|
39
|
+
await generateVideo("a fox trotting through fresh snow at dawn, tracking shot", { duration: 10, aspectRatio: "9:16" });
|
|
40
|
+
|
|
41
|
+
// photo to video: the photo becomes the first frame
|
|
42
|
+
await generateVideo("the camera slowly orbits the statue", { imageUrl: "https://example.com/statue.jpg" });
|
|
43
|
+
|
|
44
|
+
// another model
|
|
45
|
+
await generateVideo("a street drummer in the rain, close-up", { model: "seedance-2-5", duration: 10, resolution: "720p" });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| model | options |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `minimax-h3` (default) | `duration` 4 / 5 / 6 / 8 / 10 / 15 · `resolution` 480p / 768p · `aspectRatio` 16:9 / 9:16 / 1:1 |
|
|
51
|
+
| `veo-3-1-fast` | `duration` 8 · `resolution` 720p · `aspectRatio` 16:9 / 9:16 (photo to video: 16:9) |
|
|
52
|
+
| `seedance-2-5` | `duration` 5 / 10 / 15 / 20 / 25 / 30 · `resolution` 480p / 720p · `aspectRatio` 16:9 / 9:16 / 1:1 |
|
|
53
|
+
| `seedance-2`, `seedance-2-fast`, `seedance-2-mini` | `duration` 4 / 6 / 8 / 10 · `resolution` 480p / 720p · `aspectRatio` |
|
|
54
|
+
|
|
55
|
+
Anything else goes in `options: { … }` and is sent as it is. Every run asks for sound. On the site: [Seedance 2.5](https://movoa.ai/video/seedance-2-5) and [Veo 3](https://movoa.ai/video/google-veo-3).
|
|
56
|
+
|
|
57
|
+
## Submit now, collect later
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { submit, status, wait } from "movoa-ai";
|
|
61
|
+
|
|
62
|
+
const id = await submit("a lighthouse in a storm, waves crashing", { duration: 6 });
|
|
63
|
+
console.log((await status(id)).status); // pending | processing | success | failed | canceled
|
|
64
|
+
const url = await wait(id); // polls every 10 seconds, 30 minutes by default
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
While a run waits in line, `status()` returns `queued: true` and `queueEta` in seconds.
|
|
68
|
+
|
|
69
|
+
## Errors worth catching
|
|
70
|
+
|
|
71
|
+
Every failure is a `MovoaError` with a `kind`, and a subclass you can test with `instanceof`:
|
|
72
|
+
|
|
73
|
+
- `AuthError` (`auth`): no key, or the key is wrong or deleted; create one at [movoa.ai/settings/apikeys](https://movoa.ai/settings/apikeys)
|
|
74
|
+
- `QuotaError` (`quota`): the account does not have enough credits for this run
|
|
75
|
+
- `PlanError` (`plan`): that model or quality needs a plan on the account
|
|
76
|
+
- `BusyError` (`busy`): too many runs on this account are generating at once
|
|
77
|
+
- `RejectedError` (`rejected`): the content filter refused the prompt, or a cooldown is active; reword it
|
|
78
|
+
- `GenerationError` (`failed`): the run was accepted but failed or was canceled; `.errorMessage` says why
|
|
79
|
+
- `TimeoutError` (`timeout`): the wait ran out; `.taskId` lets you check the run later with `status()`
|
|
80
|
+
|
|
81
|
+
Any other refusal is a plain `MovoaError` (`other`) with the server's message.
|
|
82
|
+
|
|
83
|
+
MIT licensed. Made by [Movoa](https://movoa.ai/?utm_source=npm&utm_medium=package).
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** The service this module talks to. */
|
|
2
|
+
export declare const BASE_URL = "https://movoa.ai";
|
|
3
|
+
/** Where users create API keys. */
|
|
4
|
+
export declare const KEYS_URL: string;
|
|
5
|
+
/** Model ids. `minimax-h3` is the default and the cheapest. */
|
|
6
|
+
export declare const MODELS: readonly ["minimax-h3", "veo-3-1-fast", "seedance-2-5", "seedance-2", "seedance-2-fast", "seedance-2-mini"];
|
|
7
|
+
export declare const DEFAULT_MODEL = "minimax-h3";
|
|
8
|
+
/**
|
|
9
|
+
* What went wrong, so callers can decide what to do next.
|
|
10
|
+
* auth: no or bad key · quota: not enough credits · plan: model or quality needs a plan ·
|
|
11
|
+
* busy: too many runs at once · rejected: content filter or cooldown, reword ·
|
|
12
|
+
* failed: the run was accepted but failed or was canceled · timeout: still running, check later ·
|
|
13
|
+
* other: any other refusal, network failure or unexpected answer
|
|
14
|
+
*/
|
|
15
|
+
export type ErrorKind = "auth" | "quota" | "plan" | "busy" | "rejected" | "failed" | "timeout" | "other";
|
|
16
|
+
/** Every failure is a MovoaError; branch on `kind` or on the subclass. */
|
|
17
|
+
export declare class MovoaError extends Error {
|
|
18
|
+
readonly kind: ErrorKind;
|
|
19
|
+
/** HTTP status, when there was a response. */
|
|
20
|
+
status?: number;
|
|
21
|
+
/** Envelope code (0, -1, -2, -3). */
|
|
22
|
+
code?: number;
|
|
23
|
+
/** `data.reason` the service sent, if any. */
|
|
24
|
+
reason?: string;
|
|
25
|
+
/** The run this is about (failed and timeout). */
|
|
26
|
+
taskId?: string;
|
|
27
|
+
constructor(message: string, kind?: ErrorKind, extra?: {
|
|
28
|
+
status?: number;
|
|
29
|
+
code?: number;
|
|
30
|
+
reason?: string;
|
|
31
|
+
taskId?: string;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** No API key, or the key is invalid or deleted. */
|
|
35
|
+
export declare class AuthError extends MovoaError {
|
|
36
|
+
constructor(m: string, e?: {});
|
|
37
|
+
}
|
|
38
|
+
/** The account does not have enough credits for this run. */
|
|
39
|
+
export declare class QuotaError extends MovoaError {
|
|
40
|
+
constructor(m: string, e?: {});
|
|
41
|
+
}
|
|
42
|
+
/** This model or quality needs a plan, or the account has never bought credits. */
|
|
43
|
+
export declare class PlanError extends MovoaError {
|
|
44
|
+
constructor(m: string, e?: {});
|
|
45
|
+
}
|
|
46
|
+
/** Too many runs on this account are generating at once. */
|
|
47
|
+
export declare class BusyError extends MovoaError {
|
|
48
|
+
constructor(m: string, e?: {});
|
|
49
|
+
}
|
|
50
|
+
/** The content filter refused the prompt (code -2) or a cooldown is active (code -3). Reword it. */
|
|
51
|
+
export declare class RejectedError extends MovoaError {
|
|
52
|
+
constructor(m: string, e?: {});
|
|
53
|
+
}
|
|
54
|
+
/** The run was accepted but ended as failed or canceled. */
|
|
55
|
+
export declare class GenerationError extends MovoaError {
|
|
56
|
+
readonly errorMessage?: string;
|
|
57
|
+
readonly failureKind?: string;
|
|
58
|
+
constructor(m: string, errorMessage?: string, failureKind?: string, taskId?: string);
|
|
59
|
+
}
|
|
60
|
+
/** The wait ran out; the run may still finish. `taskId` lets you check it later. */
|
|
61
|
+
export declare class TimeoutError extends MovoaError {
|
|
62
|
+
constructor(m: string, taskId: string);
|
|
63
|
+
}
|
|
64
|
+
/** Connection options shared by every call. */
|
|
65
|
+
export interface ClientOptions {
|
|
66
|
+
/** Defaults to the `MOVOA_API_KEY` environment variable. */
|
|
67
|
+
apiKey?: string;
|
|
68
|
+
/** Defaults to `MOVOA_BASE_URL`, then https://movoa.ai. */
|
|
69
|
+
baseUrl?: string;
|
|
70
|
+
fetch?: typeof fetch;
|
|
71
|
+
}
|
|
72
|
+
/** Options for calls that wait for the result. */
|
|
73
|
+
export interface WaitOptions extends ClientOptions {
|
|
74
|
+
/** Milliseconds. Default 30 minutes. */
|
|
75
|
+
timeout?: number;
|
|
76
|
+
/** Milliseconds between polls. Default 10 seconds. */
|
|
77
|
+
pollEvery?: number;
|
|
78
|
+
}
|
|
79
|
+
/** What to generate. Anything not listed is sent in `options` as it is. */
|
|
80
|
+
export interface VideoOptions {
|
|
81
|
+
model?: string;
|
|
82
|
+
/** A public https image; turns the run into photo to video with it as the first frame. */
|
|
83
|
+
imageUrl?: string;
|
|
84
|
+
duration?: number;
|
|
85
|
+
resolution?: string;
|
|
86
|
+
aspectRatio?: string;
|
|
87
|
+
/** Extra model options, sent as they are. */
|
|
88
|
+
options?: Record<string, unknown>;
|
|
89
|
+
}
|
|
90
|
+
/** The run record returned by `status`. */
|
|
91
|
+
export interface Task {
|
|
92
|
+
id: string;
|
|
93
|
+
status: "pending" | "processing" | "success" | "failed" | "canceled" | string;
|
|
94
|
+
videos?: string[];
|
|
95
|
+
errorMessage?: string;
|
|
96
|
+
failureKind?: string;
|
|
97
|
+
queued?: boolean;
|
|
98
|
+
/** Seconds. */
|
|
99
|
+
queueEta?: number;
|
|
100
|
+
[k: string]: unknown;
|
|
101
|
+
}
|
|
102
|
+
type Envelope = {
|
|
103
|
+
code?: number;
|
|
104
|
+
message?: string;
|
|
105
|
+
data?: any;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Map a refusal to the error that says what to do next. movoa.ai answers almost
|
|
109
|
+
* every refusal as HTTP 200 with a non-zero `code` and a `data.reason`.
|
|
110
|
+
*/
|
|
111
|
+
export declare function errorFor(status: number, body: Envelope): MovoaError;
|
|
112
|
+
/** Start one run and resolve to its task id without waiting. */
|
|
113
|
+
export declare function submit(prompt: string, v?: VideoOptions, o?: ClientOptions): Promise<string>;
|
|
114
|
+
/** Fetch the run record: `status` is pending, processing, success, failed or canceled. */
|
|
115
|
+
export declare function status(taskId: string, o?: ClientOptions): Promise<Task>;
|
|
116
|
+
/** Poll a run until it finishes and resolve to the video URL. */
|
|
117
|
+
export declare function wait(taskId: string, o?: WaitOptions): Promise<string>;
|
|
118
|
+
/** Generate one clip with sound and resolve to its URL. */
|
|
119
|
+
export declare function generateVideo(prompt: string, v?: VideoOptions & WaitOptions): Promise<string>;
|
|
120
|
+
/** Resolve to the remaining credits on the account. */
|
|
121
|
+
export declare function credits(o?: ClientOptions): Promise<number>;
|
|
122
|
+
export {};
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Official SDK for Movoa (movoa.ai), the AI video generator with sound. See README.md.
|
|
2
|
+
/** The service this module talks to. */
|
|
3
|
+
export const BASE_URL = "https://movoa.ai";
|
|
4
|
+
/** Where users create API keys. */
|
|
5
|
+
export const KEYS_URL = `${BASE_URL}/settings/apikeys`;
|
|
6
|
+
/** Model ids. `minimax-h3` is the default and the cheapest. */
|
|
7
|
+
export const MODELS = [
|
|
8
|
+
"minimax-h3", "veo-3-1-fast", "seedance-2-5", "seedance-2", "seedance-2-fast", "seedance-2-mini",
|
|
9
|
+
];
|
|
10
|
+
export const DEFAULT_MODEL = "minimax-h3";
|
|
11
|
+
/** Every failure is a MovoaError; branch on `kind` or on the subclass. */
|
|
12
|
+
export class MovoaError extends Error {
|
|
13
|
+
kind;
|
|
14
|
+
/** HTTP status, when there was a response. */
|
|
15
|
+
status;
|
|
16
|
+
/** Envelope code (0, -1, -2, -3). */
|
|
17
|
+
code;
|
|
18
|
+
/** `data.reason` the service sent, if any. */
|
|
19
|
+
reason;
|
|
20
|
+
/** The run this is about (failed and timeout). */
|
|
21
|
+
taskId;
|
|
22
|
+
constructor(message, kind = "other", extra = {}) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.kind = kind;
|
|
25
|
+
this.name = "MovoaError";
|
|
26
|
+
Object.assign(this, extra);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** No API key, or the key is invalid or deleted. */
|
|
30
|
+
export class AuthError extends MovoaError {
|
|
31
|
+
constructor(m, e = {}) { super(m, "auth", e); this.name = "AuthError"; }
|
|
32
|
+
}
|
|
33
|
+
/** The account does not have enough credits for this run. */
|
|
34
|
+
export class QuotaError extends MovoaError {
|
|
35
|
+
constructor(m, e = {}) { super(m, "quota", e); this.name = "QuotaError"; }
|
|
36
|
+
}
|
|
37
|
+
/** This model or quality needs a plan, or the account has never bought credits. */
|
|
38
|
+
export class PlanError extends MovoaError {
|
|
39
|
+
constructor(m, e = {}) { super(m, "plan", e); this.name = "PlanError"; }
|
|
40
|
+
}
|
|
41
|
+
/** Too many runs on this account are generating at once. */
|
|
42
|
+
export class BusyError extends MovoaError {
|
|
43
|
+
constructor(m, e = {}) { super(m, "busy", e); this.name = "BusyError"; }
|
|
44
|
+
}
|
|
45
|
+
/** The content filter refused the prompt (code -2) or a cooldown is active (code -3). Reword it. */
|
|
46
|
+
export class RejectedError extends MovoaError {
|
|
47
|
+
constructor(m, e = {}) { super(m, "rejected", e); this.name = "RejectedError"; }
|
|
48
|
+
}
|
|
49
|
+
/** The run was accepted but ended as failed or canceled. */
|
|
50
|
+
export class GenerationError extends MovoaError {
|
|
51
|
+
errorMessage;
|
|
52
|
+
failureKind;
|
|
53
|
+
constructor(m, errorMessage, failureKind, taskId) {
|
|
54
|
+
super(m, "failed", { taskId });
|
|
55
|
+
this.errorMessage = errorMessage;
|
|
56
|
+
this.failureKind = failureKind;
|
|
57
|
+
this.name = "GenerationError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** The wait ran out; the run may still finish. `taskId` lets you check it later. */
|
|
61
|
+
export class TimeoutError extends MovoaError {
|
|
62
|
+
constructor(m, taskId) { super(m, "timeout", { taskId }); this.name = "TimeoutError"; }
|
|
63
|
+
}
|
|
64
|
+
function env(name) {
|
|
65
|
+
// deno-lint-ignore no-explicit-any
|
|
66
|
+
const g = globalThis;
|
|
67
|
+
try {
|
|
68
|
+
return g.process?.env?.[name] ?? g.Deno?.env?.get(name);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined; // Deno without --allow-env
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Map a refusal to the error that says what to do next. movoa.ai answers almost
|
|
76
|
+
* every refusal as HTTP 200 with a non-zero `code` and a `data.reason`.
|
|
77
|
+
*/
|
|
78
|
+
export function errorFor(status, body) {
|
|
79
|
+
const message = body.message || `request failed (HTTP ${status})`;
|
|
80
|
+
const reason = typeof body.data?.reason === "string" ? body.data.reason : undefined;
|
|
81
|
+
const extra = { status, code: body.code, reason };
|
|
82
|
+
if (status === 401)
|
|
83
|
+
return new AuthError(message.includes(KEYS_URL) ? message : `${message} (create a key at ${KEYS_URL})`, extra);
|
|
84
|
+
if (body.code === -2 || body.code === -3)
|
|
85
|
+
return new RejectedError(message, extra);
|
|
86
|
+
if (reason === "insufficient_credits")
|
|
87
|
+
return new QuotaError(message, extra);
|
|
88
|
+
if (reason === "plan_required")
|
|
89
|
+
return new PlanError(message, extra);
|
|
90
|
+
if (reason === "concurrency")
|
|
91
|
+
return new BusyError(message, extra);
|
|
92
|
+
if (/no auth/i.test(message))
|
|
93
|
+
return new AuthError(`${message}. Create a key at ${KEYS_URL}`, extra);
|
|
94
|
+
return new MovoaError(message, "other", extra);
|
|
95
|
+
}
|
|
96
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
97
|
+
async function post(path, body, o) {
|
|
98
|
+
const key = (o.apiKey || env("MOVOA_API_KEY") || "").trim();
|
|
99
|
+
if (!key)
|
|
100
|
+
throw new AuthError(`No API key: pass apiKey or set MOVOA_API_KEY. Create a key at ${KEYS_URL}`);
|
|
101
|
+
const base = (o.baseUrl || env("MOVOA_BASE_URL") || BASE_URL).replace(/\/$/, "");
|
|
102
|
+
let res;
|
|
103
|
+
try {
|
|
104
|
+
res = await (o.fetch ?? fetch)(base + path, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
|
|
107
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
throw new MovoaError(`Could not reach movoa.ai: ${e.message}`, "other");
|
|
112
|
+
}
|
|
113
|
+
const text = await res.text();
|
|
114
|
+
let json;
|
|
115
|
+
try {
|
|
116
|
+
json = text ? JSON.parse(text) : {};
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
json = undefined;
|
|
120
|
+
}
|
|
121
|
+
if (res.status === 401)
|
|
122
|
+
throw errorFor(401, json ?? {});
|
|
123
|
+
if (!json || typeof json !== "object")
|
|
124
|
+
throw new MovoaError(`movoa.ai returned a non-JSON response (HTTP ${res.status})`, "other", { status: res.status });
|
|
125
|
+
if (res.status < 400 && json.code === 0)
|
|
126
|
+
return json;
|
|
127
|
+
throw errorFor(res.status, json);
|
|
128
|
+
}
|
|
129
|
+
/** Start one run and resolve to its task id without waiting. */
|
|
130
|
+
export async function submit(prompt, v = {}, o = {}) {
|
|
131
|
+
if (!prompt?.trim())
|
|
132
|
+
throw new TypeError("prompt is required");
|
|
133
|
+
const options = { sound: true };
|
|
134
|
+
for (const [k, val] of Object.entries(v.options ?? {}))
|
|
135
|
+
if (val !== undefined && val !== null)
|
|
136
|
+
options[k] = val;
|
|
137
|
+
if (v.duration !== undefined)
|
|
138
|
+
options.duration = v.duration;
|
|
139
|
+
if (v.resolution !== undefined)
|
|
140
|
+
options.resolution = v.resolution;
|
|
141
|
+
if (v.aspectRatio !== undefined)
|
|
142
|
+
options.aspect_ratio = v.aspectRatio;
|
|
143
|
+
if (v.imageUrl)
|
|
144
|
+
options.image_input = [v.imageUrl];
|
|
145
|
+
const json = await post("/api/ai/generate", {
|
|
146
|
+
// The server routes by model; this only has to be non-empty.
|
|
147
|
+
provider: "auto",
|
|
148
|
+
mediaType: "video",
|
|
149
|
+
model: v.model ?? DEFAULT_MODEL,
|
|
150
|
+
scene: v.imageUrl ? "image-to-video" : "text-to-video",
|
|
151
|
+
prompt,
|
|
152
|
+
options,
|
|
153
|
+
}, o);
|
|
154
|
+
const id = json.data?.id;
|
|
155
|
+
if (!id)
|
|
156
|
+
throw new MovoaError("movoa.ai accepted the run but returned no id", "other");
|
|
157
|
+
return String(id);
|
|
158
|
+
}
|
|
159
|
+
/** Fetch the run record: `status` is pending, processing, success, failed or canceled. */
|
|
160
|
+
export async function status(taskId, o = {}) {
|
|
161
|
+
const json = await post("/api/ai/query", { taskId }, o);
|
|
162
|
+
return (json.data ?? {});
|
|
163
|
+
}
|
|
164
|
+
/** Poll a run until it finishes and resolve to the video URL. */
|
|
165
|
+
export async function wait(taskId, o = {}) {
|
|
166
|
+
const timeout = o.timeout ?? 30 * 60_000;
|
|
167
|
+
const every = o.pollEvery ?? 10_000;
|
|
168
|
+
const deadline = Date.now() + timeout;
|
|
169
|
+
for (;;) {
|
|
170
|
+
const t = await status(taskId, o);
|
|
171
|
+
if (t.status === "success") {
|
|
172
|
+
if (!t.videos?.length)
|
|
173
|
+
throw new GenerationError(`run ${taskId} finished with no video`, undefined, undefined, taskId);
|
|
174
|
+
return String(t.videos[0]);
|
|
175
|
+
}
|
|
176
|
+
if (t.status === "failed" || t.status === "canceled") {
|
|
177
|
+
throw new GenerationError(`run ${taskId} ${t.status}${t.errorMessage ? `: ${t.errorMessage}` : ""}`, t.errorMessage, t.failureKind, taskId);
|
|
178
|
+
}
|
|
179
|
+
if (Date.now() + every > deadline) {
|
|
180
|
+
throw new TimeoutError(`run ${taskId} is still generating; check it later with status()`, taskId);
|
|
181
|
+
}
|
|
182
|
+
await sleep(every);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Generate one clip with sound and resolve to its URL. */
|
|
186
|
+
export async function generateVideo(prompt, v = {}) {
|
|
187
|
+
return wait(await submit(prompt, v, v), v);
|
|
188
|
+
}
|
|
189
|
+
/** Resolve to the remaining credits on the account. */
|
|
190
|
+
export async function credits(o = {}) {
|
|
191
|
+
const json = await post("/api/user/get-user-credits", undefined, o);
|
|
192
|
+
return Number(json.data?.remainingCredits ?? 0);
|
|
193
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "movoa-ai",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unlimited AI video generator with sound: on paid plans the tenth take of a shot costs what the first did, and your first clip is free to make right now. Official TypeScript / JavaScript SDK for Movoa (movoa.ai): text to video and photo to video with sound from Node, Deno or Bun.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/mod.js",
|
|
7
|
+
"types": "./dist/mod.d.ts",
|
|
8
|
+
"exports": { ".": { "types": "./dist/mod.d.ts", "default": "./dist/mod.js" } },
|
|
9
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
10
|
+
"engines": { "node": ">=18" },
|
|
11
|
+
"keywords": ["ai", "video-generation", "ai-video-generator", "text-to-video", "image-to-video", "photo-to-video", "ai-video-with-audio", "minimax-h3", "seedance-2-5", "veo-3", "movoa"],
|
|
12
|
+
"homepage": "https://movoa.ai/?utm_source=npm&utm_medium=package",
|
|
13
|
+
"repository": { "type": "git", "url": "git+https://github.com/hanshs474/movoa-ts.git" },
|
|
14
|
+
"bugs": "https://github.com/hanshs474/movoa-ts/issues",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Movoa (https://movoa.ai)",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc mod.ts --outDir dist --declaration --target es2022 --module es2022 --moduleResolution bundler --lib es2022,dom --skipLibCheck",
|
|
19
|
+
"test": "npm run build && node --test test.mjs",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": { "typescript": "^5.6.0" }
|
|
23
|
+
}
|