Setting the file. One moment.
Audio Test · Faceless Explainer · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page scripts/ audio.test.mjs
JavaScript · 176 lines · 8 KB
script
=
new
URL
(
"./audio.mjs"
,
import
.
meta
.url).pathname;
9
10 // The header of this adapter declares it "intentionally identical across the
11 // reusing skills" — pin that contract so a fix landing in one copy can't
12 // silently miss the other (the fully-silent marker bug did exactly that).
13 test ( "audio.mjs is byte-identical to pr-to-video's copy (the stated contract)" , () => {
14 const here = readFileSync (script, "utf8" );
15 const sibling = readFileSync (
16 new URL ( "../../pr-to-video/scripts/audio.mjs" , import . meta .url),
17 "utf8" ,
18 );
19 assert. equal (here, sibling);
20 });
21
22 // ── the canonical fully-silent marker (SKILL.md Step 3.1) ────────────────────
23 // Same contract as product-launch (see its audio.test.mjs): `music: none` in
24 // the storyboard's top YAML block + no SCRIPT.md ⇒ generate nothing; with
25 // narration ⇒ TTS runs, BGM off.
26
27 function runAudioRaw ({ storyboard , scriptMd = null , preexistingMeta = null }) {
28 const dir = mkdtempSync ( join ( tmpdir (), "faceless-audio-" ));
29 const engine = join (dir, "engine.mjs" );
30 writeFileSync ( join (dir, "STORYBOARD.md" ), storyboard);
31 if (scriptMd != null ) writeFileSync ( join (dir, "SCRIPT.md" ), scriptMd);
32 if (preexistingMeta != null ) writeFileSync ( join (dir, "audio_meta.json" ), preexistingMeta);
33 writeFileSync (
34 engine,
35 `import { readFileSync, writeFileSync } from "node:fs";
36 const argv = process.argv.slice(2);
37 const flag = (name) => argv[argv.indexOf(name) + 1];
38 const request = JSON.parse(readFileSync(flag("--request"), "utf8"));
39 writeFileSync(new URL("request.json", import.meta.url), JSON.stringify(request));
40 writeFileSync(flag("--out"), JSON.stringify({ voices: [], bgm: null, sfx: [] }));
41 ` ,
42 );
43 const result = spawnSync (
44 process.execPath,
45 [script, "--hyperframes" , dir, "--storyboard" , join (dir, "STORYBOARD.md" )],
46 { encoding: "utf8" , env: { ... process.env, HF_MEDIA_ENGINE: engine } },
47 );
48 return { dir, result };
49 }
50
51 test ( "music: none + no SCRIPT.md = fully silent: no engine run, no audio_meta.json" , () => {
52 const { dir , result } = runAudioRaw ({ storyboard: "--- \n message: Test \n music: none \n --- \n " });
53
54 assert. equal (result.status, 0 , result.stderr);
55 assert. match (result.stdout, /marked silent/ );
56 assert. equal ( existsSync ( join (dir, "request.json" )), false );
57 assert. equal ( existsSync ( join (dir, "audio_meta.json" )), false );
58 });
59
60 test ( "fully-silent run removes stale audio_meta.json from a previous non-silent run" , () => {
61 const { dir , result } = runAudioRaw ({
62 storyboard: "--- \n message: Test \n music: none \n --- \n " ,
63 preexistingMeta: JSON . stringify ({ bgm: { path: "old.mp3" }, voices: [], sfx: [] }),
64 });
65
66 assert. equal (result.status, 0 , result.stderr);
67 assert. equal ( existsSync ( join (dir, "audio_meta.json" )), false );
68 });
69
70 test ( "music: none with narration keeps TTS but turns BGM off (not fully silent)" , () => {
71 const { dir , result } = runAudioRaw ({
72 storyboard: "--- \n message: Test \n music: none \n --- \n " ,
73 scriptMd: "## Hook (Frame 1) \n\n Spoken line for frame one. \n " ,
74 });
75
76 assert. equal (result.status, 0 , result.stderr);
77 const request = JSON . parse ( readFileSync ( join (dir, "request.json" ), "utf8" ));
78 assert. equal (request.bgm.mode, "none" );
79 assert. equal (request.lines. length , 1 );
80 });
81
82 test ( "a storyboard music mood still retrieves BGM (marker is exact, not fuzzy)" , () => {
83 const { dir , result } = runAudioRaw ({
84 storyboard: "--- \n message: Test \n music: upbeat synthwave with heavy drums \n --- \n " ,
85 });
86
87 assert. equal (result.status, 0 , result.stderr);
88 const request = JSON . parse ( readFileSync ( join (dir, "request.json" ), "utf8" ));
89 assert. equal (request.bgm.mode, "retrieve" );
90 assert. equal (request.bgm.query, "upbeat synthwave with heavy drums" );
91 });
92
93 // ── fetch-sfx ────────────────────────────────────────────────────────────────
94 // Regressions found while running the full product-launch workflow end to end
95 // (linear.app site showcase, 2026-07-30).
96
97 /** Runs the fetch-sfx subcommand. `neutralOut` is what the stub engine writes to --out. */
98 function runFetchSfx ({ storyboard , neutralOut = { voices: [], bgm: null , sfx: [] } }) {
99 const dir = mkdtempSync ( join ( tmpdir (), "product-launch-sfx-" ));
100 const engine = join (dir, "engine.mjs" );
101 writeFileSync ( join (dir, "STORYBOARD.md" ), storyboard);
102 writeFileSync (
103 engine,
104 `import { readFileSync, writeFileSync } from "node:fs";
105 const argv = process.argv.slice(2);
106 const flag = (name) => argv[argv.indexOf(name) + 1];
107 const request = JSON.parse(readFileSync(flag("--request"), "utf8"));
108 writeFileSync(new URL("request.json", import.meta.url), JSON.stringify(request));
109 writeFileSync(flag("--out"), ${ JSON . stringify ( JSON . stringify ( neutralOut )) });
110 ` ,
111 );
112 const result = spawnSync (
113 process.execPath,
114 [script, "fetch-sfx" , "--hyperframes" , dir, "--storyboard" , join (dir, "STORYBOARD.md" )],
115 { encoding: "utf8" , env: { ... process.env, HF_MEDIA_ENGINE: engine } },
116 );
117 return { dir, result };
118 }
119
120 const FRAME_WITH_SFX = ( sfx ) =>
121 `--- \n message: Test \n --- \n\n ## Frame 1 — Hook \n - duration: 3s \n - sfx: ${ sfx } \n ` ;
122
123 test ( "fetch-sfx: `sfx: none` is an absence marker, not a cue named none" , () => {
124 const { dir , result } = runFetchSfx ({ storyboard: FRAME_WITH_SFX ( "none" ) });
125
126 assert. equal (result.status, 0 , result.stderr);
127 const request = JSON . parse ( readFileSync ( join (dir, "request.json" ), "utf8" ));
128 // Used to reach the engine as { sfx: ["none"] } — a cue that cannot resolve.
129 assert. deepEqual (request.lines, []);
130 });
131
132 test ( "fetch-sfx: the other absence spellings are markers too" , () => {
133 for ( const spelling of [ "None" , "n/a" , "NA" , "skip" , "-" , "—" ]) {
134 const { dir , result } = runFetchSfx ({ storyboard: FRAME_WITH_SFX (spelling) });
135 assert. equal (result.status, 0 , result.stderr);
136 const request = JSON . parse ( readFileSync ( join (dir, "request.json" ), "utf8" ));
137 assert. deepEqual (request.lines, [], `spelling: ${ spelling }` );
138 }
139 });
140
141 test ( "fetch-sfx: a real cue still reaches the engine, and mixed lists drop only the marker" , () => {
142 const { dir , result } = runFetchSfx ({ storyboard: FRAME_WITH_SFX ( "whoosh, none, click" ) });
143
144 assert. equal (result.status, 0 , result.stderr);
145 const request = JSON . parse ( readFileSync ( join (dir, "request.json" ), "utf8" ));
146 assert. deepEqual (request.lines, [{ id: "01" , sfx: [ "whoosh" , "click" ] }]);
147 });
148
149 test ( "fetch-sfx: carries bgm_pending through and warns that the snapshot has no bed" , () => {
150 const { dir , result } = runFetchSfx ({
151 storyboard: FRAME_WITH_SFX ( "whoosh" ),
152 // A detached Lyria/MusicGen generate that has not landed yet.
153 neutralOut: { voices: [], bgm: null , bgm_pending: true , sfx: [] },
154 });
155
156 assert. equal (result.status, 0 , result.stderr);
157 const meta = JSON . parse ( readFileSync ( join (dir, "audio_meta.json" ), "utf8" ));
158 // The flag used to be dropped in the neutral → PL translation, making "not ready yet"
159 // indistinguishable from "silent by design".
160 assert. equal (meta.bgm_pending, true );
161 assert. equal (meta.bgm, null );
162 assert. match (result.stderr + result.stdout, /pending/ i );
163 });
164
165 test ( "fetch-sfx: a resolved bed reports bgm_pending false and no warning" , () => {
166 const { dir , result } = runFetchSfx ({
167 storyboard: FRAME_WITH_SFX ( "whoosh" ),
168 neutralOut: { voices: [], bgm: { path: "assets/bgm/track.mp3" , volume: 0.12 }, sfx: [] },
169 });
170
171 assert. equal (result.status, 0 , result.stderr);
172 const meta = JSON . parse ( readFileSync ( join (dir, "audio_meta.json" ), "utf8" ));
173 assert. equal (meta.bgm_pending, false );
174 assert. equal (meta.bgm.path, "assets/bgm/track.mp3" );
175 assert. doesNotMatch (result.stderr, /pending/ i );
176 });