hyperframes 0.8.57 → 0.8.59
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/cli.js +5300 -3428
- package/dist/docs/data-attributes.md +2 -1
- package/dist/fontLocalizeCli.js +73 -12
- package/dist/hyperframe-runtime.js +25 -25
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +25 -25
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/registry/localSemantic.js +548 -0
- package/dist/skills/hyperframes-cli/SKILL.md +2 -2
- package/dist/skills/media-use/SKILL.md +96 -0
- package/dist/skills/media-use/audio/assets/sfx/CREDITS.md +35 -0
- package/dist/skills/media-use/audio/assets/sfx/chime.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/click-soft.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/click.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/error.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/glitch-1.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/glitch-2.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/glitch-3.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/impact-bass-1.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/impact-bass-2.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/key-press.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/manifest.json +97 -0
- package/dist/skills/media-use/audio/assets/sfx/notification.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/ping.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/pop.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/riser.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/sparkle.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/typing.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/whoosh-cinematic.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/whoosh-short.mp3 +0 -0
- package/dist/skills/media-use/audio/assets/sfx/whoosh.mp3 +0 -0
- package/dist/skills/media-use/audio/references/bgm.md +74 -0
- package/dist/skills/media-use/audio/references/captions/authoring.md +163 -0
- package/dist/skills/media-use/audio/references/captions/motion.md +87 -0
- package/dist/skills/media-use/audio/references/captions/transcript-handling.md +97 -0
- package/dist/skills/media-use/audio/references/remove-background.md +143 -0
- package/dist/skills/media-use/audio/references/requirements.md +29 -0
- package/dist/skills/media-use/audio/references/sfx.md +42 -0
- package/dist/skills/media-use/audio/references/transcribe.md +52 -0
- package/dist/skills/media-use/audio/references/tts-to-captions.md +26 -0
- package/dist/skills/media-use/audio/references/tts.md +165 -0
- package/dist/skills/media-use/audio/scripts/audio.mjs +295 -0
- package/dist/skills/media-use/audio/scripts/audio.test.mjs +49 -0
- package/dist/skills/media-use/audio/scripts/heygen-tts.mjs +121 -0
- package/dist/skills/media-use/audio/scripts/lib/audio-meta.mjs +39 -0
- package/dist/skills/media-use/audio/scripts/lib/audio-meta.test.mjs +136 -0
- package/dist/skills/media-use/audio/scripts/lib/bgm.mjs +257 -0
- package/dist/skills/media-use/audio/scripts/lib/bgm.test.mjs +30 -0
- package/dist/skills/media-use/audio/scripts/lib/concurrency.mjs +14 -0
- package/dist/skills/media-use/audio/scripts/lib/concurrency.test.mjs +41 -0
- package/dist/skills/media-use/audio/scripts/lib/heygen.mjs +156 -0
- package/dist/skills/media-use/audio/scripts/lib/heygen.test.mjs +103 -0
- package/dist/skills/media-use/audio/scripts/lib/python.mjs +63 -0
- package/dist/skills/media-use/audio/scripts/lib/python.test.mjs +68 -0
- package/dist/skills/media-use/audio/scripts/lib/sfx.mjs +143 -0
- package/dist/skills/media-use/audio/scripts/lib/sfx.test.mjs +69 -0
- package/dist/skills/media-use/audio/scripts/lib/tts.mjs +379 -0
- package/dist/skills/media-use/audio/scripts/lib/tts.spawn.test.mjs +158 -0
- package/dist/skills/media-use/audio/scripts/lib/tts.test.mjs +157 -0
- package/dist/skills/media-use/audio/scripts/lyria-recipe.py +136 -0
- package/dist/skills/media-use/audio/scripts/wait-bgm.mjs +171 -0
- package/dist/skills/media-use/audio/scripts/wait-bgm.test.mjs +65 -0
- package/dist/skills/media-use/luts/README.md +28 -0
- package/dist/skills/media-use/luts/index.json +57 -0
- package/dist/skills/media-use/references/audio.md +19 -0
- package/dist/skills/media-use/references/grading.md +160 -0
- package/dist/skills/media-use/references/media-treatment-recipes.md +929 -0
- package/dist/skills/media-use/references/media-treatments.md +202 -0
- package/dist/skills/media-use/references/memory.md +36 -0
- package/dist/skills/media-use/references/meta.md +61 -0
- package/dist/skills/media-use/references/operations.md +325 -0
- package/dist/skills/media-use/references/resolve.md +150 -0
- package/dist/skills/media-use/references/setup-providers.md +80 -0
- package/dist/skills/media-use/references/telemetry-dashboard.md +49 -0
- package/dist/skills/media-use/registry/localSemantic.js +548 -0
- package/dist/skills/media-use/scripts/audio-duck.mjs +113 -0
- package/dist/skills/media-use/scripts/compatibility.test.mjs +27 -0
- package/dist/skills/media-use/scripts/dither.mjs +328 -0
- package/dist/skills/media-use/scripts/dither.test.mjs +176 -0
- package/dist/skills/media-use/scripts/eval.mjs +369 -0
- package/dist/skills/media-use/scripts/lib/adopt.mjs +128 -0
- package/dist/skills/media-use/scripts/lib/adopt.test.mjs +95 -0
- package/dist/skills/media-use/scripts/lib/bgm-provider.mjs +20 -0
- package/dist/skills/media-use/scripts/lib/brand-provider.mjs +59 -0
- package/dist/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +119 -0
- package/dist/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +139 -0
- package/dist/skills/media-use/scripts/lib/cache.mjs +150 -0
- package/dist/skills/media-use/scripts/lib/candidates.mjs +91 -0
- package/dist/skills/media-use/scripts/lib/candidates.test.mjs +155 -0
- package/dist/skills/media-use/scripts/lib/codex-provider.mjs +161 -0
- package/dist/skills/media-use/scripts/lib/codex-provider.test.mjs +34 -0
- package/dist/skills/media-use/scripts/lib/coverage.test.mjs +132 -0
- package/dist/skills/media-use/scripts/lib/cube-build.mjs +181 -0
- package/dist/skills/media-use/scripts/lib/cube-build.test.mjs +80 -0
- package/dist/skills/media-use/scripts/lib/cube-validate.mjs +198 -0
- package/dist/skills/media-use/scripts/lib/cube-validate.test.mjs +125 -0
- package/dist/skills/media-use/scripts/lib/cutlist.mjs +184 -0
- package/dist/skills/media-use/scripts/lib/cutlist.test.mjs +162 -0
- package/dist/skills/media-use/scripts/lib/duck.mjs +95 -0
- package/dist/skills/media-use/scripts/lib/duck.test.mjs +153 -0
- package/dist/skills/media-use/scripts/lib/error-diffusion.mjs +230 -0
- package/dist/skills/media-use/scripts/lib/error-diffusion.test.mjs +102 -0
- package/dist/skills/media-use/scripts/lib/freeze.mjs +62 -0
- package/dist/skills/media-use/scripts/lib/freeze.test.mjs +46 -0
- package/dist/skills/media-use/scripts/lib/grade-analyzer.mjs +265 -0
- package/dist/skills/media-use/scripts/lib/grade-analyzer.test.mjs +285 -0
- package/dist/skills/media-use/scripts/lib/heygen-cli.mjs +191 -0
- package/dist/skills/media-use/scripts/lib/heygen-cli.test.mjs +301 -0
- package/dist/skills/media-use/scripts/lib/heygen-search.mjs +45 -0
- package/dist/skills/media-use/scripts/lib/heygen-search.test.mjs +49 -0
- package/dist/skills/media-use/scripts/lib/heygen-video-provider.mjs +138 -0
- package/dist/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +435 -0
- package/dist/skills/media-use/scripts/lib/image-provider.mjs +44 -0
- package/dist/skills/media-use/scripts/lib/index-gen.mjs +63 -0
- package/dist/skills/media-use/scripts/lib/local-media-search.mjs +32 -0
- package/dist/skills/media-use/scripts/lib/local-models.mjs +318 -0
- package/dist/skills/media-use/scripts/lib/local-models.test.mjs +220 -0
- package/dist/skills/media-use/scripts/lib/local-run.mjs +78 -0
- package/dist/skills/media-use/scripts/lib/local-run.test.mjs +121 -0
- package/dist/skills/media-use/scripts/lib/logo-provider.mjs +223 -0
- package/dist/skills/media-use/scripts/lib/logo-provider.test.mjs +154 -0
- package/dist/skills/media-use/scripts/lib/ltx-video-provider.mjs +94 -0
- package/dist/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +316 -0
- package/dist/skills/media-use/scripts/lib/lut-preset-provider.mjs +271 -0
- package/dist/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +199 -0
- package/dist/skills/media-use/scripts/lib/manifest.mjs +229 -0
- package/dist/skills/media-use/scripts/lib/manifest.test.mjs +388 -0
- package/dist/skills/media-use/scripts/lib/match.mjs +47 -0
- package/dist/skills/media-use/scripts/lib/media-fetch.mjs +71 -0
- package/dist/skills/media-use/scripts/lib/media-fetch.test.mjs +169 -0
- package/dist/skills/media-use/scripts/lib/media-search.d.mts +9 -0
- package/dist/skills/media-use/scripts/lib/media-search.mjs +107 -0
- package/dist/skills/media-use/scripts/lib/mflux-provider.mjs +128 -0
- package/dist/skills/media-use/scripts/lib/mflux-provider.test.mjs +174 -0
- package/dist/skills/media-use/scripts/lib/misses.mjs +49 -0
- package/dist/skills/media-use/scripts/lib/misses.test.mjs +78 -0
- package/dist/skills/media-use/scripts/lib/npx-sync.mjs +31 -0
- package/dist/skills/media-use/scripts/lib/npx-sync.test.mjs +50 -0
- package/dist/skills/media-use/scripts/lib/parakeet-words.mjs +27 -0
- package/dist/skills/media-use/scripts/lib/parakeet-words.test.mjs +44 -0
- package/dist/skills/media-use/scripts/lib/prefs-store.mjs +180 -0
- package/dist/skills/media-use/scripts/lib/prefs-store.test.mjs +189 -0
- package/dist/skills/media-use/scripts/lib/probe.mjs +39 -0
- package/dist/skills/media-use/scripts/lib/probe.test.mjs +31 -0
- package/dist/skills/media-use/scripts/lib/providers.mjs +5 -0
- package/dist/skills/media-use/scripts/lib/recipe-store.mjs +367 -0
- package/dist/skills/media-use/scripts/lib/recipe-store.test.mjs +367 -0
- package/dist/skills/media-use/scripts/lib/registry.mjs +218 -0
- package/dist/skills/media-use/scripts/lib/registry.test.mjs +262 -0
- package/dist/skills/media-use/scripts/lib/search.mjs +24 -0
- package/dist/skills/media-use/scripts/lib/search.test.mjs +32 -0
- package/dist/skills/media-use/scripts/lib/sfx-provider.mjs +23 -0
- package/dist/skills/media-use/scripts/lib/specs.mjs +132 -0
- package/dist/skills/media-use/scripts/lib/specs.test.mjs +157 -0
- package/dist/skills/media-use/scripts/lib/stats.mjs +117 -0
- package/dist/skills/media-use/scripts/lib/stats.test.mjs +165 -0
- package/dist/skills/media-use/scripts/lib/telemetry.mjs +193 -0
- package/dist/skills/media-use/scripts/lib/telemetry.test.mjs +373 -0
- package/dist/skills/media-use/scripts/lib/tts-local-provider.mjs +92 -0
- package/dist/skills/media-use/scripts/lib/tts-local-provider.test.mjs +72 -0
- package/dist/skills/media-use/scripts/lib/usage.mjs +57 -0
- package/dist/skills/media-use/scripts/lib/usage.test.mjs +54 -0
- package/dist/skills/media-use/scripts/lib/voice-provider.mjs +57 -0
- package/dist/skills/media-use/scripts/lib/voice-provider.test.mjs +46 -0
- package/dist/skills/media-use/scripts/lib/words.mjs +18 -0
- package/dist/skills/media-use/scripts/prefs.mjs +70 -0
- package/dist/skills/media-use/scripts/recipe.mjs +103 -0
- package/dist/skills/media-use/scripts/resolve.mjs +1287 -0
- package/dist/skills/media-use/scripts/transcribe.mjs +180 -0
- package/dist/skills/media-use/scripts/transcript-cut.mjs +239 -0
- package/dist/skills/registry/localSemantic.js +548 -0
- package/dist/studio/assets/{hyperframes-player-c23wnIfc.js → hyperframes-player-BBub3ZdI.js} +3 -3
- package/dist/studio/assets/{index-IDNr3X1H.js → index-BxZ5Cw5w.js} +1 -1
- package/dist/studio/assets/{index-DgkAzx3n.js → index-CI7WON-E.js} +1 -1
- package/dist/studio/assets/{index-gbOCDCuZ.js → index-D1KNPijK.js} +192 -192
- package/dist/studio/assets/{index-DKJpHYYd.css → index-DZBHG9TW.css} +1 -1
- package/dist/studio/index.d.ts +577 -8
- package/dist/studio/index.html +2 -2
- package/dist/studio/index.js +22537 -21600
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/styles.css +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# SFX Credits
|
|
2
|
+
|
|
3
|
+
All sound effects in this directory are sourced from [Pixabay](https://pixabay.com/sound-effects/) and used under the [Pixabay Content License](https://pixabay.com/service/license-summary/).
|
|
4
|
+
|
|
5
|
+
The Pixabay license allows free use for commercial and non-commercial purposes without attribution, but attribution is appreciated and given here for transparency.
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
The following `.mp3` files are bundled with this skill:
|
|
10
|
+
|
|
11
|
+
- `chime.mp3`
|
|
12
|
+
- `click.mp3` / `click-soft.mp3`
|
|
13
|
+
- `error.mp3`
|
|
14
|
+
- `glitch-1.mp3` / `glitch-2.mp3` / `glitch-3.mp3`
|
|
15
|
+
- `impact-bass-1.mp3` / `impact-bass-2.mp3`
|
|
16
|
+
- `key-press.mp3`
|
|
17
|
+
- `notification.mp3`
|
|
18
|
+
- `ping.mp3`
|
|
19
|
+
- `pop.mp3`
|
|
20
|
+
- `riser.mp3`
|
|
21
|
+
- `sparkle.mp3`
|
|
22
|
+
- `typing.mp3`
|
|
23
|
+
- `whoosh.mp3` / `whoosh-short.mp3` / `whoosh-cinematic.mp3`
|
|
24
|
+
|
|
25
|
+
See `manifest.json` for per-file metadata (duration, energy character, recommended use).
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
All files are distributed under the [Pixabay Content License](https://pixabay.com/service/license-summary/), which permits:
|
|
30
|
+
|
|
31
|
+
- Commercial and non-commercial use
|
|
32
|
+
- Modification and remixing
|
|
33
|
+
- Redistribution as part of derivative works (such as videos rendered with HyperFrames)
|
|
34
|
+
|
|
35
|
+
without any attribution requirement.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chime": {
|
|
3
|
+
"file": "chime.mp3",
|
|
4
|
+
"duration": 2.5,
|
|
5
|
+
"description": "Soft melodic chime — gentle positive beat: success/confirmation or a lighthearted transition. Sync to the visual moment."
|
|
6
|
+
},
|
|
7
|
+
"click-soft": {
|
|
8
|
+
"file": "click-soft.mp3",
|
|
9
|
+
"duration": 0.37,
|
|
10
|
+
"description": "Quiet short click — low-key UI tap / soft selection. Short accent, sync exactly to the on-screen action."
|
|
11
|
+
},
|
|
12
|
+
"click": {
|
|
13
|
+
"file": "click.mp3",
|
|
14
|
+
"duration": 0.37,
|
|
15
|
+
"description": "Crisp UI click — button press, toggle, selection. Short accent, sync exactly to the on-screen action."
|
|
16
|
+
},
|
|
17
|
+
"error": {
|
|
18
|
+
"file": "error.mp3",
|
|
19
|
+
"duration": 1.62,
|
|
20
|
+
"description": "Negative / error tone — failure state, a 'wrong' beat, or a glitchy interruption."
|
|
21
|
+
},
|
|
22
|
+
"glitch-1": {
|
|
23
|
+
"file": "glitch-1.mp3",
|
|
24
|
+
"duration": 2.64,
|
|
25
|
+
"description": "Punchy digital glitch — hard-cut accent or sudden reveal. Trigger on the hit; let the decay bleed into the next shot (J-cut)."
|
|
26
|
+
},
|
|
27
|
+
"glitch-2": {
|
|
28
|
+
"file": "glitch-2.mp3",
|
|
29
|
+
"duration": 3.5,
|
|
30
|
+
"description": "Harsh, longer glitch — chaotic / jarring transition or a distorted reveal."
|
|
31
|
+
},
|
|
32
|
+
"glitch-3": {
|
|
33
|
+
"file": "glitch-3.mp3",
|
|
34
|
+
"duration": 3.1,
|
|
35
|
+
"description": "Low-key glitch texture — subtle digital shift, minimal transition that sits under other audio."
|
|
36
|
+
},
|
|
37
|
+
"impact-bass-1": {
|
|
38
|
+
"file": "impact-bass-1.mp3",
|
|
39
|
+
"duration": 2.12,
|
|
40
|
+
"description": "Bass impact hit — logo/hero snap, headline slam. Trigger on the visual landing; decay carries into the next shot (J-cut)."
|
|
41
|
+
},
|
|
42
|
+
"impact-bass-2": {
|
|
43
|
+
"file": "impact-bass-2.mp3",
|
|
44
|
+
"duration": 2.59,
|
|
45
|
+
"description": "Bass impact with a short swell — brief anticipation then a deep hit. Place so the peak lands on the reveal."
|
|
46
|
+
},
|
|
47
|
+
"key-press": {
|
|
48
|
+
"file": "key-press.mp3",
|
|
49
|
+
"duration": 0.4,
|
|
50
|
+
"description": "Single key press — one keystroke / terminal-input beat. Short accent, sync to the typed character."
|
|
51
|
+
},
|
|
52
|
+
"notification": {
|
|
53
|
+
"file": "notification.mp3",
|
|
54
|
+
"duration": 2.46,
|
|
55
|
+
"description": "Notification chime — alert, message-in, toast/badge appears. Sync to the element entering."
|
|
56
|
+
},
|
|
57
|
+
"ping": {
|
|
58
|
+
"file": "ping.mp3",
|
|
59
|
+
"duration": 1.32,
|
|
60
|
+
"description": "Sharp electronic ping — punchy accent on a key reveal or data point. Sync to the beat."
|
|
61
|
+
},
|
|
62
|
+
"pop": {
|
|
63
|
+
"file": "pop.mp3",
|
|
64
|
+
"duration": 0.72,
|
|
65
|
+
"description": "Quick pop — element appear/spawn, chip/tag/badge in. Small precise accent, sync to the pop-in."
|
|
66
|
+
},
|
|
67
|
+
"riser": {
|
|
68
|
+
"file": "riser.mp3",
|
|
69
|
+
"duration": 10.03,
|
|
70
|
+
"description": "Long cinematic riser (~10s build, peak at the end). Trigger at (climax_time − 10.03s) so it crests exactly on the reveal."
|
|
71
|
+
},
|
|
72
|
+
"sparkle": {
|
|
73
|
+
"file": "sparkle.mp3",
|
|
74
|
+
"duration": 1.8,
|
|
75
|
+
"description": "Bright sparkle / shimmer — magical reveal or 'shine' highlight on a hero element. Sync to the highlight."
|
|
76
|
+
},
|
|
77
|
+
"typing": {
|
|
78
|
+
"file": "typing.mp3",
|
|
79
|
+
"duration": 1.5,
|
|
80
|
+
"description": "Typing burst (~1.5s of keys) — keyboard / code typing reveal, text-being-typed beat. Start as the text begins typing."
|
|
81
|
+
},
|
|
82
|
+
"whoosh-cinematic": {
|
|
83
|
+
"file": "whoosh-cinematic.mp3",
|
|
84
|
+
"duration": 5.54,
|
|
85
|
+
"description": "Cinematic whoosh build (~5.5s) — sweeping scene transition. Align so the swell peaks on the cut."
|
|
86
|
+
},
|
|
87
|
+
"whoosh-short": {
|
|
88
|
+
"file": "whoosh-short.mp3",
|
|
89
|
+
"duration": 0.57,
|
|
90
|
+
"description": "Short whoosh — quick swipe/slide accent, fast element move, snappy transition. Sync to the motion."
|
|
91
|
+
},
|
|
92
|
+
"whoosh": {
|
|
93
|
+
"file": "whoosh.mp3",
|
|
94
|
+
"duration": 0.57,
|
|
95
|
+
"description": "Punchy whoosh/impact — fast reveal or hard transition accent. Sync to the motion."
|
|
96
|
+
}
|
|
97
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Background music (BGM)
|
|
2
|
+
|
|
3
|
+
One music bed per composition, produced by the shared audio engine (`scripts/audio.mjs` → `scripts/lib/bgm.mjs`). Two routes, chosen by the engine's one switch — whether a HeyGen credential is present:
|
|
4
|
+
|
|
5
|
+
- **HeyGen retrieval — the default when credentialed.** Search HeyGen's music catalog by mood, download the top track. No generation; same `~/.heygen` / `$HEYGEN_API_KEY` credential as TTS.
|
|
6
|
+
- **Local generation (Lyria → MusicGen) — the fallback when there is no credential** (or when asked for explicitly). Generate a WAV from a mood prompt. There is **no `npx hyperframes bgm` command**; the engine spawns `scripts/lyria-recipe.py` or an inline MusicGen script directly.
|
|
7
|
+
|
|
8
|
+
> **Run the Preflight first — no credential is not a green light to silently generate locally.** Before generating, complete the sign-in **Preflight** (see `../SKILL.md` → Preflight): run `npx hyperframes auth status`, recommend signing in, and **STOP for the user's choice** (sign in for HeyGen's music library, or continue offline with local generation). This applies to a one-off "generate a BGM" request just as much as inside a full workflow.
|
|
9
|
+
|
|
10
|
+
## Driving it from the request
|
|
11
|
+
|
|
12
|
+
`audio_request.json` → `bgm: { mode?, query?, prompt? }`:
|
|
13
|
+
|
|
14
|
+
- **`mode`** — `retrieve | generate | none`. Omit for **auto** (retrieve when credentialed, else generate). An **explicit** `retrieve` is strict: no credential ⇒ skip, never a detached generate (so a caller with no `wait-bgm` step, e.g. product-launch, can't get a pending job it won't await).
|
|
15
|
+
- **`query`** — the mood, used for retrieval and as a fallback prompt seed (e.g. a storyboard's `music:` field, falling back to `message` → `arc` → `"calm cinematic underscore"`).
|
|
16
|
+
- **`prompt`** — an explicit full prompt for generation; omit and the engine infers one (see Mood inference). Optional `blob` / `archetype` / `arc` feed that inference.
|
|
17
|
+
|
|
18
|
+
## HeyGen retrieval (default)
|
|
19
|
+
|
|
20
|
+
`searchSounds(query, "music", { limit: 5 })` → `GET /audio/sounds?query=<mood>&type=music&limit=5`. Take the top result (ranked by `score`), download its presigned `audio_url` → `assets/bgm/track.mp3`. Synchronous. No match → skip (BGM is optional; never fail the render over it). Cue written to `audio_meta.json`:
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
{
|
|
24
|
+
"path": "assets/bgm/track.mp3",
|
|
25
|
+
"volume": 0.12,
|
|
26
|
+
"mode": "retrieve",
|
|
27
|
+
"query": "calm cinematic underscore",
|
|
28
|
+
"duration_s": 42.0,
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`volume` comes from the engine's `bgmDefaultVolume()`: `BGM_BED_VOLUME` (currently `0.12` ≈ -18 dB — a bed under the voice) under narration, `BGM_SILENT_VOLUME` (currently `0.9`) for a silent film (no voice). Tune those constants in `scripts/lib/bgm.mjs`, not call sites. An explicit `volume` in `audio_meta.json` always overrides this default. `bgm_pending` is `false` — the file is on disk when the engine returns.
|
|
33
|
+
|
|
34
|
+
For short launch videos, do not assume the beginning of the retrieved file is the best edit point. Check the opening against later five-second sections. If the track starts with a quiet build but a later section has a stronger, clean musical entrance, trim from that section and apply a short fade-in and longer fade-out. Repeat this check whenever the composition duration changes; the final music file must cover the full cut without a silent tail.
|
|
35
|
+
|
|
36
|
+
## Local generation (fallback) — Lyria → MusicGen
|
|
37
|
+
|
|
38
|
+
Spawned **detached** so voice work isn't blocked; `audio_meta.bgm_pending: true` and `bgm_pid` / `bgm_log` are set until it finishes. **Run `scripts/wait-bgm.mjs` before assembling** — it polls the output file / process / log, detects crashes, and writes `bgm_status.json` (`status: ready | failed | timeout | disabled`). A failed/absent track is simply omitted; it never blocks voice/SFX.
|
|
39
|
+
|
|
40
|
+
| Order | Provider | Env / deps | Speed | Quality |
|
|
41
|
+
| ----- | ------------------------------------ | ------------------------------------------------------------------------------------- | --------------------------------------- | --------------------------- |
|
|
42
|
+
| 1 | Google Lyria RealTime | `$GEMINI_API_KEY` or `$GOOGLE_API_KEY` + `google-genai` (auto-installed on demand) | Real-time stream (≈ requested duration) | Production-grade |
|
|
43
|
+
| 2 | MusicGen (`facebook/musicgen-small`) | Python `transformers + torch + soundfile + numpy` (~300 MB first run; auto-installed) | Slow on CPU; fast on Apple MPS / CUDA | Decent; prompt-only control |
|
|
44
|
+
|
|
45
|
+
Output → `assets/bgm/track.wav`, target = total voice duration. MusicGen generates **one** seed clip (≤28–30s, under the decoder's positional limit) then crossfade-loops it up to the target (or trims down if shorter), avoiding per-segment seams. Backend selection is by what can actually **run**: Lyria only when `import google.genai` succeeds, else MusicGen; if neither can be made to run, BGM is skipped (voice + SFX still render).
|
|
46
|
+
|
|
47
|
+
## Mood inference (the generate prompt)
|
|
48
|
+
|
|
49
|
+
`inferBgmPrompt()` in `scripts/lib/bgm.mjs`: an explicit `prompt` wins; otherwise industry-keyword **base** → narrative-**archetype** shape → emotional-**arc** tiebreaker.
|
|
50
|
+
|
|
51
|
+
| Match in `blob` / `query` | Base prompt | BPM |
|
|
52
|
+
| ------------------------------------------------------ | --------------------------------------------------------------------------- | --- |
|
|
53
|
+
| `crypto / nft / web3 / defi / token / blockchain` | atmospheric electronic, deep bass, futuristic synths, restrained percussion | 100 |
|
|
54
|
+
| `finance / fintech / bank / payment / invest / wealth` | calm cinematic, soft strings, subtle piano, restrained percussion | 92 |
|
|
55
|
+
| `creative / agency / design / studio / art / brand` | playful electronic, warm pads, light percussion | 115 |
|
|
56
|
+
| _(default: SaaS / tech / platform)_ | uplifting corporate tech, bright modern piano with synth pads | 108 |
|
|
57
|
+
|
|
58
|
+
Archetype then reshapes the arc — PAS → "MINOR to MAJOR" build; BAB / future-pacing → aspirational rising; feature-cascade → +10 BPM driving; demo-loop → −8 BPM minimal. The emotional arc breaks remaining ties (tension→relief, excitement, trust/reassurance).
|
|
59
|
+
|
|
60
|
+
## Lyria knobs (direct recipe use)
|
|
61
|
+
|
|
62
|
+
The engine bakes BPM / scale into the **prompt text** (via the inference above) and passes only `--output` / `--duration` / `--prompt` to the recipe. If you invoke `scripts/lyria-recipe.py` directly you can also set: `--bpm` (90–110 calm, 110–130 energetic), `--brightness` (0–1, ≥0.7 promotional), `--density` (0–1, higher = fuller), `--scale` (`MAJOR` / `MINOR` / `PENTATONIC` / …), `--negative-prompt` (styles to exclude). MusicGen ignores all of these — put the mood in the prompt.
|
|
63
|
+
|
|
64
|
+
## Failure modes
|
|
65
|
+
|
|
66
|
+
| Failure | Behavior |
|
|
67
|
+
| --------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
68
|
+
| No music match (retrieve) | `bgm: null`, anomaly logged. Render proceeds without BGM. |
|
|
69
|
+
| Explicit `retrieve`, no credential | Skipped (no silent generate fallback). Use `mode: generate` or omit `mode` for auto. |
|
|
70
|
+
| Neither Lyria nor MusicGen can run (generate) | `bgm` disabled with a `pip install …` hint. Voice + SFX still render. |
|
|
71
|
+
| Generate still rendering at assemble time | `bgm_pending: true`; `wait-bgm.mjs` waits/checks and writes `bgm_status.json` first. |
|
|
72
|
+
| Generate crashed | `wait-bgm.mjs` → `bgm_status.json { status: "failed" }`; the `<audio>` track is omitted. |
|
|
73
|
+
|
|
74
|
+
BGM failure never blocks a render.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Captions
|
|
2
|
+
|
|
3
|
+
<!-- registry-items: allow=max-width,data-composition-src,hyperframes-registry,blend-mode,caption-style,font-family -->
|
|
4
|
+
|
|
5
|
+
**The live search is the source of truth for what the registry has.** The table(s) below are a hand-maintained sample and under-cover by design: run `npx hyperframes catalog --query "<what you want>" --json` — it needs nothing installed — before concluding the registry lacks something. Item names here are checked against `registry/registry.json` by `bun run lint:skills`.
|
|
6
|
+
|
|
7
|
+
Before authoring: confirm the transcript came from the right Whisper model. CLI default `small.en` silently translates non-English audio — see [`../transcribe.md`](../transcribe.md) → "Language Rule" and [`transcript-handling.md`](transcript-handling.md) for the mandatory quality check.
|
|
8
|
+
|
|
9
|
+
Analyze spoken content to determine caption style. If user specifies a style, use that. Otherwise, detect tone from the transcript.
|
|
10
|
+
|
|
11
|
+
## Transcript Source
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
[
|
|
15
|
+
{ "id": "w0", "text": "Hello", "start": 0.0, "end": 0.5 },
|
|
16
|
+
{ "id": "w1", "text": "world.", "start": 0.6, "end": 1.2 }
|
|
17
|
+
]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`id` (`w0`, `w1`, …) is the stable reference for per-word overrides and is added by `hyperframes transcribe`. It's optional for backwards compatibility with hand-authored transcripts. See [`../transcribe.md`](../transcribe.md) → "Output Shape" for how this is produced, and [`transcript-handling.md`](transcript-handling.md) for cleanup before consumption.
|
|
21
|
+
|
|
22
|
+
## Style Detection (When No Style Specified)
|
|
23
|
+
|
|
24
|
+
Read the full transcript before choosing. Four dimensions:
|
|
25
|
+
|
|
26
|
+
**1. Visual feel** — corporate→clean; energetic→bold; storytelling→elegant; technical→precise; social→playful.
|
|
27
|
+
|
|
28
|
+
**2. Color palette** — dark+bright for energy; muted for professional; high contrast for clarity; one accent color.
|
|
29
|
+
|
|
30
|
+
**3. Font mood** — heavy/condensed for impact; clean sans for modern; rounded for friendly; serif for elegance.
|
|
31
|
+
|
|
32
|
+
**4. Animation character** — scale-pop for punchy; gentle fade for calm; word-by-word for emphasis; typewriter for technical.
|
|
33
|
+
|
|
34
|
+
## Per-Word Styling
|
|
35
|
+
|
|
36
|
+
Scan for words deserving distinct treatment:
|
|
37
|
+
|
|
38
|
+
- **Brand/product names** — larger size, unique color
|
|
39
|
+
- **ALL CAPS** — scale boost, flash, accent color
|
|
40
|
+
- **Numbers/statistics** — bold weight, accent color
|
|
41
|
+
- **Emotional keywords** — exaggerated animation (overshoot, bounce)
|
|
42
|
+
- **Call-to-action** — highlight, underline, color pop
|
|
43
|
+
- **Marker highlight** — for beyond-color emphasis (highlight sweep, circle, burst, scribble, sketchout), see `hyperframes-animation/rules/css-marker-patterns.md`.
|
|
44
|
+
|
|
45
|
+
## Script-to-Style Mapping
|
|
46
|
+
|
|
47
|
+
| Tone | Font mood | Animation | Color | Size |
|
|
48
|
+
| ------------ | ------------------------ | ---------------------------------- | --------------------------- | ------- |
|
|
49
|
+
| Hype/launch | Heavy condensed, 800-900 | Scale-pop, back.out(1.7), 0.1-0.2s | Bright on dark | 72-96px |
|
|
50
|
+
| Corporate | Clean sans, 600-700 | Fade+slide, power3.out, 0.3s | White/neutral, muted accent | 56-72px |
|
|
51
|
+
| Tutorial | Mono/clean sans, 500-600 | Typewriter/fade, 0.4-0.5s | High contrast, minimal | 48-64px |
|
|
52
|
+
| Storytelling | Serif/elegant, 400-500 | Slow fade, power2.out, 0.5-0.6s | Warm muted tones | 44-56px |
|
|
53
|
+
| Social | Rounded sans, 700-800 | Bounce, elastic.out, word-by-word | Playful, colored pills | 56-80px |
|
|
54
|
+
|
|
55
|
+
## Word Grouping
|
|
56
|
+
|
|
57
|
+
- **High energy:** 2-3 words. Quick turnover.
|
|
58
|
+
- **Conversational:** 3-5 words. Natural phrases.
|
|
59
|
+
- **Measured/calm:** 4-6 words. Longer groups.
|
|
60
|
+
|
|
61
|
+
Break on sentence boundaries, 150ms+ pauses, or max word count.
|
|
62
|
+
|
|
63
|
+
## Positioning
|
|
64
|
+
|
|
65
|
+
- **Landscape (1920x1080):** Bottom 80-120px, centered
|
|
66
|
+
- **Portrait (1080x1920):** Lower middle ~600-700px from bottom, centered
|
|
67
|
+
- Never cover the subject's face
|
|
68
|
+
- `position: absolute` — never relative
|
|
69
|
+
- One caption group visible at a time
|
|
70
|
+
|
|
71
|
+
## Text Overflow Prevention
|
|
72
|
+
|
|
73
|
+
Use `window.__hyperframes.fitTextFontSize()`:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
var result = window.__hyperframes.fitTextFontSize(group.text.toUpperCase(), {
|
|
77
|
+
fontFamily: "Outfit",
|
|
78
|
+
fontWeight: 900,
|
|
79
|
+
maxWidth: 1600,
|
|
80
|
+
});
|
|
81
|
+
el.style.fontSize = result.fontSize + "px";
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Options: `maxWidth` (1600 landscape, 900 portrait), `baseFontSize` (78), `minFontSize` (42), `fontWeight`, `fontFamily`, `step` (2).
|
|
85
|
+
|
|
86
|
+
CSS safety nets: `max-width` on container, `overflow: visible` (**not** `hidden` — hidden clips scaled emphasis words and glow effects), `position: absolute`, explicit `height`. When per-word styling uses `scale > 1.0`, compute `maxWidth = safeWidth / maxScale` to leave headroom.
|
|
87
|
+
|
|
88
|
+
**Container pattern:** Full-width absolute container, centered. Do **not** use `left: 50%; transform: translateX(-50%)` — causes clipping at composition edges.
|
|
89
|
+
|
|
90
|
+
## Caption Exit Guarantee
|
|
91
|
+
|
|
92
|
+
Every group **must** have a hard kill after exit animation:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
tl.to(groupEl, { opacity: 0, scale: 0.95, duration: 0.12, ease: "power2.in" }, group.end - 0.12);
|
|
96
|
+
// `tl.set` is an instant flip, not a tween — safe to set `visibility` here (core's "no animating
|
|
97
|
+
// visibility" rule applies to tweens, which can't smoothly interpolate non-numeric values anyway).
|
|
98
|
+
tl.set(groupEl, { opacity: 0, visibility: "hidden" }, group.end);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Self-lint after building timeline — place **before** `window.__timelines[id] = tl` so it runs at composition init:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
GROUPS.forEach(function (group, gi) {
|
|
105
|
+
var el = document.getElementById("cg-" + gi);
|
|
106
|
+
if (!el) return;
|
|
107
|
+
tl.seek(group.end + 0.01);
|
|
108
|
+
var computed = window.getComputedStyle(el);
|
|
109
|
+
if (computed.opacity !== "0" && computed.visibility !== "hidden") {
|
|
110
|
+
console.warn(
|
|
111
|
+
"[caption-lint] group " + gi + " still visible at t=" + (group.end + 0.01).toFixed(2) + "s",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
tl.seek(0);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Pre-Built Caption Components
|
|
119
|
+
|
|
120
|
+
Before building caption styles from scratch, check the registry — 15 ready-to-use caption components cover the most common styles. Install with `npx hyperframes add <name>` and wire as a sub-composition via `data-composition-src` (see `hyperframes-registry`).
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx hyperframes catalog --tag caption-style # list all caption components
|
|
124
|
+
npx hyperframes add caption-highlight # install a specific one
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Style | Component | Best for |
|
|
128
|
+
| ------------------------- | ---------------------------- | ---------------------------- |
|
|
129
|
+
| TikTok-style highlight | `caption-highlight` | Social, high-energy |
|
|
130
|
+
| Karaoke pill | `caption-pill-karaoke` | Music, lyric videos |
|
|
131
|
+
| Cinematic editorial | `caption-editorial-emphasis` | Documentary, storytelling |
|
|
132
|
+
| Glitch / cyber | `caption-glitch-rgb` | Tech, gaming |
|
|
133
|
+
| Full-screen slam | `caption-kinetic-slam` | Hype, announcements |
|
|
134
|
+
| Neon glow | `caption-neon-glow` | Night, club, neon aesthetics |
|
|
135
|
+
| Neon accent (multi-color) | `caption-neon-accent` | Colorful, playful |
|
|
136
|
+
| Wipe reveal | `caption-clip-wipe` | Clean, modern |
|
|
137
|
+
| Gradient fill | `caption-gradient-fill` | Vibrant, eye-catching |
|
|
138
|
+
| Matrix decode | `caption-matrix-decode` | Sci-fi, tech reveals |
|
|
139
|
+
| Emoji pop | `caption-emoji-pop` | Social, casual |
|
|
140
|
+
| Parallax layers | `caption-parallax-layers` | Depth, cinematic |
|
|
141
|
+
| Particle burst | `caption-particle-burst` | Celebration, impact keywords |
|
|
142
|
+
| Lava texture | `caption-texture` | Bold, dramatic |
|
|
143
|
+
| Weight shift | `caption-weight-shift` | Elegant, typographic |
|
|
144
|
+
|
|
145
|
+
Related: `caption-blend-difference` (tagged `text` / `blend-mode`, not `caption-style`, so it won't appear under the filter above) auto-inverts text against any background via `mix-blend-mode: difference` — useful when the background is busy or unpredictable.
|
|
146
|
+
|
|
147
|
+
Browse all with previews: [hyperframes.heygen.com/catalog](https://hyperframes.heygen.com/catalog)
|
|
148
|
+
|
|
149
|
+
Caption components ship with transparent backgrounds — they're pure overlays. If the underlying video is bright or busy, add a contrast layer (e.g. a semi-transparent dark div) in the host composition beneath the caption sub-composition, not inside the component itself.
|
|
150
|
+
|
|
151
|
+
## Further References
|
|
152
|
+
|
|
153
|
+
- [`motion.md`](motion.md) — karaoke, marker effects, audio-reactive modulation, scatter exits.
|
|
154
|
+
- [`transcript-handling.md`](transcript-handling.md) — input formats, quality checks, cleaning, external API fallback.
|
|
155
|
+
- `hyperframes-animation/rules/css-marker-patterns.md` — marker highlighting (deterministic, fully seekable).
|
|
156
|
+
|
|
157
|
+
## Constraints
|
|
158
|
+
|
|
159
|
+
- Deterministic. No `Math.random()`, no `Date.now()`.
|
|
160
|
+
- Sync to transcript timestamps.
|
|
161
|
+
- One group visible at a time.
|
|
162
|
+
- Every group must have a hard `tl.set` kill at `group.end`.
|
|
163
|
+
- Fonts: the compiler auto-embeds only its **built-in mapped set** (Inter, Roboto, Montserrat, …) — for those, just declare `font-family` in CSS. Any **other** font (a brand/custom font like `TT Norms Pro`, or a non-Latin CJK/Devanagari family) is **not** auto-supplied: it needs an `@font-face` pointing at a real `.woff2` shipped with the project, or the text silently falls back to a generic font in the render. Don't assume a `font-family` you can see locally will render — the render machine is a clean headless Chrome with no installed fonts.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Dynamic Caption Techniques
|
|
2
|
+
|
|
3
|
+
You are here because SKILL.md told you to read this file before writing animation code. Pick your technique combination from the table below based on the energy level you detected from the transcript, then implement using standard GSAP patterns.
|
|
4
|
+
|
|
5
|
+
## Technique Selection by Energy
|
|
6
|
+
|
|
7
|
+
| Energy level | Highlight | Exit | Cycle pattern |
|
|
8
|
+
| ------------ | ------------------------------------- | ------------------- | ----------------------------------------- |
|
|
9
|
+
| High | Karaoke with accent glow + scale pop | Scatter or drop | Alternate highlight styles every 2 groups |
|
|
10
|
+
| Medium-high | Karaoke with color pop | Scatter or collapse | Alternate every 3 groups |
|
|
11
|
+
| Medium | Karaoke (subtle, white only) | Fade + slide | Alternate every 3 groups |
|
|
12
|
+
| Medium-low | Karaoke (minimal scale change) | Fade | Single style, vary ease per group |
|
|
13
|
+
| Low | Karaoke (warm tones, slow transition) | Collapse | Alternate every 4 groups |
|
|
14
|
+
|
|
15
|
+
**All energy levels use karaoke highlight as the baseline.** The difference is intensity — high energy gets accent color + glow + 15% scale pop on active words, low energy gets a gentle white shift with 3% scale.
|
|
16
|
+
|
|
17
|
+
**Emphasis words always break the pattern.** When a word is flagged as emphasis (emotional keyword, ALL CAPS, brand name), give it a stronger animation than surrounding words (larger scale, accent color, overshoot ease). This creates contrast.
|
|
18
|
+
|
|
19
|
+
**Marker highlight modes add a visual layer on top of karaoke.** For emphasis words that need more than color/scale, add a marker-style effect: highlight sweep, circle, burst, scribble, or sketchout. See `hyperframes-animation/rules/css-marker-patterns.md` for implementation details. Match mode to energy: burst for hype, circle for key terms, highlight for standard, scribble for subtle.
|
|
20
|
+
|
|
21
|
+
## Audio-Reactive Captions (Mandatory for Music)
|
|
22
|
+
|
|
23
|
+
**If the source audio is music (vocals over instrumentation, beats, any musical content), you MUST extract audio data and add audio-reactive animations.** This is not optional — music without audio reactivity looks disconnected. Even low-energy ballads get subtle bass pulse and treble glow.
|
|
24
|
+
|
|
25
|
+
No special wiring is needed. The group loop already iterates over every caption group to build entrance, karaoke, and exit tweens. At that point, read the audio data for each group's time range and use it to modulate the group's animation intensity with regular GSAP tweens.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
// Load audio data inline (same pattern as TRANSCRIPT)
|
|
29
|
+
var AUDIO = JSON.parse(audioDataJson); // { fps, totalFrames, frames: [{ bands: [...] }] }
|
|
30
|
+
|
|
31
|
+
GROUPS.forEach(function (group, gi) {
|
|
32
|
+
var groupEl = document.getElementById("cg-" + gi);
|
|
33
|
+
if (!groupEl) return;
|
|
34
|
+
|
|
35
|
+
// Read peak energy for this group's time range
|
|
36
|
+
var startFrame = Math.floor(group.start * AUDIO.fps);
|
|
37
|
+
var endFrame = Math.min(Math.floor(group.end * AUDIO.fps), AUDIO.totalFrames - 1);
|
|
38
|
+
var peakBass = 0;
|
|
39
|
+
var peakTreble = 0;
|
|
40
|
+
for (var f = startFrame; f <= endFrame; f++) {
|
|
41
|
+
var frame = AUDIO.frames[f];
|
|
42
|
+
if (!frame) continue;
|
|
43
|
+
peakBass = Math.max(peakBass, frame.bands[0] || 0, frame.bands[1] || 0);
|
|
44
|
+
peakTreble = Math.max(peakTreble, frame.bands[6] || 0, frame.bands[7] || 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Modulate entrance — louder groups enter bigger and glowier
|
|
48
|
+
tl.to(
|
|
49
|
+
groupEl,
|
|
50
|
+
{
|
|
51
|
+
scale: 1 + peakBass * 0.06,
|
|
52
|
+
textShadow:
|
|
53
|
+
"0 0 " + Math.round(peakTreble * 12) + "px rgba(255,255,255," + peakTreble * 0.4 + ")",
|
|
54
|
+
duration: 0.3,
|
|
55
|
+
ease: "power2.out",
|
|
56
|
+
},
|
|
57
|
+
group.start,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Reset at exit so audio-driven values don't persist
|
|
61
|
+
tl.set(groupEl, { scale: 1, textShadow: "none" }, group.end - 0.15);
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This shapes the animation at build time, not playback time — no per-frame callbacks, no `tl.call()` loops, no async fetch timing issues. Loud groups come in with more weight and glow; quiet groups come in soft. The audio data modulates _how much_, the content determines _what_.
|
|
66
|
+
|
|
67
|
+
Keep audio reactivity subtle — 3-6% scale variation and soft glow. Heavy pulsing makes text unreadable.
|
|
68
|
+
|
|
69
|
+
To generate the audio data file:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python3 skills/hyperframes-creative/scripts/extract-audio-data.py audio.mp3 --fps 30 --bands 8 -o audio-data.json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Combining Techniques
|
|
76
|
+
|
|
77
|
+
Don't use the same highlight animation on every group — cycle through styles using the group index. Don't combine multiple competing animations on the same word at the same timestamp. Vary techniques across groups to match the content's pace changes.
|
|
78
|
+
|
|
79
|
+
**Marker highlight effects** layer well with karaoke — use karaoke for the word-by-word reveal, then add a marker effect on emphasis words only. For example: karaoke highlights each word in white, but brand names get a yellow highlight sweep and stats get a red circle. Cycle marker modes across groups for visual variety.
|
|
80
|
+
|
|
81
|
+
## Runtime Tools
|
|
82
|
+
|
|
83
|
+
Caption motion uses standard HyperFrames runtime APIs. Use the canonical sources:
|
|
84
|
+
|
|
85
|
+
- **GSAP timeline + tween syntax** — `hyperframes-animation/adapters/gsap.md` (eases, position parameter, performance)
|
|
86
|
+
- **`window.__hyperframes.fitTextFontSize` / `pretext`** — `hyperframes-core/references/determinism-rules.md` → Layout Contract (overflow prevention, per-frame text measurement)
|
|
87
|
+
- **Audio data extraction** — generate via `python3 skills/hyperframes-creative/scripts/extract-audio-data.py audio.mp3 --fps 30 --bands 8 -o audio-data.json`, then load inline as shown in "Audio-Reactive Captions" above
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Transcript Guide
|
|
2
|
+
|
|
3
|
+
For the `transcribe` CLI invocation, the `.en`-translates-non-English rule, and whisper model selection, see [`../transcribe.md`](../transcribe.md). This file covers what to do with the resulting transcript when authoring captions: input formats, mandatory quality checks, cleaning code, external-API fallbacks.
|
|
4
|
+
|
|
5
|
+
## Supported Input Formats
|
|
6
|
+
|
|
7
|
+
The CLI auto-detects and normalizes these formats:
|
|
8
|
+
|
|
9
|
+
| Format | Extension | Source | Word-level? |
|
|
10
|
+
| --------------------- | --------- | --------------------------------------------------------------------------- | ----------------- |
|
|
11
|
+
| whisper.cpp JSON | `.json` | `hyperframes init --video`, `hyperframes transcribe` | Yes |
|
|
12
|
+
| OpenAI Whisper API | `.json` | `openai.audio.transcriptions.create({ timestamp_granularities: ["word"] })` | Yes |
|
|
13
|
+
| SRT subtitles | `.srt` | Video editors, subtitle tools, YouTube | No (phrase-level) |
|
|
14
|
+
| VTT subtitles | `.vtt` | Web players, YouTube, transcription services | No (phrase-level) |
|
|
15
|
+
| Normalized word array | `.json` | Pre-processed by any tool | Yes |
|
|
16
|
+
|
|
17
|
+
**Word-level timestamps produce better captions.** SRT/VTT give phrase-level timing, which works but can't do per-word animation effects.
|
|
18
|
+
|
|
19
|
+
## Transcript Quality Check (Mandatory)
|
|
20
|
+
|
|
21
|
+
After every transcription, **read the transcript and check for quality issues before proceeding.** Bad transcripts produce nonsensical captions. Never skip this step.
|
|
22
|
+
|
|
23
|
+
### What to look for
|
|
24
|
+
|
|
25
|
+
| Signal | Example | Cause |
|
|
26
|
+
| ---------------------------- | -------------------------------------- | ---------------------------------------------------------------------------- |
|
|
27
|
+
| Music note tokens (`♪`, `�`) | `{ "text": "♪" }` or `{ "text": "�" }` | Whisper detected music, not speech |
|
|
28
|
+
| Garbled / nonsense words | "Do a chin", "Get so gay", "huh" | Model misheard lyrics or background noise |
|
|
29
|
+
| Long gaps with no words | 20+ seconds of only `♪` tokens | Instrumental section — expected, but high ratio means speech is being missed |
|
|
30
|
+
| Repeated filler | Many "huh", "uh", "oh" entries | Model is hallucinating on music |
|
|
31
|
+
| Very short word spans | Words with `end - start < 0.05` | Unreliable timestamp alignment |
|
|
32
|
+
|
|
33
|
+
### Automatic retry rules
|
|
34
|
+
|
|
35
|
+
**If more than 20% of entries are `♪`/`�` tokens, or the transcript contains obvious nonsense words, the transcription failed.** Do not proceed with the bad transcript. Instead:
|
|
36
|
+
|
|
37
|
+
1. **Retry with `medium.en`** if the original used `small.en` or smaller:
|
|
38
|
+
```bash
|
|
39
|
+
npx hyperframes transcribe audio.mp3 --model medium.en
|
|
40
|
+
```
|
|
41
|
+
2. **If `medium.en` also fails** (still >20% music tokens or garbled), tell the user the audio is too noisy for local transcription and suggest:
|
|
42
|
+
- Providing lyrics manually as an SRT/VTT file
|
|
43
|
+
- Using an external API (OpenAI or Groq Whisper — see below)
|
|
44
|
+
3. **Always clean the transcript** before building captions — filter out `♪`/`�` tokens and entries where `text` is a single non-word character. Only real words should reach the caption composition.
|
|
45
|
+
|
|
46
|
+
### Cleaning a transcript
|
|
47
|
+
|
|
48
|
+
After transcription (even with a good model), strip non-word entries:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
var raw = JSON.parse(transcriptJson);
|
|
52
|
+
var words = raw.filter(function (w) {
|
|
53
|
+
if (!w.text || w.text.trim().length === 0) return false;
|
|
54
|
+
if (/^[♪�\u266a\u266b\u266c\u266d\u266e\u266f]+$/.test(w.text)) return false;
|
|
55
|
+
if (/^(huh|uh|um|ah|oh)$/i.test(w.text) && w.end - w.start < 0.1) return false;
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For model-selection guidance by content type, see [`../transcribe.md`](../transcribe.md) → "Picking a model by content type".
|
|
61
|
+
|
|
62
|
+
## Using External Transcription APIs
|
|
63
|
+
|
|
64
|
+
For the best accuracy, use an external API and import the result:
|
|
65
|
+
|
|
66
|
+
**OpenAI Whisper API** (recommended for quality):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Generate with word timestamps, then import
|
|
70
|
+
curl https://api.openai.com/v1/audio/transcriptions \
|
|
71
|
+
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
|
72
|
+
-F file=@audio.mp3 -F model=whisper-1 \
|
|
73
|
+
-F response_format=verbose_json \
|
|
74
|
+
-F "timestamp_granularities[]=word" \
|
|
75
|
+
-o transcript-openai.json
|
|
76
|
+
|
|
77
|
+
npx hyperframes transcribe transcript-openai.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Groq Whisper API** (fast, free tier available):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
curl https://api.groq.com/openai/v1/audio/transcriptions \
|
|
84
|
+
-H "Authorization: Bearer $GROQ_API_KEY" \
|
|
85
|
+
-F file=@audio.mp3 -F model=whisper-large-v3 \
|
|
86
|
+
-F response_format=verbose_json \
|
|
87
|
+
-F "timestamp_granularities[]=word" \
|
|
88
|
+
-o transcript-groq.json
|
|
89
|
+
|
|
90
|
+
npx hyperframes transcribe transcript-groq.json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## If No Transcript Exists
|
|
94
|
+
|
|
95
|
+
1. Check the project root for `transcript.json`, `.srt`, or `.vtt` files.
|
|
96
|
+
2. If none found, run [`../transcribe.md`](../transcribe.md) — pick the starting model from "Picking a model by content type" there.
|
|
97
|
+
3. Run the quality check above. If it fails, retry with a larger model or fall back to manual lyrics / external API.
|