Setting the file. One moment.
Fill Timings · Embedded Captions · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page 14.42
Hershey Script1
scripts/fill-timings.cjs
scripts/ fill-timings.cjs
JavaScript · 124 lines · 4 KB
* or an added word degrades gracefully instead of silently mis-timing).
15 */
16 const path = require ( "path" );
17 const fs = require ( "fs" );
18
19 const norm = ( s ) =>
20 String (s == null ? "" : s)
21 . toLowerCase ()
22 . replace ( / [ ^ a-z0-9] / g , "" );
23 const LOOKAHEAD = 40 ; // how far past the pointer to search for a group word (skips dropped fillers)
24
25 function fillGroup ( g , tw , state ) {
26 const matched = [];
27 const unmatched = [];
28 for ( const w of g.words || []) {
29 const target = norm (w.text);
30 if ( ! target) {
31 unmatched. push (w.text);
32 continue ;
33 }
34 let found = - 1 ;
35 for ( let j = state.p; j < Math. min (tw. length , state.p + LOOKAHEAD ); j ++ ) {
36 if ( norm (tw[j].text) === target) {
37 found = j;
38 break ;
39 }
40 }
41 if (found >= 0 ) {
42 w.start = tw[found].start;
43 w.end = tw[found].end;
44 matched. push (w);
45 state.p = found + 1 ;
46 } else {
47 unmatched. push (w.text);
48 }
49 }
50 if (matched. length ) {
51 const firstStart = + matched[ 0 ].start. toFixed ( 3 );
52 const lastEnd = + matched[matched. length - 1 ].end. toFixed ( 3 );
53 // WORD start/end are filled above (deterministic). For the group's DISPLAY window we
54 // only GUARANTEE it brackets the words (never clip a word) — we do NOT tighten it:
55 // a deliberately later `out` is the apex HOLD / sentence ACCUMULATION the author set,
56 // and an earlier `in` is a pre-entry. Preserve both; clamp only if they'd clip.
57 g.in = g.in == null ? firstStart : Math. min (g.in, firstStart);
58 g.out = g.out == null ? lastEnd : Math. max (g.out, lastEnd);
59 }
60 return { matched: matched. length , unmatched };
61 }
62
63 function main () {
64 const project = path. resolve (process.argv[ 2 ] || "" );
65 if ( ! process.argv[ 2 ]) {
66 console. error ( "usage: fill-timings.cjs <project-dir>" );
67 process. exit ( 1 );
68 }
69 const planPath = path. join (project, "plan.json" );
70 const trPath = path. join (project, "transcript.json" );
71 let plan, trWordsRaw;
72 try {
73 plan = JSON . parse (fs. readFileSync (planPath, "utf8" ));
74 } catch {
75 console. error ( `[fill-timings] no plan.json (Cinematic only) — skipping` );
76 process. exit ( 0 );
77 }
78 try {
79 trWordsRaw = JSON . parse (fs. readFileSync (trPath, "utf8" )).words || [];
80 } catch {
81 console. error ( `[fill-timings] no transcript.json — skipping` );
82 process. exit ( 0 );
83 }
84 const tw = trWordsRaw. filter (( w ) => w && "start" in w && "end" in w);
85 if ( ! tw. length ) {
86 console. error ( `[fill-timings] transcript has no word timings — skipping` );
87 process. exit ( 0 );
88 }
89
90 const state = { p: 0 };
91 let totalMatched = 0 ,
92 totalUnmatched = 0 ;
93 const groups = plan.groups || [];
94 // crown_group (if any) is spoken last in the standard templates; process groups in order, crown after.
95 const ordered = [ ... groups];
96 for ( const g of ordered) {
97 const r = fillGroup (g, tw, state);
98 totalMatched += r.matched;
99 totalUnmatched += r.unmatched. length ;
100 if (r.unmatched. length )
101 console. error (
102 `[fill-timings] ⚠ ${ g . id || "(group)"}: ${ r . unmatched . length } word(s) not found in transcript from here: ${ r . unmatched . join ( " " ) }` ,
103 );
104 }
105 if (plan.crown_group) {
106 const r = fillGroup (plan.crown_group, tw, state);
107 totalMatched += r.matched;
108 totalUnmatched += r.unmatched. length ;
109 if (r.unmatched. length )
110 console. error ( `[fill-timings] ⚠ crown_group: ${ r . unmatched . join ( " " ) } not found` );
111 }
112
113 fs. writeFileSync (planPath, JSON . stringify (plan, null , 2 ));
114 console. log (
115 `[fill-timings] filled ${ totalMatched } word time(s) from transcript by sequence` +
116 (totalUnmatched
117 ? `; ${ totalUnmatched } unmatched (kept prior time — check those words)`
118 : `; all matched ✓` ),
119 );
120 console. log (
121 `[fill-timings] group windows now: ${ ordered . map (( g ) => `${ g . id || "?"}[${ g . in }-${ g . out }]` ). join ( " " ) }` ,
122 );
123 }
124 main ();