@remixmate/cli 0.9.55 → 0.9.57
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/dist/handlers/gen-digital-human.d.ts +10 -5
- package/dist/handlers/gen-digital-human.js +143 -37
- package/dist/manifest.json +47 -33
- package/package.json +1 -1
- package/skills/gen-digital-human/SKILL.md +60 -117
- package/skills/gen-digital-human/skill.json +44 -30
- package/skills/gen-digital-human/version.json +2 -2
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* gen-digital-human handler — talking-head video,
|
|
3
|
-
* --list-avatars true
|
|
4
|
-
* --
|
|
5
|
-
*
|
|
2
|
+
* gen-digital-human handler — talking-head video + custom avatars, five modes:
|
|
3
|
+
* --list-avatars true → POST /digital-human/avatar/page (list)
|
|
4
|
+
* --clone --clone-url <url> --name <n> → POST /digital-human/avatar/clone, then poll clone-status
|
|
5
|
+
* --clone-status --avatar-id <id> → POST /digital-human/avatar/clone-status (one-shot)
|
|
6
|
+
* --check-status --generation-id <id> → POST /digital-human/video/status (one-shot)
|
|
7
|
+
* default (--avatar-id <id> ...) → generate: resolve avatar → submit → poll
|
|
6
8
|
*
|
|
7
9
|
* Ported from `scripts/gen_digital_human_video.py` (python → http) so it shares
|
|
8
10
|
* auth / base-URL / business-code handling with the other generators via
|
|
9
|
-
* http.ts. Endpoints and payloads match the backend
|
|
11
|
+
* http.ts. Endpoints and payloads match the backend DigitalHuman* DTOs.
|
|
12
|
+
*
|
|
13
|
+
* Avatar ids are the backend's opaque uids (`dh_...`); legacy numeric ids are
|
|
14
|
+
* still accepted as strings.
|
|
10
15
|
*/
|
|
11
16
|
import type { HandlerContext, HandlerInput } from './index.js';
|
|
12
17
|
export declare function genDigitalHuman(input: HandlerInput, ctxIn: HandlerContext): Promise<void>;
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* gen-digital-human handler — talking-head video,
|
|
3
|
-
* --list-avatars true
|
|
4
|
-
* --
|
|
5
|
-
*
|
|
2
|
+
* gen-digital-human handler — talking-head video + custom avatars, five modes:
|
|
3
|
+
* --list-avatars true → POST /digital-human/avatar/page (list)
|
|
4
|
+
* --clone --clone-url <url> --name <n> → POST /digital-human/avatar/clone, then poll clone-status
|
|
5
|
+
* --clone-status --avatar-id <id> → POST /digital-human/avatar/clone-status (one-shot)
|
|
6
|
+
* --check-status --generation-id <id> → POST /digital-human/video/status (one-shot)
|
|
7
|
+
* default (--avatar-id <id> ...) → generate: resolve avatar → submit → poll
|
|
6
8
|
*
|
|
7
9
|
* Ported from `scripts/gen_digital_human_video.py` (python → http) so it shares
|
|
8
10
|
* auth / base-URL / business-code handling with the other generators via
|
|
9
|
-
* http.ts. Endpoints and payloads match the backend
|
|
11
|
+
* http.ts. Endpoints and payloads match the backend DigitalHuman* DTOs.
|
|
12
|
+
*
|
|
13
|
+
* Avatar ids are the backend's opaque uids (`dh_...`); legacy numeric ids are
|
|
14
|
+
* still accepted as strings.
|
|
10
15
|
*/
|
|
11
16
|
import { mmPost, pollUntil, resolveHttpContext, SkillError } from '../http.js';
|
|
12
17
|
import { getCapabilities } from '../capabilities.js';
|
|
13
18
|
import { emitProgress } from '../progress.js';
|
|
14
19
|
import { isTrue, toNumber } from './shared.js';
|
|
20
|
+
const CLONE_TYPES = ['video', 'image'];
|
|
21
|
+
/**
|
|
22
|
+
* The only provider currently wired end to end (HiFly). HiFly's generate APIs
|
|
23
|
+
* take no aspect ratio or action prompt — the output keeps the cloned source's
|
|
24
|
+
* framing — so the handler sends neither.
|
|
25
|
+
*/
|
|
26
|
+
const ACTIVE_SOURCE = 'hifly';
|
|
27
|
+
function toId(value) {
|
|
28
|
+
if (value === undefined || value === null)
|
|
29
|
+
return '';
|
|
30
|
+
return String(value).trim();
|
|
31
|
+
}
|
|
15
32
|
async function listAvatars(ctx, input) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
payload.source = String(input.source);
|
|
33
|
+
// Only avatars of the active provider can be driven, so only those are listed.
|
|
34
|
+
const payload = { current: 1, pageSize: 100, source: ACTIVE_SOURCE };
|
|
19
35
|
if (input.gender)
|
|
20
36
|
payload.gender = String(input.gender);
|
|
21
37
|
if (isTrue(input.mine))
|
|
@@ -32,31 +48,107 @@ async function listAvatars(ctx, input) {
|
|
|
32
48
|
process.stdout.write('⚠️ No avatars available\n');
|
|
33
49
|
return;
|
|
34
50
|
}
|
|
35
|
-
process.stdout.write(`\n${'ID'.padEnd(
|
|
36
|
-
process.stdout.write(`${'-'.repeat(
|
|
51
|
+
process.stdout.write(`\n${'ID'.padEnd(24)} ${'Name'.padEnd(16)} ${'Source'.padEnd(14)} ${'Gender'.padEnd(8)} ${'Ratio'.padEnd(6)} Clone\n`);
|
|
52
|
+
process.stdout.write(`${'-'.repeat(24)} ${'-'.repeat(16)} ${'-'.repeat(14)} ${'-'.repeat(8)} ${'-'.repeat(6)} ${'-'.repeat(8)}\n`);
|
|
37
53
|
for (const a of avatars) {
|
|
38
|
-
process.stdout.write(`${
|
|
54
|
+
process.stdout.write(`${toId(a.id).padEnd(24)} ${String(a.name ?? '').padEnd(16)} ${String(a.source ?? '').padEnd(14)} ${String(a.gender ?? '').padEnd(8)} ${String(a.aspectRatio ?? '').padEnd(6)} ${a.cloneStatus || '-'}\n`);
|
|
39
55
|
}
|
|
40
|
-
|
|
56
|
+
const total = data.pager?.total ?? avatars.length;
|
|
57
|
+
process.stdout.write(`\n${avatars.length} avatar(s) shown${total > avatars.length ? ` of ${total}; narrow with --name` : ''}\n`);
|
|
41
58
|
process.stdout.write('💡 Use --avatar-id <ID> to pick an avatar for generation\n');
|
|
42
59
|
}
|
|
43
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Look up one avatar's metadata. Only the caller's own avatars are searched: they
|
|
62
|
+
* are few, and they are the only ones that can be mid-clone or failed. The public
|
|
63
|
+
* HiFly catalog has ~1000 entries, far beyond one page, so a public id that is not
|
|
64
|
+
* among "mine" is passed through as-is and the backend validates it (unknown ids
|
|
65
|
+
* come back as a business error).
|
|
66
|
+
*/
|
|
44
67
|
async function fetchAvatarInfo(ctx, avatarId) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (found)
|
|
49
|
-
return found;
|
|
50
|
-
}
|
|
51
|
-
throw new SkillError(`❌ avatar_id=${avatarId} not found; use --list-avatars [--mine] to see what is available`);
|
|
68
|
+
const data = await mmPost(ctx, '/digital-human/avatar/page', { current: 1, pageSize: 100, mine: true, source: ACTIVE_SOURCE }, { timeoutMs: 30_000 });
|
|
69
|
+
const found = (data.list ?? []).find((a) => toId(a.id) === avatarId);
|
|
70
|
+
return found ?? { id: avatarId, source: ACTIVE_SOURCE };
|
|
52
71
|
}
|
|
53
|
-
function resolveSource(
|
|
54
|
-
const src = (
|
|
72
|
+
function resolveSource(avatar, validSources) {
|
|
73
|
+
const src = (avatar.source ?? '').trim() || ACTIVE_SOURCE;
|
|
55
74
|
if (!validSources.includes(src)) {
|
|
56
|
-
throw new SkillError(`❌
|
|
75
|
+
throw new SkillError(`❌ avatar uses provider "${src}", which is not available (supported: ${validSources.join('/')}). Pick another avatar from --list-avatars.`);
|
|
57
76
|
}
|
|
58
77
|
return src;
|
|
59
78
|
}
|
|
79
|
+
// ── Avatar cloning (HiFly) ─────────────────────────────────────────────────
|
|
80
|
+
async function cloneAvatar(ctx, input) {
|
|
81
|
+
const fileUrl = toId(input.clone_url);
|
|
82
|
+
if (!/^https?:\/\//i.test(fileUrl)) {
|
|
83
|
+
throw new SkillError('❌ --clone needs --clone-url <public http(s) URL of a video or portrait image> (local files are not uploaded)');
|
|
84
|
+
}
|
|
85
|
+
const name = toId(input.name);
|
|
86
|
+
if (!name)
|
|
87
|
+
throw new SkillError('❌ --clone needs --name <avatar name>');
|
|
88
|
+
const sourceType = toId(input.clone_type) || 'video';
|
|
89
|
+
if (!CLONE_TYPES.includes(sourceType)) {
|
|
90
|
+
throw new SkillError(`❌ --clone-type must be one of ${CLONE_TYPES.join(' / ')}, received: ${sourceType}`);
|
|
91
|
+
}
|
|
92
|
+
const payload = { name, sourceType, fileUrl };
|
|
93
|
+
if (input.thumbnail_url)
|
|
94
|
+
payload.thumbnailUrl = toId(input.thumbnail_url);
|
|
95
|
+
if (input.gender)
|
|
96
|
+
payload.gender = toId(input.gender);
|
|
97
|
+
emitProgress({ phase: 'gen-digital-human:clone-request', sourceType });
|
|
98
|
+
const data = await mmPost(ctx, '/digital-human/avatar/clone', payload, { timeoutMs: 60_000 });
|
|
99
|
+
const avatarId = toId(data.id);
|
|
100
|
+
if (!avatarId) {
|
|
101
|
+
throw new SkillError(`❌ could not parse the new avatar id; API returned: ${JSON.stringify(data)}`);
|
|
102
|
+
}
|
|
103
|
+
emitProgress({ phase: 'gen-digital-human:clone-async', avatarId });
|
|
104
|
+
const json = isTrue(input.json_output);
|
|
105
|
+
if (input.wait !== undefined && !isTrue(input.wait)) {
|
|
106
|
+
if (json) {
|
|
107
|
+
process.stdout.write(JSON.stringify({ avatarId, cloneStatus: data.cloneStatus ?? 'cloning' }) + '\n');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
process.stdout.write(`\n🧬 Clone submitted, avatar id: ${avatarId}\n`);
|
|
111
|
+
process.stdout.write(`💡 Check progress with --clone-status --avatar-id ${avatarId}\n`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
let final;
|
|
115
|
+
try {
|
|
116
|
+
final = await pollUntil(async () => {
|
|
117
|
+
const s = await mmPost(ctx, '/digital-human/avatar/clone-status', { id: avatarId }, { timeoutMs: 30_000 });
|
|
118
|
+
const status = s.cloneStatus ?? 'unknown';
|
|
119
|
+
emitProgress({ phase: 'gen-digital-human:clone-poll', avatarId, status });
|
|
120
|
+
if (status === 'done')
|
|
121
|
+
return s;
|
|
122
|
+
if (status === 'failed')
|
|
123
|
+
throw new SkillError(`❌ avatar clone failed: ${s.cloneError || 'unknown error'}`);
|
|
124
|
+
return null;
|
|
125
|
+
}, { intervalMs: 10_000, timeoutMs: 20 * 60_000 });
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (err instanceof SkillError && /polling timed out/.test(err.message)) {
|
|
129
|
+
throw new SkillError(`❌ avatar ${avatarId} is still cloning; rerun --clone-status --avatar-id ${avatarId} later`);
|
|
130
|
+
}
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
if (json) {
|
|
134
|
+
process.stdout.write(JSON.stringify({ avatarId, cloneStatus: final.cloneStatus }) + '\n');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
process.stdout.write(`\n✅ Avatar cloned, id: ${avatarId}\n`);
|
|
138
|
+
process.stdout.write(`💡 Generate with --avatar-id ${avatarId} --text "..."\n`);
|
|
139
|
+
}
|
|
140
|
+
async function checkCloneStatus(ctx, avatarId, json) {
|
|
141
|
+
const s = await mmPost(ctx, '/digital-human/avatar/clone-status', { id: avatarId }, { timeoutMs: 30_000 });
|
|
142
|
+
const status = s.cloneStatus || 'unknown';
|
|
143
|
+
if (json) {
|
|
144
|
+
process.stdout.write(JSON.stringify({ avatarId, cloneStatus: status, error: s.cloneError || null }) + '\n');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
process.stdout.write(`\nclone status: ${status}\n`);
|
|
148
|
+
if (status === 'failed')
|
|
149
|
+
process.stdout.write(` failure reason: ${s.cloneError || 'unknown error'}\n`);
|
|
150
|
+
}
|
|
151
|
+
// ── Video generation ───────────────────────────────────────────────────────
|
|
60
152
|
async function checkStatus(ctx, generationId, json) {
|
|
61
153
|
const data = await mmPost(ctx, '/digital-human/video/status', { generationId }, { timeoutMs: 30_000 });
|
|
62
154
|
const status = data.status ?? 'unknown';
|
|
@@ -93,25 +185,40 @@ export async function genDigitalHuman(input, ctxIn) {
|
|
|
93
185
|
privateToken: input.priv_token,
|
|
94
186
|
preflightToken: ctxIn.auth?.token,
|
|
95
187
|
});
|
|
188
|
+
const json = isTrue(input.json_output);
|
|
96
189
|
// ── Mode 1: list avatars ────────────────────────────────────────────────
|
|
97
190
|
if (isTrue(input.list_avatars)) {
|
|
98
191
|
return listAvatars(ctx, input);
|
|
99
192
|
}
|
|
100
|
-
// ── Mode
|
|
193
|
+
// ── Mode 2: clone a custom avatar (HiFly) ───────────────────────────────
|
|
194
|
+
if (isTrue(input.clone)) {
|
|
195
|
+
return cloneAvatar(ctx, input);
|
|
196
|
+
}
|
|
197
|
+
// ── Mode 3: clone status ────────────────────────────────────────────────
|
|
198
|
+
if (isTrue(input.clone_status)) {
|
|
199
|
+
const avatarId = toId(input.avatar_id);
|
|
200
|
+
if (!avatarId)
|
|
201
|
+
throw new SkillError('❌ clone-status mode requires --avatar-id');
|
|
202
|
+
return checkCloneStatus(ctx, avatarId, json);
|
|
203
|
+
}
|
|
204
|
+
// ── Mode 4: check generation status ─────────────────────────────────────
|
|
101
205
|
if (isTrue(input.check_status)) {
|
|
102
206
|
const generationId = toNumber(input.generation_id, 'generation_id');
|
|
103
207
|
if (generationId === undefined) {
|
|
104
208
|
throw new SkillError('❌ check-status mode requires --generation-id');
|
|
105
209
|
}
|
|
106
|
-
return checkStatus(ctx, generationId,
|
|
210
|
+
return checkStatus(ctx, generationId, json);
|
|
107
211
|
}
|
|
108
|
-
// ── Mode
|
|
109
|
-
const avatarId =
|
|
110
|
-
if (avatarId
|
|
212
|
+
// ── Mode 5: generate (default) ──────────────────────────────────────────
|
|
213
|
+
const avatarId = toId(input.avatar_id);
|
|
214
|
+
if (!avatarId) {
|
|
111
215
|
throw new SkillError('❌ provide --avatar-id to pick the avatar (use --list-avatars to see available avatars)');
|
|
112
216
|
}
|
|
113
217
|
const audioUrl = (input.audio_url ?? '').trim();
|
|
114
218
|
const text = input.text ?? '';
|
|
219
|
+
if (audioUrl && !/^https?:\/\//i.test(audioUrl)) {
|
|
220
|
+
throw new SkillError(`❌ --audio-url needs a public http(s) URL (local files are not uploaded), received: ${audioUrl}`);
|
|
221
|
+
}
|
|
115
222
|
if (!audioUrl) {
|
|
116
223
|
if (!text.trim()) {
|
|
117
224
|
throw new SkillError('❌ in TTS mode provide --text, or use --audio-url for audio-driven mode');
|
|
@@ -120,12 +227,16 @@ export async function genDigitalHuman(input, ctxIn) {
|
|
|
120
227
|
const dh = (await getCapabilities(input.api_base_url)).digitalHuman;
|
|
121
228
|
emitProgress({ phase: 'gen-digital-human:resolve-avatar', avatarId });
|
|
122
229
|
const avatar = await fetchAvatarInfo(ctx, avatarId);
|
|
123
|
-
|
|
230
|
+
if (avatar.cloneStatus === 'cloning') {
|
|
231
|
+
throw new SkillError(`❌ avatar ${avatarId} is still cloning; check with --clone-status --avatar-id ${avatarId}`);
|
|
232
|
+
}
|
|
233
|
+
if (avatar.cloneStatus === 'failed') {
|
|
234
|
+
throw new SkillError(`❌ avatar ${avatarId} failed to clone (${avatar.cloneError || 'unknown error'}); clone it again`);
|
|
235
|
+
}
|
|
236
|
+
const source = resolveSource(avatar, dh.sources);
|
|
124
237
|
const payload = { avatarId, source };
|
|
125
238
|
if (audioUrl) {
|
|
126
239
|
payload.audioUrl = audioUrl;
|
|
127
|
-
if (text.trim())
|
|
128
|
-
payload.text = text.trim();
|
|
129
240
|
}
|
|
130
241
|
else {
|
|
131
242
|
payload.text = text;
|
|
@@ -133,11 +244,6 @@ export async function genDigitalHuman(input, ctxIn) {
|
|
|
133
244
|
if (input.voice_name)
|
|
134
245
|
payload.voiceName = String(input.voice_name);
|
|
135
246
|
}
|
|
136
|
-
const aspectRatio = input.aspect_ratio ?? avatar.aspectRatio;
|
|
137
|
-
if (aspectRatio)
|
|
138
|
-
payload.aspectRatio = aspectRatio;
|
|
139
|
-
if (input.prompt)
|
|
140
|
-
payload.prompt = String(input.prompt);
|
|
141
247
|
emitProgress({ phase: 'gen-digital-human:request', avatarId, source, mode: audioUrl ? 'audio' : 'tts' });
|
|
142
248
|
const data = await mmPost(ctx, '/digital-human/video/generate', payload, { timeoutMs: 60_000 });
|
|
143
249
|
const generationId = data.generationId;
|
|
@@ -149,7 +255,7 @@ export async function genDigitalHuman(input, ctxIn) {
|
|
|
149
255
|
if (!videoUrl) {
|
|
150
256
|
throw new SkillError(`❌ video generated but no URL was returned; rerun --check-status --generation-id ${generationId} later`);
|
|
151
257
|
}
|
|
152
|
-
if (
|
|
258
|
+
if (json) {
|
|
153
259
|
process.stdout.write(JSON.stringify({ url: videoUrl, generationId }) + '\n');
|
|
154
260
|
return;
|
|
155
261
|
}
|
package/dist/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"version": "0.9.57",
|
|
4
|
+
"generatedAt": "2026-09-29T07:05:55.130Z",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"id": "doc-parse",
|
|
@@ -169,12 +169,12 @@
|
|
|
169
169
|
"tier": "atomic",
|
|
170
170
|
"category": "asset",
|
|
171
171
|
"title": "Digital-Human Talking-Head",
|
|
172
|
-
"summary": "Digital-human video: list
|
|
172
|
+
"summary": "Digital-human video (HiFly): list or clone avatars; produce a talking-head video from text via TTS, or drive an avatar from an existing audio URL. Aspect ratio follows the avatar's cloned source and cannot be chosen.",
|
|
173
173
|
"triggers": [
|
|
174
174
|
"Digital human, talking-head video, AI presenter, virtual host",
|
|
175
175
|
"Make a talking-head video out of this script / this audio file",
|
|
176
176
|
"List or browse digital-human avatars",
|
|
177
|
-
"
|
|
177
|
+
"Clone / create a custom avatar from a video or photo"
|
|
178
178
|
],
|
|
179
179
|
"entry": {
|
|
180
180
|
"type": "http",
|
|
@@ -199,15 +199,7 @@
|
|
|
199
199
|
},
|
|
200
200
|
"name": {
|
|
201
201
|
"type": "string",
|
|
202
|
-
"description": "With list_avatars=true: fuzzy-filter avatars by name"
|
|
203
|
-
},
|
|
204
|
-
"source": {
|
|
205
|
-
"type": "string",
|
|
206
|
-
"enum": [
|
|
207
|
-
"jimeng",
|
|
208
|
-
"hifly"
|
|
209
|
-
],
|
|
210
|
-
"description": "Provider: jimeng is image-driven, hifly is video-driven. Usually inferred from the avatar; pass it explicitly when the avatar declares no source."
|
|
202
|
+
"description": "With list_avatars=true: fuzzy-filter avatars by name. With clone=true: the new avatar's name (required)."
|
|
211
203
|
},
|
|
212
204
|
"gender": {
|
|
213
205
|
"type": "string",
|
|
@@ -218,33 +210,53 @@
|
|
|
218
210
|
"description": "Filter by gender"
|
|
219
211
|
},
|
|
220
212
|
"avatar_id": {
|
|
221
|
-
"type": "
|
|
222
|
-
"description": "Avatar id"
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "Avatar id as returned by list_avatars (opaque string like dh_xxx). Also used with clone_status=true."
|
|
215
|
+
},
|
|
216
|
+
"clone": {
|
|
217
|
+
"type": "boolean",
|
|
218
|
+
"description": "Clone mode: create a custom HiFly avatar from a video (free) or a portrait image (costs HiFly credits). Requires clone_url and name. Waits until cloning finishes unless wait=false."
|
|
219
|
+
},
|
|
220
|
+
"clone_url": {
|
|
221
|
+
"type": "string",
|
|
222
|
+
"description": "With clone=true: public http(s) URL of the source. Video: mp4/mov (h264), 5s-30min, 360p-4K, ≤500MB, one clearly visible speaking person. Image: a front-facing portrait."
|
|
223
|
+
},
|
|
224
|
+
"clone_type": {
|
|
225
|
+
"type": "string",
|
|
226
|
+
"enum": [
|
|
227
|
+
"video",
|
|
228
|
+
"image"
|
|
229
|
+
],
|
|
230
|
+
"description": "With clone=true: source type. Defaults to video."
|
|
231
|
+
},
|
|
232
|
+
"thumbnail_url": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"description": "With clone=true: optional cover image URL shown in avatar lists (image clones default to the image itself). The output aspect ratio always follows the cloned source; it cannot be chosen at generation time."
|
|
235
|
+
},
|
|
236
|
+
"wait": {
|
|
237
|
+
"type": "boolean",
|
|
238
|
+
"description": "With clone=true: set false to return right after submission (then poll with clone_status=true). Defaults to waiting up to ~20 minutes."
|
|
239
|
+
},
|
|
240
|
+
"clone_status": {
|
|
241
|
+
"type": "boolean",
|
|
242
|
+
"description": "Clone-status mode: report cloning / done / failed for avatar_id."
|
|
223
243
|
},
|
|
224
244
|
"text": {
|
|
225
245
|
"type": "string",
|
|
226
|
-
"description": "Narration text (TTS mode)"
|
|
246
|
+
"description": "Narration text (TTS mode, ≤ 10000 chars). Synthesized with voice_id, then drives the avatar; the speech must last at least 5 s. Ignored when audio_url is given."
|
|
227
247
|
},
|
|
228
248
|
"audio_url": {
|
|
229
249
|
"type": "string",
|
|
230
|
-
"description": "Audio
|
|
250
|
+
"description": "Audio-driven mode: public http(s) URL of mp3 / m4a / wav, 5 s – 30 min, ≤ 100 MB. Takes precedence over text / voice_id."
|
|
231
251
|
},
|
|
232
252
|
"voice_id": {
|
|
233
253
|
"type": "string",
|
|
234
|
-
"description": "Voice id (TTS mode). Shares the
|
|
254
|
+
"description": "Voice id (TTS mode). Shares the catalog with gen-voice (including cloned voices) — call gen_voice with list_voices=true to see available ids rather than inventing one."
|
|
235
255
|
},
|
|
236
256
|
"voice_name": {
|
|
237
257
|
"type": "string",
|
|
238
258
|
"description": "Voice display name, recorded alongside voice_id for bookkeeping. Does not affect synthesis."
|
|
239
259
|
},
|
|
240
|
-
"aspect_ratio": {
|
|
241
|
-
"type": "string",
|
|
242
|
-
"description": "Aspect ratio: 9:16 / 16:9 / 3:4 / 1:1. Defaults to the avatar's own ratio."
|
|
243
|
-
},
|
|
244
|
-
"prompt": {
|
|
245
|
-
"type": "string",
|
|
246
|
-
"description": "Action prompt describing how the avatar should perform, e.g. 'more hand gestures'"
|
|
247
|
-
},
|
|
248
260
|
"check_status": {
|
|
249
261
|
"type": "boolean",
|
|
250
262
|
"description": "Status-check mode: poll an earlier job instead of starting a new one. Requires generation_id. Use this when a generate call timed out."
|
|
@@ -255,7 +267,7 @@
|
|
|
255
267
|
},
|
|
256
268
|
"json_output": {
|
|
257
269
|
"type": "boolean",
|
|
258
|
-
"description": "Emit a JSON result (generate: { url, generationId }; list: { avatars }; check-status: { status, url }) instead of human-readable output"
|
|
270
|
+
"description": "Emit a JSON result (generate: { url, generationId }; list: { avatars }; check-status: { status, url }; clone / clone-status: { avatarId, cloneStatus }) instead of human-readable output"
|
|
259
271
|
}
|
|
260
272
|
},
|
|
261
273
|
"required": []
|
|
@@ -263,18 +275,20 @@
|
|
|
263
275
|
"ui": {
|
|
264
276
|
"primary": [
|
|
265
277
|
"avatar_id",
|
|
266
|
-
"text"
|
|
267
|
-
],
|
|
268
|
-
"advanced": [
|
|
269
|
-
"source",
|
|
278
|
+
"text",
|
|
270
279
|
"voice_id",
|
|
271
|
-
"aspect_ratio",
|
|
272
|
-
"prompt",
|
|
273
280
|
"audio_url"
|
|
274
281
|
],
|
|
282
|
+
"advanced": [],
|
|
275
283
|
"hidden": [
|
|
276
284
|
"json_output",
|
|
277
285
|
"list_avatars",
|
|
286
|
+
"clone",
|
|
287
|
+
"clone_url",
|
|
288
|
+
"clone_type",
|
|
289
|
+
"thumbnail_url",
|
|
290
|
+
"wait",
|
|
291
|
+
"clone_status",
|
|
278
292
|
"mine",
|
|
279
293
|
"name",
|
|
280
294
|
"gender",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.57",
|
|
4
4
|
"description": "AI media generation skills for Claude Code / Codex — 12 skills covering image, video, voice, digital human, web screenshot, web recording, script, template registry, rendering, Jianying export, and video deconstruction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1,111 +1,78 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gen-digital-human
|
|
3
3
|
description: |
|
|
4
|
-
Digital-human (talking-head) skill: list
|
|
4
|
+
Digital-human (talking-head) skill backed by HiFly: list avatars, clone a custom avatar from a video or photo, drive an avatar with text (TTS) or an existing audio URL, and check the status of pending jobs.
|
|
5
5
|
|
|
6
6
|
Use this skill immediately whenever the user asks for any of:
|
|
7
7
|
- Digital human, talking-head video, AI presenter, virtual host
|
|
8
8
|
- "Make a talking-head video out of this script / this audio file"
|
|
9
9
|
- List or browse digital-human avatars
|
|
10
|
-
-
|
|
10
|
+
- Clone / create a custom avatar from a video or photo
|
|
11
11
|
|
|
12
12
|
Even without an explicit "use AI", any request that turns text or audio into a synthesized presenter video should route here.
|
|
13
13
|
triggers:
|
|
14
14
|
- Digital human, talking-head video, AI presenter, virtual host
|
|
15
15
|
- "Make a talking-head video out of this script / this audio file"
|
|
16
16
|
- List or browse digital-human avatars
|
|
17
|
-
-
|
|
17
|
+
- Clone / create a custom avatar from a video or photo
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
# Digital-Human Skill
|
|
21
21
|
|
|
22
|
-
Wraps ab-api's digital-human endpoints (the same ones the web studio uses), authenticated with the **Tianyan privateToken**.
|
|
23
|
-
|
|
24
|
-
> This skill was migrated from a Python script to an remixmate CLI HTTP handler (`entry.type: http`). The agent invocation is unchanged (same tool name `gen_digital_human`, same params as in `skill.json`); local repro goes through `remixmate gen-digital-human ...`.
|
|
22
|
+
Wraps ab-api's digital-human endpoints (the same ones the web studio uses), authenticated with the **Tianyan privateToken**. The provider is **HiFly**: avatars are cloned from a video or photo, then driven by audio. Generation is async — the handler submits the task and polls `/digital-human/video/status` until completion.
|
|
25
23
|
|
|
26
24
|
## Capabilities
|
|
27
25
|
|
|
28
26
|
| Capability | Description | API |
|
|
29
27
|
|------------|-------------|-----|
|
|
30
|
-
| List avatars |
|
|
31
|
-
|
|
|
28
|
+
| List avatars | Public avatars, or your own with `--mine` | `POST /digital-human/avatar/page` |
|
|
29
|
+
| Text-driven | avatar + text + voice → speech is synthesized, then drives the avatar | `POST /digital-human/video/generate` |
|
|
32
30
|
| Audio-driven | avatar + existing audio URL → talking-head video | `POST /digital-human/video/generate` |
|
|
33
|
-
| Check status | Poll
|
|
31
|
+
| Check status | Poll a generation job | `POST /digital-human/video/status` |
|
|
32
|
+
| Clone avatar | Custom avatar from a video (free) or portrait photo (costs HiFly credits) | `POST /digital-human/avatar/clone` |
|
|
33
|
+
| Clone status | `cloning` / `done` / `failed` for a cloned avatar | `POST /digital-human/avatar/clone-status` |
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Avatar ids are opaque strings (e.g. `dh_xxx`) — always copy them from `--list-avatars`, never invent them.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
**What you cannot choose**: aspect ratio and on-camera actions. The output keeps the framing of the source the avatar was cloned from (a 9:16 clone produces 9:16 videos). To get another ratio, clone the avatar from footage in that ratio.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
**Cross-skill dependency**: voice ids are shared with `gen-voice` (including cloned voices). Use `gen-voice --list-voices` to see them.
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
- **Other environments**: configure the token. Without a token, non-interactive runs fail.
|
|
41
|
+
## Auth & environment
|
|
43
42
|
|
|
44
43
|
| Env var | Description | Default |
|
|
45
44
|
|---------|-------------|---------|
|
|
46
|
-
| `PRIV_TOKEN` | Tianyan token; `--priv-token` overrides | none |
|
|
45
|
+
| `PRIV_TOKEN` | Tianyan token; `--priv-token` overrides (not needed in Enterprise OpenClaw) | none |
|
|
47
46
|
| `MM_API_BASE_URL` | API root; `--api-base-url` overrides | `https://api.remixmate.ai/api` |
|
|
48
47
|
| `AGENT_NAME` | Optional `x-invoke-agent` header | none |
|
|
49
48
|
|
|
50
49
|
## Operations
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
Generation typically takes 1–5 minutes; the handler polls and emits `__progress__` lines. The resulting video URL is cloud-persisted — show it directly, no download needed.
|
|
53
52
|
|
|
54
53
|
### 1. List avatars
|
|
55
54
|
|
|
56
55
|
```bash
|
|
57
|
-
remixmate gen-digital-human --list-avatars
|
|
56
|
+
remixmate gen-digital-human --list-avatars --name "教师" # public avatars (~1000; always narrow with --name)
|
|
57
|
+
remixmate gen-digital-human --list-avatars --mine # your cloned avatars
|
|
58
|
+
remixmate gen-digital-human --list-avatars --gender female --name "alice"
|
|
58
59
|
```
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
# Filter by source (jimeng = image-driven, hifly = video-driven)
|
|
64
|
-
remixmate gen-digital-human --list-avatars --source jimeng
|
|
65
|
-
remixmate gen-digital-human --list-avatars --source hifly
|
|
66
|
-
|
|
67
|
-
# Filter by gender
|
|
68
|
-
remixmate gen-digital-human --list-avatars --gender female
|
|
61
|
+
### 2. Generate a video
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
remixmate gen-digital-human --list-avatars --mine
|
|
72
|
-
|
|
73
|
-
# Filter by name
|
|
74
|
-
remixmate gen-digital-human --list-avatars --name "alice"
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
> Custom avatars require `--mine`. When generating a video the handler auto-falls back from the public list to `mine: true` if needed.
|
|
78
|
-
|
|
79
|
-
### 2. Generate a digital-human video
|
|
80
|
-
|
|
81
|
-
#### TTS mode
|
|
63
|
+
Pick exactly one driver: **text** (+ voice) or **audio**. With `--audio-url`, text and voice are ignored.
|
|
82
64
|
|
|
83
65
|
```bash
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
--text "Hi everyone,
|
|
66
|
+
# Text-driven
|
|
67
|
+
remixmate gen-digital-human --avatar-id dh_xxx \
|
|
68
|
+
--text "Hi everyone, today I'll introduce a new product." \
|
|
87
69
|
--voice-id "male-qn-qingse"
|
|
88
|
-
```
|
|
89
70
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
remixmate gen-digital-human \
|
|
94
|
-
--avatar-id 7 \
|
|
95
|
-
--text "Hi class, here's our brand-new course." \
|
|
96
|
-
--voice-id "female-shaonv" \
|
|
97
|
-
--aspect-ratio "9:16"
|
|
71
|
+
# Audio-driven
|
|
72
|
+
remixmate gen-digital-human --avatar-id dh_xxx --audio-url "https://example.com/voice.mp3"
|
|
98
73
|
```
|
|
99
74
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
remixmate gen-digital-human \
|
|
104
|
-
--avatar-id 39 \
|
|
105
|
-
--source hifly \
|
|
106
|
-
--audio-url "https://example.com/voice.mp3" \
|
|
107
|
-
--aspect-ratio "16:9"
|
|
108
|
-
```
|
|
75
|
+
Limits: speech/audio must last **5 s – 30 min**; audio is mp3 / m4a / wav, ≤ 100 MB, public http(s) URL; text ≤ 10000 characters.
|
|
109
76
|
|
|
110
77
|
### 3. Check job status
|
|
111
78
|
|
|
@@ -113,67 +80,42 @@ remixmate gen-digital-human \
|
|
|
113
80
|
remixmate gen-digital-human --check-status --generation-id 123
|
|
114
81
|
```
|
|
115
82
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
### List avatars
|
|
119
|
-
|
|
120
|
-
| Flag | Description | Default |
|
|
121
|
-
|------|-------------|---------|
|
|
122
|
-
| `--list-avatars` | List avatars and exit | — |
|
|
123
|
-
| `--source` | Filter by source: `jimeng` / `hifly` | no filter |
|
|
124
|
-
| `--gender` | Filter by gender: `male` / `female` | no filter |
|
|
125
|
-
| `--mine` | Limit to user's custom avatars | off |
|
|
126
|
-
| `--name` | Fuzzy filter by name | no filter |
|
|
127
|
-
|
|
128
|
-
### Generate video
|
|
129
|
-
|
|
130
|
-
| Flag | Description | Default |
|
|
131
|
-
|------|-------------|---------|
|
|
132
|
-
| `--avatar-id` | Avatar id (required) | — |
|
|
133
|
-
| `-t` / `--text` | Speech text (required for TTS mode; optional with `--audio-url`) | — |
|
|
134
|
-
| `--audio-url` | Audio URL (audio-driven mode) | none |
|
|
135
|
-
| `--voice-id` | Voice id (TTS mode) | `male-qn-qingse` |
|
|
136
|
-
| `--voice-name` | Voice display name (recordkeeping) | none |
|
|
137
|
-
| `--source` | `jimeng` / `hifly` (inferable from avatar) | auto |
|
|
138
|
-
| `--aspect-ratio` | `9:16` / `16:9` / `3:4` / `1:1` | avatar's default |
|
|
139
|
-
| `--prompt` | Action prompt (e.g. "more hand gestures") | none |
|
|
140
|
-
| `--api-base-url` | Override API root | see env vars |
|
|
141
|
-
| `--priv-token` | Override token | see env vars |
|
|
142
|
-
|
|
143
|
-
### Check status
|
|
83
|
+
### 4. Clone a custom avatar
|
|
144
84
|
|
|
145
|
-
|
|
146
|
-
|------|-------------|---------|
|
|
147
|
-
| `--check-status` | Enter status-check mode | — |
|
|
148
|
-
| `--generation-id` | Job id (required) | — |
|
|
85
|
+
The source must be a public http(s) URL (local files are not uploaded). Video: mp4/mov (h264), 5 s – 30 min, 360p – 4K, ≤ 500 MB, one person speaking to camera. Cloning takes minutes; the handler waits (up to ~20 min) and prints the new avatar id. The avatar also shows up under 素材库 → 数字人 in the web app.
|
|
149
86
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```
|
|
155
|
-
1. List avatars → --list-avatars [--gender female]
|
|
156
|
-
2. Pick avatar-id
|
|
157
|
-
3. Confirm or polish the speech text
|
|
158
|
-
4. (Optional) pick a voice → use gen-voice --list-voices
|
|
159
|
-
5. Generate → --avatar-id <id> --text "..." --voice-id "female-shaonv"
|
|
160
|
-
6. Show URL
|
|
161
|
-
```
|
|
87
|
+
```bash
|
|
88
|
+
# Video clone (free)
|
|
89
|
+
remixmate gen-digital-human --clone --name "My presenter" --clone-url "https://example.com/me.mp4"
|
|
162
90
|
|
|
163
|
-
|
|
91
|
+
# Portrait photo clone (costs HiFly credits)
|
|
92
|
+
remixmate gen-digital-human --clone --clone-type image --name "My presenter" --clone-url "https://example.com/me.jpg"
|
|
164
93
|
|
|
94
|
+
# Submit without waiting, then poll
|
|
95
|
+
remixmate gen-digital-human --clone --wait false --name "My presenter" --clone-url "https://example.com/me.mp4"
|
|
96
|
+
remixmate gen-digital-human --clone-status --avatar-id dh_xxx
|
|
165
97
|
```
|
|
166
|
-
1. List avatars → --list-avatars [--mine]
|
|
167
|
-
2. Prepare an audio URL (gen-voice can produce one)
|
|
168
|
-
3. Generate → --avatar-id <id> --audio-url "https://..." [--source hifly]
|
|
169
|
-
4. Show URL
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
## Text guidance (TTS)
|
|
173
98
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
99
|
+
## Flags
|
|
100
|
+
|
|
101
|
+
| Flag | Mode | Description | Default |
|
|
102
|
+
|------|------|-------------|---------|
|
|
103
|
+
| `--list-avatars` | list | List avatars and exit | — |
|
|
104
|
+
| `--mine` | list | Only your own avatars | off |
|
|
105
|
+
| `--gender` | list / clone | `male` / `female` | none |
|
|
106
|
+
| `--name` | list / clone | Fuzzy filter; with `--clone` the new avatar's name (required) | — |
|
|
107
|
+
| `--avatar-id` | generate / clone-status | Avatar id (required) | — |
|
|
108
|
+
| `-t` / `--text` | generate | Script (text-driven) | — |
|
|
109
|
+
| `--voice-id` | generate | Voice id (text-driven) | `male-qn-qingse` |
|
|
110
|
+
| `--voice-name` | generate | Voice display name (recordkeeping only) | none |
|
|
111
|
+
| `--audio-url` | generate | Audio URL (audio-driven) | none |
|
|
112
|
+
| `--check-status` / `--generation-id` | status | Poll a generation job | — |
|
|
113
|
+
| `--clone` / `--clone-url` | clone | Clone mode and source URL (required) | — |
|
|
114
|
+
| `--clone-type` | clone | `video` / `image` | `video` |
|
|
115
|
+
| `--thumbnail-url` | clone | Cover shown in lists | image itself / none |
|
|
116
|
+
| `--wait` | clone | `false` to return right after submission | wait |
|
|
117
|
+
| `--clone-status` | clone-status | Report clone status of `--avatar-id` | — |
|
|
118
|
+
| `--api-base-url` / `--priv-token` | all | Overrides | see env vars |
|
|
177
119
|
|
|
178
120
|
## Credits
|
|
179
121
|
|
|
@@ -192,9 +134,10 @@ generation cost, and the balance is the only warning before a run fails with
|
|
|
192
134
|
- **401** / **token missing** (non-OpenClaw): set `PRIV_TOKEN`.
|
|
193
135
|
- **Business `code != 0`**: read `msg` on stderr.
|
|
194
136
|
- **429**: rate-limited; retry later.
|
|
195
|
-
- **Generation timeout**:
|
|
196
|
-
- **
|
|
137
|
+
- **Generation timeout**: polling caps at ~10 minutes; use `--check-status --generation-id <id>` later.
|
|
138
|
+
- **Speech/audio under 5 s**: lengthen the text or audio.
|
|
197
139
|
- **Invalid voice id**: re-check via `gen-voice --list-voices`.
|
|
198
140
|
- **Avatar id not found**: re-check via `--list-avatars` (or `--list-avatars --mine`).
|
|
199
|
-
- **
|
|
141
|
+
- **Avatar still cloning / clone failed**: poll `--clone-status --avatar-id <id>`; on failure, re-clone with a clearer source (single speaker facing the camera, h264 mp4).
|
|
142
|
+
- **User asks for a different aspect ratio or gestures**: not supported at generation time — explain that the ratio follows the cloned source, and offer to clone from footage in the wanted ratio.
|
|
200
143
|
- **Network**: verify connectivity and `MM_API_BASE_URL`.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"tier": "atomic",
|
|
5
5
|
"category": "asset",
|
|
6
6
|
"title": "Digital-Human Talking-Head",
|
|
7
|
-
"description": "Digital-human video: list
|
|
7
|
+
"description": "Digital-human video (HiFly): list or clone avatars; produce a talking-head video from text via TTS, or drive an avatar from an existing audio URL. Aspect ratio follows the avatar's cloned source and cannot be chosen.",
|
|
8
8
|
"auth": "required",
|
|
9
9
|
"joinsTake": true,
|
|
10
10
|
"envVars": [
|
|
@@ -29,15 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"name": {
|
|
31
31
|
"type": "string",
|
|
32
|
-
"description": "With list_avatars=true: fuzzy-filter avatars by name"
|
|
33
|
-
},
|
|
34
|
-
"source": {
|
|
35
|
-
"type": "string",
|
|
36
|
-
"enum": [
|
|
37
|
-
"jimeng",
|
|
38
|
-
"hifly"
|
|
39
|
-
],
|
|
40
|
-
"description": "Provider: jimeng is image-driven, hifly is video-driven. Usually inferred from the avatar; pass it explicitly when the avatar declares no source."
|
|
32
|
+
"description": "With list_avatars=true: fuzzy-filter avatars by name. With clone=true: the new avatar's name (required)."
|
|
41
33
|
},
|
|
42
34
|
"gender": {
|
|
43
35
|
"type": "string",
|
|
@@ -48,33 +40,53 @@
|
|
|
48
40
|
"description": "Filter by gender"
|
|
49
41
|
},
|
|
50
42
|
"avatar_id": {
|
|
51
|
-
"type": "
|
|
52
|
-
"description": "Avatar id"
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Avatar id as returned by list_avatars (opaque string like dh_xxx). Also used with clone_status=true."
|
|
45
|
+
},
|
|
46
|
+
"clone": {
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"description": "Clone mode: create a custom HiFly avatar from a video (free) or a portrait image (costs HiFly credits). Requires clone_url and name. Waits until cloning finishes unless wait=false."
|
|
49
|
+
},
|
|
50
|
+
"clone_url": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "With clone=true: public http(s) URL of the source. Video: mp4/mov (h264), 5s-30min, 360p-4K, ≤500MB, one clearly visible speaking person. Image: a front-facing portrait."
|
|
53
|
+
},
|
|
54
|
+
"clone_type": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"enum": [
|
|
57
|
+
"video",
|
|
58
|
+
"image"
|
|
59
|
+
],
|
|
60
|
+
"description": "With clone=true: source type. Defaults to video."
|
|
61
|
+
},
|
|
62
|
+
"thumbnail_url": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "With clone=true: optional cover image URL shown in avatar lists (image clones default to the image itself). The output aspect ratio always follows the cloned source; it cannot be chosen at generation time."
|
|
65
|
+
},
|
|
66
|
+
"wait": {
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"description": "With clone=true: set false to return right after submission (then poll with clone_status=true). Defaults to waiting up to ~20 minutes."
|
|
69
|
+
},
|
|
70
|
+
"clone_status": {
|
|
71
|
+
"type": "boolean",
|
|
72
|
+
"description": "Clone-status mode: report cloning / done / failed for avatar_id."
|
|
53
73
|
},
|
|
54
74
|
"text": {
|
|
55
75
|
"type": "string",
|
|
56
|
-
"description": "Narration text (TTS mode)"
|
|
76
|
+
"description": "Narration text (TTS mode, ≤ 10000 chars). Synthesized with voice_id, then drives the avatar; the speech must last at least 5 s. Ignored when audio_url is given."
|
|
57
77
|
},
|
|
58
78
|
"audio_url": {
|
|
59
79
|
"type": "string",
|
|
60
|
-
"description": "Audio
|
|
80
|
+
"description": "Audio-driven mode: public http(s) URL of mp3 / m4a / wav, 5 s – 30 min, ≤ 100 MB. Takes precedence over text / voice_id."
|
|
61
81
|
},
|
|
62
82
|
"voice_id": {
|
|
63
83
|
"type": "string",
|
|
64
|
-
"description": "Voice id (TTS mode). Shares the
|
|
84
|
+
"description": "Voice id (TTS mode). Shares the catalog with gen-voice (including cloned voices) — call gen_voice with list_voices=true to see available ids rather than inventing one."
|
|
65
85
|
},
|
|
66
86
|
"voice_name": {
|
|
67
87
|
"type": "string",
|
|
68
88
|
"description": "Voice display name, recorded alongside voice_id for bookkeeping. Does not affect synthesis."
|
|
69
89
|
},
|
|
70
|
-
"aspect_ratio": {
|
|
71
|
-
"type": "string",
|
|
72
|
-
"description": "Aspect ratio: 9:16 / 16:9 / 3:4 / 1:1. Defaults to the avatar's own ratio."
|
|
73
|
-
},
|
|
74
|
-
"prompt": {
|
|
75
|
-
"type": "string",
|
|
76
|
-
"description": "Action prompt describing how the avatar should perform, e.g. 'more hand gestures'"
|
|
77
|
-
},
|
|
78
90
|
"check_status": {
|
|
79
91
|
"type": "boolean",
|
|
80
92
|
"description": "Status-check mode: poll an earlier job instead of starting a new one. Requires generation_id. Use this when a generate call timed out."
|
|
@@ -85,7 +97,7 @@
|
|
|
85
97
|
},
|
|
86
98
|
"json_output": {
|
|
87
99
|
"type": "boolean",
|
|
88
|
-
"description": "Emit a JSON result (generate: { url, generationId }; list: { avatars }; check-status: { status, url }) instead of human-readable output"
|
|
100
|
+
"description": "Emit a JSON result (generate: { url, generationId }; list: { avatars }; check-status: { status, url }; clone / clone-status: { avatarId, cloneStatus }) instead of human-readable output"
|
|
89
101
|
}
|
|
90
102
|
},
|
|
91
103
|
"required": []
|
|
@@ -93,18 +105,20 @@
|
|
|
93
105
|
"ui": {
|
|
94
106
|
"primary": [
|
|
95
107
|
"avatar_id",
|
|
96
|
-
"text"
|
|
97
|
-
],
|
|
98
|
-
"advanced": [
|
|
99
|
-
"source",
|
|
108
|
+
"text",
|
|
100
109
|
"voice_id",
|
|
101
|
-
"aspect_ratio",
|
|
102
|
-
"prompt",
|
|
103
110
|
"audio_url"
|
|
104
111
|
],
|
|
112
|
+
"advanced": [],
|
|
105
113
|
"hidden": [
|
|
106
114
|
"json_output",
|
|
107
115
|
"list_avatars",
|
|
116
|
+
"clone",
|
|
117
|
+
"clone_url",
|
|
118
|
+
"clone_type",
|
|
119
|
+
"thumbnail_url",
|
|
120
|
+
"wait",
|
|
121
|
+
"clone_status",
|
|
108
122
|
"mine",
|
|
109
123
|
"name",
|
|
110
124
|
"gender",
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "gen-digital-human",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "336",
|
|
5
|
-
"version": "
|
|
6
|
-
"skillDescription": "Digital-human (talking-head) skill: list
|
|
5
|
+
"version": "V9",
|
|
6
|
+
"skillDescription": "Digital-human (talking-head) skill backed by HiFly: list avatars, clone a custom avatar from a video or photo, drive an avatar with text (TTS) or an existing audio URL, and check the status of pending jobs.\n\nUse this skill immediately whenever the user asks for any of:\n- Digital human, talking-head video, AI presenter, virtual host\n- \"Make a talking-head video out of this script / this audio file\"\n- List or browse digital-human avatars\n- Clone / create a custom avatar from a video or photo\n\nEven without an explicit \"use AI\", any request that turns text or audio into a synthesized presenter video should route here."
|
|
7
7
|
}
|