hyper-animator-codex 0.8.0 → 0.8.3
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/README.md +30 -13
- package/bin/hyper-animator-codex.mjs +4 -4
- package/lib/install-skill.mjs +49 -20
- package/lib/minimax-config.mjs +5 -9
- package/package.json +10 -2
- package/skills/hyper-animator-codex/SKILL.md +33 -25
- package/skills/hyper-animator-codex/agents/openai.yaml +2 -2
- package/skills/hyper-animator-codex/contracts/shot-plan.schema.json +113 -7
- package/skills/hyper-animator-codex/contracts/workflow-policy.json +21 -3
- package/skills/hyper-animator-codex/references/examples/shot-plan-dual-catalog.json +41 -5
- package/skills/hyper-animator-codex/references/hyperframes-intent-workflow.md +20 -10
- package/skills/hyper-animator-codex/references/minimax-music-workflow.md +5 -7
- package/skills/hyper-animator-codex/references/narration-audio-workflow.md +20 -14
- package/skills/hyper-animator-codex/references/preview-controls-workflow.md +4 -2
- package/skills/hyper-animator-codex/references/shotcraft/motion-pack/README.md +32 -0
- package/skills/hyper-animator-codex/references/shotcraft/motion-pack/shotcraft-motion-pack.js +178 -0
- package/skills/hyper-animator-codex/scripts/generate_minimax_music.mjs +11 -6
- package/skills/hyper-animator-codex/scripts/generate_minimax_tts.mjs +20 -8
- package/skills/hyper-animator-codex/scripts/git_checkpoint.mjs +29 -3
- package/skills/hyper-animator-codex/scripts/inject_preview_controls.mjs +37 -1
- package/skills/hyper-animator-codex/scripts/minimax_runtime_config.mjs +5 -9
- package/skills/hyper-animator-codex/scripts/query_shotcraft.mjs +10 -1
- package/skills/hyper-animator-codex/scripts/shot_plan_schema_validator.mjs +3 -0
- package/skills/hyper-animator-codex/scripts/validate_hyperframes_html.py +122 -36
- package/skills/hyper-animator-codex/scripts/validate_shot_plan.mjs +110 -17
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"generated_narration": {
|
|
6
6
|
"provider": "minimax_cn",
|
|
7
7
|
"endpoint": "https://api.minimaxi.com/v1/t2a_v2",
|
|
8
|
+
"stage_after": "visual_confirmation",
|
|
8
9
|
"backup_endpoints": [
|
|
9
10
|
"https://api-bj.minimaxi.com/v1/t2a_v2"
|
|
10
11
|
],
|
|
@@ -26,10 +27,14 @@
|
|
|
26
27
|
],
|
|
27
28
|
"forbidden_commands": [
|
|
28
29
|
"hyperframes auth login",
|
|
29
|
-
"hyperframes tts"
|
|
30
|
+
"hyperframes tts",
|
|
31
|
+
"python3 -m pip install librosa",
|
|
32
|
+
"pip3 install librosa",
|
|
33
|
+
"pip install librosa"
|
|
30
34
|
],
|
|
31
35
|
"automatic_fallback": false,
|
|
32
36
|
"automatic_runtime_install": false,
|
|
37
|
+
"automatic_dependency_install": false,
|
|
33
38
|
"failure_choices": [
|
|
34
39
|
"retry_minimax_cn",
|
|
35
40
|
"use_user_local_file",
|
|
@@ -42,14 +47,20 @@
|
|
|
42
47
|
},
|
|
43
48
|
"model_routing": {
|
|
44
49
|
"page_animation_authoring": {
|
|
45
|
-
"
|
|
50
|
+
"tier": "high_quality",
|
|
51
|
+
"preferred_models": {
|
|
52
|
+
"codex": "gpt-5.6-sol"
|
|
53
|
+
},
|
|
46
54
|
"use_for": [
|
|
47
55
|
"page_generation",
|
|
48
56
|
"animation_authoring"
|
|
49
57
|
]
|
|
50
58
|
},
|
|
51
59
|
"planning_and_validation": {
|
|
52
|
-
"
|
|
60
|
+
"tier": "fast",
|
|
61
|
+
"preferred_models": {
|
|
62
|
+
"codex": "gpt-5.6-terra"
|
|
63
|
+
},
|
|
53
64
|
"use_for": [
|
|
54
65
|
"requirements",
|
|
55
66
|
"file_detection",
|
|
@@ -69,6 +80,13 @@
|
|
|
69
80
|
"default_statuses": [
|
|
70
81
|
"implemented"
|
|
71
82
|
],
|
|
83
|
+
"motion_pack_path": "references/shotcraft/motion-pack/README.md",
|
|
84
|
+
"motion_pack_snippet": "references/shotcraft/motion-pack/shotcraft-motion-pack.js",
|
|
85
|
+
"prefer_motion_pack_before_freehand": true,
|
|
86
|
+
"motion_pack_required_for_supported_refs": true,
|
|
87
|
+
"motion_contract_required": true,
|
|
88
|
+
"minimum_commitments_per_shot": 1,
|
|
89
|
+
"read_selected_cards_before_html": true,
|
|
72
90
|
"excluded_asset_extensions": [
|
|
73
91
|
".mp4",
|
|
74
92
|
".mov",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schema_version": "1.
|
|
2
|
+
"schema_version": "1.1.0",
|
|
3
|
+
"workflow_stage": "visual_draft",
|
|
3
4
|
"generation_mode": "generate_new_hyperframes_html",
|
|
4
5
|
"catalog_strategy": "dual_catalog",
|
|
5
6
|
"dimensions": {
|
|
@@ -9,8 +10,7 @@
|
|
|
9
10
|
"duration_seconds": 12,
|
|
10
11
|
"audio": {
|
|
11
12
|
"narration": {
|
|
12
|
-
"mode": "
|
|
13
|
-
"provider": "minimax_cn"
|
|
13
|
+
"mode": "deferred"
|
|
14
14
|
},
|
|
15
15
|
"bgm": {
|
|
16
16
|
"mode": "generated",
|
|
@@ -18,8 +18,14 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"model_routing": {
|
|
21
|
-
"page_animation_authoring":
|
|
22
|
-
|
|
21
|
+
"page_animation_authoring": {
|
|
22
|
+
"tier": "high_quality",
|
|
23
|
+
"actual_model": "gpt-5.6-sol"
|
|
24
|
+
},
|
|
25
|
+
"planning_and_validation": {
|
|
26
|
+
"tier": "fast",
|
|
27
|
+
"actual_model": "gpt-5.6-terra"
|
|
28
|
+
}
|
|
23
29
|
},
|
|
24
30
|
"shots": [
|
|
25
31
|
{
|
|
@@ -33,6 +39,16 @@
|
|
|
33
39
|
"shotcraft_refs": [
|
|
34
40
|
"scene-locked-title"
|
|
35
41
|
],
|
|
42
|
+
"motion_contract": {
|
|
43
|
+
"motion_preset": "none",
|
|
44
|
+
"energy": "cinematic",
|
|
45
|
+
"camera": "slow crane rise from a title detail to the full product stage",
|
|
46
|
+
"transition": "full-frame light wipe into the proof shot",
|
|
47
|
+
"shotcraft_commitments": [
|
|
48
|
+
"keep the title locked and readable before the camera travel opens the scene"
|
|
49
|
+
],
|
|
50
|
+
"readable_rest_seconds": 0.6
|
|
51
|
+
},
|
|
36
52
|
"narration_scene": "1"
|
|
37
53
|
},
|
|
38
54
|
{
|
|
@@ -46,6 +62,16 @@
|
|
|
46
62
|
"shotcraft_refs": [
|
|
47
63
|
"ai-stream-response"
|
|
48
64
|
],
|
|
65
|
+
"motion_contract": {
|
|
66
|
+
"motion_preset": "none",
|
|
67
|
+
"energy": "high",
|
|
68
|
+
"camera": "locked readable screen with short punch-ins on key rows",
|
|
69
|
+
"transition": "snap to the final proof state on a clear beat",
|
|
70
|
+
"shotcraft_commitments": [
|
|
71
|
+
"stream response rows progressively instead of fading static copy"
|
|
72
|
+
],
|
|
73
|
+
"readable_rest_seconds": 0.5
|
|
74
|
+
},
|
|
49
75
|
"narration_scene": "2"
|
|
50
76
|
},
|
|
51
77
|
{
|
|
@@ -59,6 +85,16 @@
|
|
|
59
85
|
"shotcraft_refs": [
|
|
60
86
|
"ui-strip-away-outro"
|
|
61
87
|
],
|
|
88
|
+
"motion_contract": {
|
|
89
|
+
"motion_preset": "none",
|
|
90
|
+
"energy": "cinematic",
|
|
91
|
+
"camera": "wide resolve with a final centered brand hold",
|
|
92
|
+
"transition": "strip away interface fragments into the outro mark",
|
|
93
|
+
"shotcraft_commitments": [
|
|
94
|
+
"resolve dense motion into a clean brand hold for at least one second"
|
|
95
|
+
],
|
|
96
|
+
"readable_rest_seconds": 1
|
|
97
|
+
},
|
|
62
98
|
"narration_scene": "3"
|
|
63
99
|
}
|
|
64
100
|
],
|
|
@@ -19,23 +19,28 @@
|
|
|
19
19
|
Transition、可选 Outro,以及每个镜头的 ShotCraft 参考。
|
|
20
20
|
6. 判断 generation mode:组装现有 catalog,还是生成新 HTML。
|
|
21
21
|
7. 调用 `AskUserQuestion` 第二轮澄清样式、动效、候选取舍,以及 generation mode。
|
|
22
|
-
8.
|
|
23
|
-
|
|
22
|
+
8. 写出 `workflow_stage: visual_draft` 的 shot plan。每个镜头必须包含
|
|
23
|
+
`motion_contract.motion_preset` 和 `motion_contract.shotcraft_commitments`,
|
|
24
|
+
把 ShotCraft 卡片的镜头、转场、节奏和避坑要求变成可验证条目。若最终需要
|
|
25
|
+
旁白,此阶段使用 `audio.narration.mode: deferred`。
|
|
26
|
+
9. 运行 `scripts/validate_shot_plan.mjs shot-plan.json`。
|
|
27
|
+
10. 若选择 `assemble_existing_catalog_items`,生成 wrapper composition,并解析 component paste 指令为真实 snippet。
|
|
28
|
+
11. 若选择 `generate_new_hyperframes_html`,按
|
|
24
29
|
`references/HyperFrames-AI-Generation-Patterns-codex.md` 生成完整
|
|
25
30
|
block/component HTML;ShotCraft TSX 只作为动效参考,不引入 Remotion
|
|
26
31
|
运行时。
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
14. 渲染视频。
|
|
32
|
+
12. 运行 pre-render quality gates。
|
|
33
|
+
13. 展示方案摘要和 HTML preview,让用户确认页面、转场和动画。
|
|
34
|
+
14. 视觉确认后再生成旁白、字幕并按实际时长更新 shot plan。
|
|
35
|
+
15. 根据反馈修订。
|
|
36
|
+
16. 渲染视频。
|
|
33
37
|
|
|
34
38
|
## Intent Profile
|
|
35
39
|
|
|
36
40
|
```json
|
|
37
41
|
{
|
|
38
42
|
"rawRequest": "",
|
|
43
|
+
"workflowStage": "visual_draft",
|
|
39
44
|
"purpose": "product_launch",
|
|
40
45
|
"format": "landscape_16_9",
|
|
41
46
|
"generationMode": "generate_new_hyperframes_html",
|
|
@@ -136,6 +141,8 @@ score =
|
|
|
136
141
|
}
|
|
137
142
|
```
|
|
138
143
|
|
|
144
|
+
Round 2 不选择 voice/emotion,也不生成字幕。旁白选择放到视觉确认后。
|
|
145
|
+
|
|
139
146
|
## HTML Generation Plan
|
|
140
147
|
|
|
141
148
|
生成 HTML 前输出 plan:
|
|
@@ -149,6 +156,7 @@ score =
|
|
|
149
156
|
{"id": "logo-outro", "role": "outro_reference"}
|
|
150
157
|
],
|
|
151
158
|
"patternsRequired": true,
|
|
159
|
+
"audio": {"narration": {"mode": "deferred"}},
|
|
152
160
|
"assumptions": ["用户未提供 logo,先使用文字 logo 占位"],
|
|
153
161
|
"nextValidation": "请确认风格、文案、时长和画幅。"
|
|
154
162
|
}
|
|
@@ -176,9 +184,11 @@ score =
|
|
|
176
184
|
|
|
177
185
|
## Model Routing
|
|
178
186
|
|
|
179
|
-
- `
|
|
187
|
+
- 页面生成和动画编写使用当前客户端的 `high_quality` 层级模型;
|
|
188
|
+
Codex 优先 `gpt-5.6-sol`。
|
|
180
189
|
- 需求抽取、文件检测、catalog 查询、镜头计划、音频提示词、校验和打包
|
|
181
|
-
|
|
190
|
+
使用当前客户端的 `fast` 层级模型;Codex 优先 `gpt-5.6-terra`。
|
|
191
|
+
- 在 shot plan 中记录层级和实际模型名;Claude Code 不要伪填 GPT 型号。
|
|
182
192
|
- 不为模型路由自动新增 Agent 或其他实体;在运行环境支持模型选择时才
|
|
183
193
|
应用路由。
|
|
184
194
|
|
|
@@ -26,17 +26,15 @@ The installed config shape is:
|
|
|
26
26
|
```json
|
|
27
27
|
{
|
|
28
28
|
"api_key": "[redacted]",
|
|
29
|
-
"
|
|
30
|
-
"model": "music-2.6-free"
|
|
29
|
+
"model": "music-3.0"
|
|
31
30
|
}
|
|
32
31
|
```
|
|
33
32
|
|
|
34
|
-
Do not print or echo the raw API key. `group_id` is
|
|
33
|
+
Do not print or echo the raw API key. `group_id` is optional legacy account
|
|
34
|
+
metadata and is not required by the current `/v1/music_generation` call.
|
|
35
35
|
|
|
36
|
-
The wrapper accepts `music-3.0`, `music-
|
|
37
|
-
|
|
38
|
-
`music-3.0` explicitly when the account has access and the user wants the
|
|
39
|
-
current model.
|
|
36
|
+
The wrapper accepts `music-3.0`, `music-3.0-free`, `music-2.6`, and
|
|
37
|
+
`music-2.6-free`. The installer default is `music-3.0`.
|
|
40
38
|
|
|
41
39
|
## Clarify Music
|
|
42
40
|
|
|
@@ -15,19 +15,23 @@ Rules:
|
|
|
15
15
|
- scan only the current directory top level;
|
|
16
16
|
- trust fixed names first: `outline.*` and `narration.*`;
|
|
17
17
|
- if multiple candidates are returned, ask the user to choose;
|
|
18
|
-
- if only outline exists,
|
|
19
|
-
- if only narration exists,
|
|
18
|
+
- if only outline exists, record narration as deferred until visual approval;
|
|
19
|
+
- if only narration exists, use it as content context but do not generate audio or subtitles yet;
|
|
20
20
|
- if narration is not used, do not generate voice audio or subtitles.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
The coding agent writes or edits outline/narration content. Scripts only validate, convert, call MiniMax, and write artifacts.
|
|
23
|
+
|
|
24
|
+
Narration is a post-visual-confirmation stage. Do not generate MiniMax TTS,
|
|
25
|
+
voice lists, or subtitles until the user has approved the page, transition,
|
|
26
|
+
and animation direction. A visual draft shot plan should use
|
|
27
|
+
`audio.narration.mode: "deferred"` when narration is requested.
|
|
23
28
|
|
|
24
29
|
## Provider Boundary
|
|
25
30
|
|
|
26
31
|
Generated narration and BGM use MiniMax mainland China (`minimax_cn`) only.
|
|
27
32
|
Never run `hyperframes auth login` or `hyperframes tts`; never invoke HeyGen,
|
|
28
|
-
Kokoro, or MusicGen; and never install a fallback runtime/model
|
|
29
|
-
|
|
30
|
-
not needed.
|
|
33
|
+
Kokoro, or MusicGen; and never install a fallback runtime/model or beat
|
|
34
|
+
analysis dependency during a run. Do not add a Kokoro runtime or component.
|
|
31
35
|
|
|
32
36
|
Before a generated-audio call, validate the route:
|
|
33
37
|
|
|
@@ -48,7 +52,7 @@ Use this shape:
|
|
|
48
52
|
|
|
49
53
|
```json
|
|
50
54
|
{
|
|
51
|
-
"voice": "
|
|
55
|
+
"voice": "male-qn-qingse",
|
|
52
56
|
"emotion": "calm",
|
|
53
57
|
"language": "zh",
|
|
54
58
|
"style": "casual",
|
|
@@ -121,15 +125,17 @@ Subtitle rules:
|
|
|
121
125
|
|
|
122
126
|
## BGM, Beat, And Schedule
|
|
123
127
|
|
|
124
|
-
BGM is the timing source when present
|
|
128
|
+
BGM is the timing source when present, but narration still waits for visual
|
|
129
|
+
approval:
|
|
125
130
|
|
|
126
131
|
1. decide BGM generation, local import, or skip;
|
|
127
|
-
2. if
|
|
128
|
-
3. analyze the music with `scripts/analyze_music_beats.py
|
|
129
|
-
4.
|
|
130
|
-
5.
|
|
131
|
-
6. preview and get
|
|
132
|
-
7.
|
|
132
|
+
2. if BGM is needed for beat-sync, generate BGM with `scripts/generate_minimax_music.mjs`;
|
|
133
|
+
3. analyze the music with `scripts/analyze_music_beats.py` only when its dependencies are already available;
|
|
134
|
+
4. if beat analysis dependencies are missing, do not run `pip install`; ask for a beat map, continue with explicit manual cues, or skip beat sync;
|
|
135
|
+
5. schedule transitions and animation against the beat map or manual cues;
|
|
136
|
+
6. generate the visual HTML preview and get page/transition/animation confirmation;
|
|
137
|
+
7. generate narration and subtitles, then reconcile timing;
|
|
138
|
+
8. render and mix final video/audio after final approval.
|
|
133
139
|
|
|
134
140
|
Default mix:
|
|
135
141
|
|
|
@@ -4,7 +4,7 @@ Use this whenever a generated or assembled HyperFrames HTML animation will be sh
|
|
|
4
4
|
|
|
5
5
|
## Required Behavior
|
|
6
6
|
|
|
7
|
-
HTML previews should include a bottom page indicator and thin progress bar.
|
|
7
|
+
HTML previews should include a bottom page indicator, play/pause button, and thin progress bar. The preview copy must start the registered timeline in interactive mode; users can pause/resume, drag the progress bar to seek, or press ArrowLeft and ArrowRight to move between pages. Never show the paused base composition as the interactive preview.
|
|
8
8
|
|
|
9
9
|
Video renders must hide those controls. Use one of these render-hidden signals:
|
|
10
10
|
|
|
@@ -12,6 +12,8 @@ Video renders must hide those controls. Use one of these render-hidden signals:
|
|
|
12
12
|
- `?preview=0`
|
|
13
13
|
- `<html data-render-mode="video">`
|
|
14
14
|
|
|
15
|
+
The main render timeline should remain `paused: true` for deterministic frame capture. Preview controls are responsible for calling `timeline.play()`/`timeline.pause()` and must skip autoplay when any render-hidden signal is active.
|
|
16
|
+
|
|
15
17
|
## Page Model
|
|
16
18
|
|
|
17
19
|
Prefer explicit page starts:
|
|
@@ -34,7 +36,7 @@ After the base HTML passes normal quality gates, create a preview copy:
|
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
38
|
node scripts/inject_preview_controls.mjs composition.html -o composition.preview.html --force
|
|
37
|
-
python3 scripts/validate_hyperframes_html.py composition.preview.html --preview-controls
|
|
39
|
+
python3 scripts/validate_hyperframes_html.py composition.preview.html --preview-controls --shot-plan shot-plan.json
|
|
38
40
|
```
|
|
39
41
|
|
|
40
42
|
Show the preview copy to the user for validation.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ShotCraft motion pack
|
|
2
|
+
|
|
3
|
+
Use this pack before freehanding animation for the supported ShotCraft cards.
|
|
4
|
+
The goal is to copy the card's actual motion grammar into GSAP/HyperFrames
|
|
5
|
+
instead of merely describing it in prose.
|
|
6
|
+
|
|
7
|
+
| ShotCraft reference | `motion_contract.motion_preset` | Helper |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `slam-entrance-moves` | `slam-entrance` | `slamEntrance` |
|
|
10
|
+
| `shot-transitions` | `whip-transition` | `whipTransition` |
|
|
11
|
+
| `crash-zoom-punch` | `crash-zoom` | `crashZoom` |
|
|
12
|
+
| `bottom-push-stack-wipe` | `push-stack-wipe` | `pushStackWipe` |
|
|
13
|
+
| `dataviz-landscape-open` | `dataviz-landscape-open` | `datavizLandscapeOpen` |
|
|
14
|
+
| `panel-grid-moves` | `panel-grid-reflow` | `panelGridReflow` |
|
|
15
|
+
| `ui-to-brand-morph` | `brand-resolve` | `brandResolve` |
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
|
|
19
|
+
1. Read the selected ShotCraft card README first.
|
|
20
|
+
2. Set the matching `motion_contract.motion_preset` in the shot plan.
|
|
21
|
+
3. Copy only the needed helper from `shotcraft-motion-pack.js` into the
|
|
22
|
+
generated HTML, or include the file as a local reference when the project
|
|
23
|
+
allows that.
|
|
24
|
+
4. Keep the helper on the main paused GSAP timeline; do not run wall-clock
|
|
25
|
+
timers, React, Remotion, or ShotCraft runtime code.
|
|
26
|
+
|
|
27
|
+
Quality bar:
|
|
28
|
+
|
|
29
|
+
- motion starts with a visible camera/geometry event, not a fade;
|
|
30
|
+
- transition covers the cut with a real mask, whip, impact, or spatial travel;
|
|
31
|
+
- dense motion resolves into at least `readable_rest_seconds` of rest;
|
|
32
|
+
- every helper call is tied to a named shot boundary or beat cue.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Copy the needed helpers into a generated HyperFrames HTML file.
|
|
2
|
+
// Requires GSAP and a paused main timeline.
|
|
3
|
+
(function registerShotCraftMotion(global) {
|
|
4
|
+
function gsapRef() {
|
|
5
|
+
if (!global.gsap) throw new Error("GSAP is required for ShotCraft motion helpers");
|
|
6
|
+
return global.gsap;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function slamEntrance(tl, target, cue = 0, opts = {}) {
|
|
10
|
+
const gsap = gsapRef();
|
|
11
|
+
const duration = opts.duration ?? 0.72;
|
|
12
|
+
tl.set(target, { transformPerspective: 900, transformOrigin: "50% 55%" }, cue)
|
|
13
|
+
.fromTo(target, {
|
|
14
|
+
autoAlpha: 0,
|
|
15
|
+
scale: 2.4,
|
|
16
|
+
z: 420,
|
|
17
|
+
rotateX: -18,
|
|
18
|
+
filter: "blur(14px) brightness(1.8)",
|
|
19
|
+
}, {
|
|
20
|
+
autoAlpha: 1,
|
|
21
|
+
scale: 0.92,
|
|
22
|
+
z: 0,
|
|
23
|
+
rotateX: 0,
|
|
24
|
+
filter: "blur(0px) brightness(1)",
|
|
25
|
+
duration: duration * 0.58,
|
|
26
|
+
ease: "expo.out",
|
|
27
|
+
}, cue)
|
|
28
|
+
.to(target, { scale: 1.04, duration: duration * 0.16, ease: "power2.out" }, cue + duration * 0.42)
|
|
29
|
+
.to(target, { scale: 1, duration: duration * 0.26, ease: "back.out(2.4)" }, cue + duration * 0.56);
|
|
30
|
+
if (opts.ring) {
|
|
31
|
+
tl.fromTo(opts.ring, { autoAlpha: 0.9, scale: 0.12 }, {
|
|
32
|
+
autoAlpha: 0,
|
|
33
|
+
scale: 2.8,
|
|
34
|
+
duration: duration * 0.72,
|
|
35
|
+
ease: "expo.out",
|
|
36
|
+
}, cue + 0.04);
|
|
37
|
+
}
|
|
38
|
+
return tl;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function whipTransition(tl, fromTarget, toTarget, cue = 0, opts = {}) {
|
|
42
|
+
const distance = opts.distance ?? 260;
|
|
43
|
+
const blur = opts.blur ?? 18;
|
|
44
|
+
tl.to(fromTarget, {
|
|
45
|
+
x: -distance,
|
|
46
|
+
rotateZ: -3,
|
|
47
|
+
filter: `blur(${blur}px)`,
|
|
48
|
+
autoAlpha: 0,
|
|
49
|
+
duration: 0.2,
|
|
50
|
+
ease: "power4.in",
|
|
51
|
+
}, cue)
|
|
52
|
+
.fromTo(toTarget, {
|
|
53
|
+
x: distance,
|
|
54
|
+
rotateZ: 3,
|
|
55
|
+
filter: `blur(${blur}px)`,
|
|
56
|
+
autoAlpha: 0,
|
|
57
|
+
}, {
|
|
58
|
+
x: 0,
|
|
59
|
+
rotateZ: 0,
|
|
60
|
+
filter: "blur(0px)",
|
|
61
|
+
autoAlpha: 1,
|
|
62
|
+
duration: 0.34,
|
|
63
|
+
ease: "expo.out",
|
|
64
|
+
}, cue + 0.16);
|
|
65
|
+
if (opts.flash) tl.fromTo(opts.flash, { autoAlpha: 0 }, { autoAlpha: 1, yoyo: true, repeat: 1, duration: 0.05 }, cue + 0.14);
|
|
66
|
+
return tl;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function crashZoom(tl, target, cue = 0, opts = {}) {
|
|
70
|
+
tl.fromTo(target, {
|
|
71
|
+
scale: opts.fromScale ?? 0.72,
|
|
72
|
+
filter: "contrast(1.3) saturate(1.25)",
|
|
73
|
+
}, {
|
|
74
|
+
scale: opts.overshoot ?? 1.18,
|
|
75
|
+
duration: 0.16,
|
|
76
|
+
ease: "power4.in",
|
|
77
|
+
}, cue)
|
|
78
|
+
.to(target, { scale: 0.98, duration: 0.12, ease: "power2.out" }, cue + 0.16)
|
|
79
|
+
.to(target, { scale: 1, filter: "contrast(1) saturate(1)", duration: 0.28, ease: "expo.out" }, cue + 0.28);
|
|
80
|
+
if (opts.shock) tl.fromTo(opts.shock, { autoAlpha: 0.85, scale: 0.2 }, { autoAlpha: 0, scale: 2.2, duration: 0.34, ease: "expo.out" }, cue + 0.12);
|
|
81
|
+
return tl;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function pushStackWipe(tl, fromTarget, toTarget, cue = 0, opts = {}) {
|
|
85
|
+
const height = opts.height ?? "112%";
|
|
86
|
+
tl.fromTo(toTarget, { y: height, autoAlpha: 1 }, {
|
|
87
|
+
y: "0%",
|
|
88
|
+
duration: 0.48,
|
|
89
|
+
ease: "power3.inOut",
|
|
90
|
+
}, cue)
|
|
91
|
+
.to(fromTarget, {
|
|
92
|
+
y: "-18%",
|
|
93
|
+
scale: 0.94,
|
|
94
|
+
filter: "blur(8px)",
|
|
95
|
+
autoAlpha: 0,
|
|
96
|
+
duration: 0.42,
|
|
97
|
+
ease: "power3.inOut",
|
|
98
|
+
}, cue + 0.04)
|
|
99
|
+
.fromTo(opts.shadow ?? toTarget, { boxShadow: "0 -40px 80px rgba(0,0,0,0.35)" }, {
|
|
100
|
+
boxShadow: "0 0 0 rgba(0,0,0,0)",
|
|
101
|
+
duration: 0.36,
|
|
102
|
+
ease: "power2.out",
|
|
103
|
+
}, cue + 0.28);
|
|
104
|
+
return tl;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function datavizLandscapeOpen(tl, bars, labels, cue = 0, opts = {}) {
|
|
108
|
+
tl.fromTo(bars, {
|
|
109
|
+
scaleY: 0,
|
|
110
|
+
transformOrigin: "50% 100%",
|
|
111
|
+
}, {
|
|
112
|
+
scaleY: 1,
|
|
113
|
+
duration: opts.duration ?? 0.86,
|
|
114
|
+
stagger: opts.stagger ?? 0.035,
|
|
115
|
+
ease: "expo.out",
|
|
116
|
+
}, cue)
|
|
117
|
+
.fromTo(labels, { y: 18, autoAlpha: 0 }, {
|
|
118
|
+
y: 0,
|
|
119
|
+
autoAlpha: 1,
|
|
120
|
+
duration: 0.34,
|
|
121
|
+
stagger: opts.stagger ?? 0.035,
|
|
122
|
+
ease: "power2.out",
|
|
123
|
+
}, cue + 0.24);
|
|
124
|
+
return tl;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function panelGridReflow(tl, panels, cue = 0, opts = {}) {
|
|
128
|
+
const gsap = gsapRef();
|
|
129
|
+
const panelList = gsap.utils.toArray(panels);
|
|
130
|
+
panelList.forEach((panel, index) => {
|
|
131
|
+
tl.fromTo(panel, {
|
|
132
|
+
xPercent: index % 2 === 0 ? -18 : 18,
|
|
133
|
+
yPercent: index % 3 === 0 ? -12 : 12,
|
|
134
|
+
rotateZ: index % 2 === 0 ? -3 : 3,
|
|
135
|
+
autoAlpha: 0,
|
|
136
|
+
}, {
|
|
137
|
+
xPercent: 0,
|
|
138
|
+
yPercent: 0,
|
|
139
|
+
rotateZ: 0,
|
|
140
|
+
autoAlpha: 1,
|
|
141
|
+
duration: opts.duration ?? 0.58,
|
|
142
|
+
ease: "expo.out",
|
|
143
|
+
}, cue + index * (opts.stagger ?? 0.045));
|
|
144
|
+
});
|
|
145
|
+
return tl;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function brandResolve(tl, tiles, wordmark, cue = 0, opts = {}) {
|
|
149
|
+
tl.to(tiles, {
|
|
150
|
+
x: 0,
|
|
151
|
+
y: 0,
|
|
152
|
+
rotateZ: 0,
|
|
153
|
+
scale: 0.78,
|
|
154
|
+
duration: opts.gatherDuration ?? 0.54,
|
|
155
|
+
stagger: opts.stagger ?? 0.025,
|
|
156
|
+
ease: "power3.inOut",
|
|
157
|
+
}, cue)
|
|
158
|
+
.to(tiles, { autoAlpha: 0, scale: 0.48, duration: 0.2, ease: "power2.in" }, cue + 0.48)
|
|
159
|
+
.fromTo(wordmark, { autoAlpha: 0, scale: 0.86, filter: "blur(10px)" }, {
|
|
160
|
+
autoAlpha: 1,
|
|
161
|
+
scale: 1,
|
|
162
|
+
filter: "blur(0px)",
|
|
163
|
+
duration: 0.42,
|
|
164
|
+
ease: "expo.out",
|
|
165
|
+
}, cue + 0.54);
|
|
166
|
+
return tl;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
global.HyperAnimatorShotCraftMotion = {
|
|
170
|
+
slamEntrance,
|
|
171
|
+
whipTransition,
|
|
172
|
+
crashZoom,
|
|
173
|
+
pushStackWipe,
|
|
174
|
+
datavizLandscapeOpen,
|
|
175
|
+
panelGridReflow,
|
|
176
|
+
brandResolve,
|
|
177
|
+
};
|
|
178
|
+
})(window);
|
|
@@ -108,7 +108,7 @@ Options:
|
|
|
108
108
|
--format <mp3|wav|pcm> Audio encoding format, default mp3
|
|
109
109
|
--sample-rate <rate> 16000, 24000, 32000, or 44100; default 44100
|
|
110
110
|
--bitrate <bits> 32000, 64000, 128000, or 256000; default 256000
|
|
111
|
-
--model <model> music-3.0, music-2.6, or music-2.6-free
|
|
111
|
+
--model <model> music-3.0, music-3.0-free, music-2.6, or music-2.6-free
|
|
112
112
|
--config <file> Explicit MiniMax config JSON
|
|
113
113
|
--request-json <file> Read request options from a JSON file
|
|
114
114
|
--dry-run Print redacted request without contacting MiniMax
|
|
@@ -270,8 +270,12 @@ function safeFileStem(prompt) {
|
|
|
270
270
|
return stem || "minimax-music";
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
async function downloadUrl(url) {
|
|
274
|
-
const
|
|
273
|
+
async function downloadUrl(url, timeoutMs) {
|
|
274
|
+
const parsed = URL.canParse(url) ? new URL(url) : null;
|
|
275
|
+
if (!parsed || !["http:", "https:"].includes(parsed.protocol)) {
|
|
276
|
+
throw minimaxError("MiniMax audio URL must use HTTP or HTTPS", { kind: "response" });
|
|
277
|
+
}
|
|
278
|
+
const response = await fetchWithTimeout(url, {}, timeoutMs);
|
|
275
279
|
|
|
276
280
|
if (!response.ok) {
|
|
277
281
|
throw new Error(`MiniMax audio URL download failed with HTTP ${response.status}`);
|
|
@@ -429,7 +433,7 @@ async function callMiniMaxWithRetries(config, request, { endpoint, timeoutMs, ma
|
|
|
429
433
|
throw minimaxError("MiniMax request failed", { kind: "network", retryable: true, attempts });
|
|
430
434
|
}
|
|
431
435
|
|
|
432
|
-
async function writeAudioOutput({ responseJson, request, outputDir, prompt, endpoint }) {
|
|
436
|
+
async function writeAudioOutput({ responseJson, request, outputDir, prompt, endpoint, timeoutMs }) {
|
|
433
437
|
await mkdir(outputDir, { recursive: true });
|
|
434
438
|
|
|
435
439
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
@@ -439,7 +443,7 @@ async function writeAudioOutput({ responseJson, request, outputDir, prompt, endp
|
|
|
439
443
|
const metadataPath = join(outputDir, `${stem}.minimax.json`);
|
|
440
444
|
const audioValue = responseJson.data.audio;
|
|
441
445
|
const audioBuffer = request.output_format === "url"
|
|
442
|
-
? await downloadUrl(audioValue)
|
|
446
|
+
? await downloadUrl(audioValue, timeoutMs)
|
|
443
447
|
: decodeHexAudio(audioValue);
|
|
444
448
|
|
|
445
449
|
await writeFile(audioPath, audioBuffer);
|
|
@@ -496,7 +500,7 @@ async function main() {
|
|
|
496
500
|
}
|
|
497
501
|
|
|
498
502
|
if (!runtime) {
|
|
499
|
-
throw Object.assign(new Error("MiniMax config not found. Run hyper-animator-codex install with --minimax-api-key
|
|
503
|
+
throw Object.assign(new Error("MiniMax config not found. Run hyper-animator-codex install with --minimax-api-key, or set MINIMAX_API_KEY."), {
|
|
500
504
|
kind: "config",
|
|
501
505
|
retryable: false,
|
|
502
506
|
});
|
|
@@ -539,6 +543,7 @@ async function main() {
|
|
|
539
543
|
outputDir: options.output_dir,
|
|
540
544
|
prompt: options.prompt,
|
|
541
545
|
endpoint,
|
|
546
|
+
timeoutMs,
|
|
542
547
|
});
|
|
543
548
|
|
|
544
549
|
printJson({
|
|
@@ -14,7 +14,7 @@ const BACKUP_TTS_ENDPOINT = "https://api-bj.minimaxi.com/v1/t2a_v2";
|
|
|
14
14
|
const BACKUP_VOICE_ENDPOINT = "https://api-bj.minimaxi.com/v1/get_voice";
|
|
15
15
|
const PROVIDER = "minimax_cn";
|
|
16
16
|
const DEFAULT_TTS_MODEL = "speech-2.8-hd";
|
|
17
|
-
const DEFAULT_VOICE_ID = "
|
|
17
|
+
const DEFAULT_VOICE_ID = "male-qn-qingse";
|
|
18
18
|
const EMOTIONS = new Set(["happy", "sad", "angry", "fearful", "disgusted", "surprised", "calm", "fluent", "whipser"]);
|
|
19
19
|
const VOICE_TYPES = new Set(["all", "system", "voice_cloning", "voice_generation"]);
|
|
20
20
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -135,12 +135,12 @@ Options:
|
|
|
135
135
|
--narration-json <file> Narration JSON with scenes[].narration
|
|
136
136
|
--request-json <file> Structured TTS request options
|
|
137
137
|
--text <text> Direct text input
|
|
138
|
-
--voice-id <id> MiniMax voice_id, default from narration or
|
|
138
|
+
--voice-id <id> MiniMax voice_id, default from narration or male-qn-qingse
|
|
139
139
|
--emotion <label> happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whipser
|
|
140
140
|
--language <code> zh, en, or MiniMax language_boost value
|
|
141
141
|
--output-dir <dir> Directory for generated audio and metadata
|
|
142
142
|
--model <model> MiniMax speech model, default speech-2.8-hd
|
|
143
|
-
--format <mp3|wav|pcm>
|
|
143
|
+
--format <mp3|wav|pcm|flac> Audio encoding format, default mp3
|
|
144
144
|
--sample-rate <rate> Default 32000
|
|
145
145
|
--bitrate <bits> Default 128000
|
|
146
146
|
--channel <count> Default 1
|
|
@@ -246,14 +246,17 @@ function validateOptions(options) {
|
|
|
246
246
|
if (!options.voice_id || !options.voice_id.trim()) {
|
|
247
247
|
throw minimaxError("voice_id is required", { kind: "validation" });
|
|
248
248
|
}
|
|
249
|
+
if (typeof options.model !== "string" || !/^speech-[a-z0-9.-]+$/i.test(options.model)) {
|
|
250
|
+
throw minimaxError(`Unsupported MiniMax speech model: ${options.model}`, { kind: "validation" });
|
|
251
|
+
}
|
|
249
252
|
if (!EMOTIONS.has(options.emotion)) {
|
|
250
253
|
throw minimaxError(`Unsupported emotion: ${options.emotion}`, { kind: "validation" });
|
|
251
254
|
}
|
|
252
|
-
if (!["mp3", "wav", "pcm"].includes(options.audio_format)) {
|
|
253
|
-
throw minimaxError("--format must be mp3, wav, or
|
|
255
|
+
if (!["mp3", "wav", "pcm", "flac"].includes(options.audio_format)) {
|
|
256
|
+
throw minimaxError("--format must be mp3, wav, pcm, or flac", { kind: "validation" });
|
|
254
257
|
}
|
|
255
|
-
if (![16000, 24000, 32000, 44100].includes(options.sample_rate)) {
|
|
256
|
-
throw minimaxError("--sample-rate must be 16000, 24000, 32000, or 44100", { kind: "validation" });
|
|
258
|
+
if (![8000, 16000, 22050, 24000, 32000, 44100].includes(options.sample_rate)) {
|
|
259
|
+
throw minimaxError("--sample-rate must be 8000, 16000, 22050, 24000, 32000, or 44100", { kind: "validation" });
|
|
257
260
|
}
|
|
258
261
|
if (![32000, 64000, 128000, 256000].includes(options.bitrate)) {
|
|
259
262
|
throw minimaxError("--bitrate must be 32000, 64000, 128000, or 256000", { kind: "validation" });
|
|
@@ -261,6 +264,15 @@ function validateOptions(options) {
|
|
|
261
264
|
if (![1, 2].includes(options.channel)) {
|
|
262
265
|
throw minimaxError("--channel must be 1 or 2", { kind: "validation" });
|
|
263
266
|
}
|
|
267
|
+
if (!Number.isFinite(options.speed) || options.speed < 0.5 || options.speed > 2) {
|
|
268
|
+
throw minimaxError("--speed must be from 0.5 to 2", { kind: "validation" });
|
|
269
|
+
}
|
|
270
|
+
if (!Number.isFinite(options.volume) || options.volume < 0.1 || options.volume > 10) {
|
|
271
|
+
throw minimaxError("--volume must be from 0.1 to 10", { kind: "validation" });
|
|
272
|
+
}
|
|
273
|
+
if (!Number.isInteger(options.pitch) || options.pitch < -12 || options.pitch > 12) {
|
|
274
|
+
throw minimaxError("--pitch must be an integer from -12 to 12", { kind: "validation" });
|
|
275
|
+
}
|
|
264
276
|
}
|
|
265
277
|
|
|
266
278
|
function buildTtsRequest(options) {
|
|
@@ -477,7 +489,7 @@ async function readRuntime(options) {
|
|
|
477
489
|
}
|
|
478
490
|
|
|
479
491
|
if (!runtime) {
|
|
480
|
-
throw minimaxError("MiniMax config not found. Run hyper-animator-codex install with --minimax-api-key
|
|
492
|
+
throw minimaxError("MiniMax config not found. Run hyper-animator-codex install with --minimax-api-key, or set MINIMAX_API_KEY.", {
|
|
481
493
|
kind: "config",
|
|
482
494
|
retryable: false,
|
|
483
495
|
});
|