Setting the file. One moment.
Wait Bgm · Media Use · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page ⋯
scripts/11 files
audio/scripts/ wait-bgm.mjs
JavaScript · 171 lines · 5 KB
15
16 import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs" ;
17 import { join, resolve } from "node:path" ;
18
19 const argv = process.argv. slice ( 2 );
20 const flag = ( name , def ) => {
21 const i = argv. indexOf ( `--${ name }` );
22 return i >= 0 && i + 1 < argv. length ? argv[i + 1 ] : def;
23 };
24
25 function die ( msg ) {
26 console. error ( `✗ wait-bgm.mjs: ${ msg }` );
27 process. exit ( 1 );
28 }
29
30 const audioMetaPath = resolve ( flag ( "audio-meta" , "./audio_meta.json" ));
31 const hyperframesDir = resolve ( flag ( "hyperframes" , "." ));
32 const outPath = resolve ( flag ( "out" , join (hyperframesDir, "bgm_status.json" )));
33 const timeoutMs = Math. max ( 0 , Number ( flag ( "timeout-ms" , "120000" )) || 0 );
34 const intervalMs = Math. max ( 250 , Number ( flag ( "interval-ms" , "2000" )) || 2000 );
35
36 function sleep ( ms ) {
37 return new Promise (( resolveSleep ) => setTimeout (resolveSleep, ms));
38 }
39
40 function isProcessAlive ( pid ) {
41 if ( ! pid || ! Number. isFinite ( Number (pid))) return false ;
42 try {
43 process. kill ( Number (pid), 0 );
44 return true ;
45 } catch {
46 return false ;
47 }
48 }
49
50 function readTail ( path , maxChars = 6000 ) {
51 if ( ! path) return "" ;
52 try {
53 const txt = readFileSync (path, "utf8" );
54 return txt. slice (Math. max ( 0 , txt. length - maxChars));
55 } catch (error) {
56 if (error.code === "ENOENT" || error.code === "ENOTDIR" ) return "" ;
57 throw error;
58 }
59 }
60
61 function detectFailure ( logTail ) {
62 if ( ! logTail) return "" ;
63 const lines = logTail. split ( " \n " );
64 // Bare "out of range" over-matched benign BGM-renderer logs (e.g. a "sample rate
65 // out of range, resampling" notice), mislabelling a healthy track as failed and
66 // silently dropping the music. Anchor to the actual crash strings instead:
67 // Python "(list) index out of range" and torch "index … out of bounds".
68 const idx = lines. findIndex (( line ) =>
69 /(Traceback | IndexError | RuntimeError | Exception | Killed | No space left | Cannot allocate | index out of range | out of bounds)/ i . test (
70 line,
71 ),
72 );
73 if (idx < 0 ) return "" ;
74 return lines. slice (idx). join ( " \n " ). trim ();
75 }
76
77 function writeStatus ( status ) {
78 const payload = {
79 generated_at: new Date (). toISOString (),
80 ... status,
81 };
82 writeFileSync (outPath, JSON . stringify (payload, null , 2 ) + " \n " );
83 return payload;
84 }
85
86 if ( ! existsSync (audioMetaPath)) die ( `audio_meta.json missing at ${ audioMetaPath }` );
87
88 const audioMeta = JSON . parse ( readFileSync (audioMetaPath, "utf8" ));
89 const bgmPath = audioMeta.bgm?.path || "" ;
90 const bgmAbsPath = bgmPath ? join (hyperframesDir, bgmPath) : "" ;
91 const logPath = audioMeta.bgm_log || "" ;
92 const pid = audioMeta.bgm_pid || null ;
93
94 const base = {
95 enabled: Boolean (audioMeta.bgm_pending && bgmPath),
96 provider: audioMeta.bgm_provider || null ,
97 mode: audioMeta.bgm_mode || null ,
98 path: bgmPath || null ,
99 log: logPath || null ,
100 pid,
101 target_duration_s: audioMeta.bgm_target_duration_s || null ,
102 seed_duration_s: audioMeta.bgm_seed_duration_s || null ,
103 loop_count: audioMeta.bgm_loop_count || null ,
104 timeout_ms: timeoutMs,
105 };
106
107 if ( ! base.enabled) {
108 const status = writeStatus ({
109 ... base,
110 status: "disabled" ,
111 ready: false ,
112 waited_ms: 0 ,
113 message: "BGM not requested or disabled in audio_meta.json." ,
114 });
115 console. log ( `✓ bgm: ${ status . status } (${ status . message })` );
116 process. exit ( 0 );
117 }
118
119 const started = Date. now ();
120 let lastFailure = "" ;
121 let lastTail = "" ;
122
123 while (Date. now () - started <= timeoutMs) {
124 if ( existsSync (bgmAbsPath)) {
125 const size = statSync (bgmAbsPath).size;
126 writeStatus ({
127 ... base,
128 status: "ready" ,
129 ready: true ,
130 waited_ms: Date. now () - started,
131 size_bytes: size,
132 message: `BGM ready at ${ bgmPath }.` ,
133 });
134 console. log ( `✓ bgm: ready (${ bgmPath }, ${ size }B)` );
135 process. exit ( 0 );
136 }
137
138 lastTail = readTail (logPath);
139 lastFailure = detectFailure (lastTail);
140 const alive = isProcessAlive (pid);
141 if (lastFailure || ( ! alive && logPath && existsSync (logPath))) {
142 const message = lastFailure
143 ? `BGM renderer failed; see ${ logPath }.`
144 : `BGM renderer exited without writing ${ bgmPath }; see ${ logPath }.` ;
145 const status = writeStatus ({
146 ... base,
147 status: "failed" ,
148 ready: false ,
149 waited_ms: Date. now () - started,
150 process_alive: alive,
151 message,
152 error_tail: lastFailure || lastTail. slice ( - 2000 ),
153 });
154 console. log ( `! bgm: failed (${ status . message })` );
155 process. exit ( 0 );
156 }
157
158 if (timeoutMs === 0 ) break ;
159 await sleep (Math. min (intervalMs, Math. max ( 0 , timeoutMs - (Date. now () - started))));
160 }
161
162 const status = writeStatus ({
163 ... base,
164 status: "timeout" ,
165 ready: false ,
166 waited_ms: Date. now () - started,
167 process_alive: isProcessAlive (pid),
168 message: `Timed out waiting for ${ bgmPath }; assemble-index will skip BGM if still absent.` ,
169 log_tail: lastTail. slice ( - 2000 ),
170 });
171 console. log ( `! bgm: timeout after ${ status . waited_ms }ms (${ bgmPath })` );