Setting the file. One moment.
Check Overflow · Embedded Captions · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page 14.42
Hershey Script1
scripts/check-overflow.cjs
scripts/ check-overflow.cjs
JavaScript · 194 lines · 7 KB
15 */
16 const path = require ( "path" );
17 const fs = require ( "fs" );
18 const os = require ( "os" );
19
20 const HF_ROOTS = [
21 process.env. HYPERFRAMES_ROOT ,
22 path. resolve (__dirname, "../../.." ),
23 path. join (os. homedir (), "Downloads" , "hyperframes" ),
24 ]. filter (Boolean);
25 let puppeteer = null ;
26 for ( const root of HF_ROOTS ) {
27 const cands = [path. join (root, "node_modules" , "puppeteer" )];
28 const bunDir = path. join (root, "node_modules" , ".bun" );
29 try {
30 if (fs. existsSync (bunDir)) {
31 for ( const d of fs. readdirSync (bunDir))
32 if (d. startsWith ( "puppeteer@" ))
33 cands. push (path. join (bunDir, d, "node_modules" , "puppeteer" ));
34 }
35 } catch {
36 /* ignore */
37 }
38 for ( const p of cands) {
39 try {
40 if (fs. existsSync (p)) {
41 puppeteer = require (p);
42 break ;
43 }
44 } catch {}
45 }
46 if (puppeteer) break ;
47 }
48 if ( ! puppeteer) {
49 console. error ( "[overflow] puppeteer not found" );
50 process. exit ( 3 );
51 }
52
53 async function main () {
54 const projectDir = process.argv[ 2 ];
55 const htmlName = process.argv[ 3 ] || "index.html" ; // Standard mode passes "rail.html" to gate the rail too
56 const indexPath = path. resolve (projectDir, htmlName);
57 if ( ! fs. existsSync (indexPath)) {
58 console. error ( `[overflow] missing ${ indexPath }` );
59 process. exit ( 2 );
60 }
61
62 const html = fs. readFileSync (indexPath, "utf8" );
63 const num = ( re , d ) => {
64 const m = html. match (re);
65 return m ? parseFloat (m[ 1 ]) : d;
66 };
67 const W = num ( /data-width="( [0-9.] + )"/ , 1920 );
68 const H = num ( /data-height="( [0-9.] + )"/ , 1080 );
69 const DUR = num ( /data-duration="( [0-9.] + )"/ , 8 );
70
71 const exe =
72 process.platform === "darwin"
73 ? "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
74 : "/usr/bin/google-chrome" ;
75 const browser = await puppeteer. launch ({
76 headless: "new" ,
77 executablePath: fs. existsSync (exe) ? exe : undefined ,
78 args: [
79 "--disable-web-security" ,
80 "--allow-file-access-from-files" ,
81 `--window-size=${ W },${ H }` ,
82 "--disable-dev-shm-usage" ,
83 ],
84 });
85 try {
86 const page = await browser. newPage ();
87 await page. setViewport ({ width: Math. round ( W ), height: Math. round ( H ), deviceScaleFactor: 1 });
88 const waitTL = async () => {
89 const t0 = Date. now ();
90 while (Date. now () - t0 < 12000 ) {
91 if ( await page. evaluate (() => !! (window.__timelines && window.__timelines.main)))
92 return true ;
93 await new Promise (( r ) => setTimeout (r, 200 ));
94 }
95 return false ;
96 };
97 await page. goto ( `file://${ indexPath }` , { waitUntil: "load" , timeout: 20000 });
98 let hasTL = await waitTL ();
99 if ( ! hasTL) {
100 // GSAP loads from CDN — a blip leaves no timeline; retry once
101 await page. reload ({ waitUntil: "load" , timeout: 20000 }). catch (() => {});
102 hasTL = await waitTL ();
103 }
104 if ( ! hasTL) {
105 // NEVER claim "ok" when we couldn't actually evaluate the animated layout.
106 console. error (
107 "[overflow] ⚠ timeline did not register (GSAP CDN blocked?) — overflow check " +
108 "INCONCLUSIVE; eyeball the render for off-frame captions." ,
109 );
110 await browser. close ();
111 process. exit ( 0 );
112 }
113 await page. evaluate ( async () => {
114 try {
115 await document.fonts.ready;
116 } catch {}
117 });
118
119 const times = Array. from ({ length: 9 }, ( _ , i ) => + (( DUR * i) / 8 ). toFixed ( 2 ));
120 const found = new Map (); // key text → worst offense
121
122 for ( const t of times) {
123 await page. evaluate (( t ) => {
124 window.__timelines.main. seek (t);
125 void document.body.offsetHeight;
126 }, t);
127 await new Promise (( r ) => setTimeout (r, 25 ));
128 const offenders = await page. evaluate (
129 ( W , H ) => {
130 const M = 2 ; // tolerance px
131 const root = document. querySelector ( "#stage" ) || document.body;
132 const out = [];
133 for ( const el of root. querySelectorAll ( "*" )) {
134 if (el.tagName === "VIDEO" || el.tagName === "AUDIO" ) continue ;
135 const own = [ ... el.childNodes]
136 . filter (( n ) => n.nodeType === 3 )
137 . map (( n ) => n.textContent. trim ())
138 . join ( " " )
139 . trim ();
140 if ( ! own) continue ;
141 const cs = getComputedStyle (el);
142 if (
143 cs.display === "none" ||
144 cs.visibility === "hidden" ||
145 parseFloat (cs.opacity) < 0.06
146 )
147 continue ;
148 const b = el. getBoundingClientRect ();
149 if (b.width === 0 || b.height === 0 ) continue ;
150 const off = {
151 left: Math. max ( 0 , Math. round ( - b.left - M )),
152 right: Math. max ( 0 , Math. round (b.right - W - M )),
153 top: Math. max ( 0 , Math. round ( - b.top - M )),
154 bottom: Math. max ( 0 , Math. round (b.bottom - H - M )),
155 };
156 if (off.left || off.right || off.top || off.bottom)
157 out. push ({ text: own. slice ( 0 , 42 ), off });
158 }
159 return out;
160 },
161 W ,
162 H ,
163 );
164 for ( const o of offenders) {
165 const sides = Object. entries (o.off)
166 . filter (([, v ]) => v > 0 )
167 . map (([ s , v ]) => `${ s } ${ v }px` )
168 . join ( ", " );
169 const prev = found. get (o.text);
170 const score = Object. values (o.off). reduce (( a , b ) => a + b, 0 );
171 if ( ! prev || score > prev.score) found. set (o.text, { sides, score, t });
172 }
173 }
174
175 if (found.size === 0 ) {
176 console. log ( `[overflow] ok — no caption text leaves the ${ W }x${ H } frame` );
177 } else {
178 console. error (
179 `[overflow] ⚠ ${ found . size } caption(s) leave the frame (custom mode — WARNING only, not blocking):` ,
180 );
181 for ( const [ text , info ] of found)
182 console. error ( ` "${ text }" → off-frame: ${ info . sides } (@${ info . t }s)` );
183 console. error (
184 `[overflow] if unintentional, reposition/resize; if it's deliberate bleed, ignore.` ,
185 );
186 }
187 } finally {
188 await browser. close ();
189 }
190 }
191 main (). catch (( e ) => {
192 console. error ( "[overflow]" , e.message);
193 process. exit ( 3 );
194 });