@@ -6,94 +6,105 @@ const vm = require('node:vm');
66const result = buildSync ( { entryPoints :[ require ( 'node:path' ) . join ( __dirname , '../src/lib/censorSections.js' ) ] , bundle :true , write :false , format :'cjs' } ) ;
77const sandbox = { module :{ exports :{ } } } ;
88vm . runInNewContext ( result . outputFiles [ 0 ] . text , sandbox ) ;
9- const { censorSections, adjust , moveEdge, resetWord, normalizeWord, parseWords} = sandbox . module . exports ;
9+ const { censorSections, setSkip , moveEdge, resetWord, normalizeWord, parseWords} = sandbox . module . exports ;
1010
1111// Objects come from the vm sandbox's realm, so compare as JSON.
1212const same = ( a , b ) => assert . equal ( JSON . stringify ( a ) , JSON . stringify ( b ) ) ;
13+ const near = ( a , b ) => assert . ok ( Math . abs ( a - b ) < 1e-9 , `${ a } != ${ b } ` ) ;
1314const WORDS = [ { w :'hello' , s :0 , e :1 } , { w :'Shit!' , s :1 , e :1.5 } , { w :'ship' , s :2 , e :3 } ] ;
15+ const SEG = [ { start :0 , end :3 } ] ;
16+ const one = ( pb , pa , adj , segs = SEG , words = WORDS ) => censorSections ( words , [ 'shit' ] , segs , pb , pa , adj ) [ 0 ] ;
1417
1518test ( 'default padding starts the section before the transcript timing' , ( ) => {
16- const [ sec ] = censorSections ( WORDS , [ 'shit' ] , [ { start : 0 , end : 3 } ] , 0.15 , 0.05 , { } ) ;
17- assert . deepEqual ( [ sec . key , sec . start , sec . end ] , [ '1.000' , 0.85 , 1.55 ] ) ;
19+ const sec = one ( 0.15 , 0.05 , { } ) ;
20+ same ( [ sec . key , sec . start , sec . end , sec . manualStart , sec . manualEnd ] , [ '1.000' , 0.85 , 1.55 , false , false ] ) ;
1821} ) ;
1922
20- test ( 'nudges, clamping and skip mirror the server' , ( ) => {
21- let adj = adjust ( { } , '1.000' , 'before' , 0.2 ) ;
22- adj = adjust ( adj , '1.000' , 'after' , - 0.1 ) ;
23- const [ sec ] = censorSections ( WORDS , [ 'shit' ] , [ { start :0 , end :3 } ] , 0.1 , 0 , adj ) ;
24- assert . deepEqual ( [ sec . start , sec . end ] , [ 0.7 , 1.4 ] ) ;
25- assert . equal ( censorSections ( WORDS , [ 'shit' ] , [ { start :0.9 , end :3 } ] , 1 , 0.05 , { } ) [ 0 ] . start , 0.9 ) ;
26- const skipped = adjust ( { } , '1.000' , 'skip' , true ) ;
27- assert . equal ( censorSections ( WORDS , [ 'shit' ] , [ { start :0 , end :3 } ] , 0.15 , 0.05 , skipped ) [ 0 ] . skip , true ) ;
28- // Back to zero drops the entry, so an untouched word sends nothing.
29- same ( adjust ( adj , '1.000' , 'before' , 0 ) , { '1.000' : { before : 0 , after : - 0.1 , skip : false } } ) ;
30- same ( adjust ( adjust ( { } , '1.000' , 'after' , 0.05 ) , '1.000' , 'after' , 0 ) , { } ) ;
23+ test ( 'hand-set edges keep their time when the presets change; the other edge follows' , ( ) => {
24+ let sec = one ( 0.15 , 0.05 , { } ) ;
25+ const adj = moveEdge ( { } , sec , 'start' , - 0.1 ) ; // start 0.85 -> 0.75, by hand
26+ same ( adj , { '1.000' : { start : 0.75 } } ) ;
27+ sec = one ( 0.5 , 0.3 , adj ) ; // presets raised afterwards
28+ same ( [ sec . start , sec . manualStart , sec . end , sec . manualEnd ] , [ 0.75 , true , 1.8 , false ] ) ;
29+ sec = one ( 0 , 0 , adj ) ; // and lowered
30+ same ( [ sec . start , sec . end ] , [ 0.75 , 1.5 ] ) ;
31+ // Moving the whole section fixes both edges.
32+ const moved = moveEdge ( adj , one ( 0 , 0 , adj ) , 'move' , 0.05 ) ;
33+ same ( moved , { '1.000' : { start : 0.8 , end : 1.55 } } ) ;
34+ same ( [ one ( 0.4 , 0.4 , moved ) . start , one ( 0.4 , 0.4 , moved ) . end ] , [ 0.8 , 1.55 ] ) ;
3135} ) ;
3236
33- test ( 'clip timeline follows segment order' , ( ) => {
34- const two = [ { w :'shit' , s :1 , e :1.5 } , { w :'shit' , s :5 , e :5.5 } ] ;
35- const secs = censorSections ( two , [ 'shit' ] , [ { start :4 , end :6 } , { start :0 , end :2 } ] , 0 , 0 , { } ) ;
36- same ( secs . map ( ( s ) => [ s . key , s . clipStart ] ) , [ [ '5.000' , 1 ] , [ '1.000' , 3 ] ] ) ;
37+ test ( 'reset and skip' , ( ) => {
38+ const adj = setSkip ( moveEdge ( { } , one ( 0.15 , 0.05 , { } ) , 'end' , 0.25 ) , '1.000' , true ) ;
39+ same ( adj , { '1.000' : { end : 1.8 , skip : true } } ) ;
40+ same ( resetWord ( adj , '1.000' ) , { '1.000' : { skip : true } } ) ;
41+ same ( resetWord ( setSkip ( adj , '1.000' , false ) , '1.000' ) , { } ) ;
42+ assert . equal ( one ( 0.15 , 0.05 , adj ) . skip , true ) ;
3743} ) ;
3844
39- test ( 'word normalisation matches word_censor.normalize ' , ( ) => {
40- assert . equal ( normalizeWord ( "'Scheiße!'" ) , 'scheiße' ) ;
41- assert . equal ( normalizeWord ( "Don't," ) , "don't" ) ;
42- same ( parseWords ( ' fuck, ,shit ' ) , [ 'fuck' , 'shit' ] ) ;
45+ test ( 'legacy relative offsets saved before hand-set edges are still read ' , ( ) => {
46+ const sec = one ( 0.1 , 0 , { '1.000' : { before : 0.2 , after : - 0.1 } } ) ;
47+ same ( [ sec . start , sec . end ] , [ 0.7 , 1.4 ] ) ;
48+ near ( one ( 1 , 0.05 , { } , [ { start : 0.9 , end : 3 } ] ) . start , 0.9 ) ; // clamped to the segment
4349} ) ;
4450
45- test ( 'the reported crossed case is flagged, and reset or widening repairs it' , ( ) => {
46- // "fuck" 238.08-238.20, lead 0.5, start 1.0 s later, end 0.85 s earlier.
47- const word = [ { w :'fuck ' , s :238.08 , e :238.2 } ] ;
51+ test ( 'the reported crossed case is flagged, and widening or reset repairs it' , ( ) => {
52+ // "fuck" 238.08-238.20, lead 0.5, legacy start 1.0 s later, end 0.85 s earlier.
53+ const word = [ { w :'shit ' , s :238.08 , e :238.2 } ] ;
4854 const seg = [ { start :157.74 , end :239.369 } ] ;
49- const broken = { '238.080' : { before :- 1 , after :- 0.85 , skip : false } } ;
50- const [ sec ] = censorSections ( word , [ 'fuck' ] , seg , 0.5 , 0.05 , broken ) ;
55+ const broken = { '238.080' : { before :- 1 , after :- 0.85 } } ;
56+ const sec = one ( 0.5 , 0.05 , broken , seg , word ) ;
5157 assert . equal ( sec . crossed , true ) ;
52- // Shrinking further is refused; widening is allowed.
53- assert . equal ( moveEdge ( broken , sec , 'start' , 0.05 ) , null ) ;
54- assert . notEqual ( moveEdge ( broken , sec , 'start' , - 0.05 ) , null ) ;
55- const [ fixed ] = censorSections ( word , [ 'fuck' ] , seg , 0.5 , 0.05 , resetWord ( broken , '238.080' ) ) ;
56- assert . deepEqual ( [ fixed . crossed , fixed . start , fixed . end ] , [ false , 237.58 , 238.25 ] ) ;
58+ assert . equal ( moveEdge ( broken , sec , 'start' , 0.05 ) , null ) ; // shrinking further: refused
59+ const widened = moveEdge ( broken , sec , 'start' , - 1.3 ) ; // start earlier, by hand
60+ same ( widened , { '238.080' : { after : - 0.85 , start : 237.28 } } ) ;
61+ assert . equal ( one ( 0.5 , 0.05 , widened , seg , word ) . crossed , false ) ;
62+ const fixed = one ( 0.5 , 0.05 , resetWord ( broken , '238.080' ) , seg , word ) ;
63+ same ( [ fixed . crossed , fixed . start , fixed . end ] , [ false , 237.58 , 238.25 ] ) ;
5764} ) ;
5865
59- test ( 'edge moves stop at the minimum length; move shifts both edges ' , ( ) => {
60- const [ sec ] = censorSections ( WORDS , [ 'shit' ] , [ { start : 0 , end : 3 } ] , 0 , 0 , { } ) ; // 0.5 s long, 0.05 s steps
66+ test ( 'sections shrink to 0.01 s; 0.05 s steps stop before reaching zero ' , ( ) => {
67+ let sec = one ( 0 , 0 , { } ) ; // 0.5 s long
6168 let adj = { } ;
62- let current = sec ;
6369 for ( let i = 0 ; i < 20 ; i ++ ) {
64- const next = moveEdge ( adj , current , 'start' , 0.05 ) ;
70+ const next = moveEdge ( adj , sec , 'start' , 0.05 ) ;
6571 if ( ! next ) break ;
6672 adj = next ;
67- [ current ] = censorSections ( WORDS , [ 'shit' ] , [ { start : 0 , end : 3 } ] , 0 , 0 , adj ) ;
73+ sec = one ( 0 , 0 , adj ) ;
6874 }
69- // 0.05 s steps stop at 0.05 s (one more would reach zero); 0.01 s steps go to 0.01 s.
70- assert . ok ( Math . abs ( current . end - current . start - 0.05 ) < 1e-9 ) ;
71- const fine = moveEdge ( adj , current , 'end' , - 0.01 ) ;
72- [ current ] = censorSections ( WORDS , [ 'shit' ] , [ { start :0 , end :3 } ] , 0 , 0 , fine ) ;
73- assert . ok ( Math . abs ( current . end - current . start - 0.04 ) < 1e-9 ) ;
74- const [ moved ] = censorSections ( WORDS , [ 'shit' ] , [ { start :0 , end :3 } ] , 0 , 0 , moveEdge ( { } , sec , 'move' , 0.05 ) ) ;
75- assert . deepEqual ( [ moved . start , moved . end ] , [ 1.05 , 1.55 ] ) ;
76- } ) ;
77-
78- test ( 'per-word moves go past one second' , ( ) => {
79- const [ sec ] = censorSections ( WORDS , [ 'shit' ] , [ { start :0 , end :3 } ] , 0 , 0 , { } ) ;
80- let adj = { } ;
81- let cur = sec ;
82- for ( let i = 0 ; i < 30 ; i ++ ) { adj = moveEdge ( adj , cur , 'end' , 0.05 ) ; [ cur ] = censorSections ( WORDS , [ 'shit' ] , [ { start :0 , end :3 } ] , 0 , 0 , adj ) ; }
83- assert . equal ( adj [ '1.000' ] . after , 1.5 ) ;
84- assert . equal ( cur . end , 3 ) ; // clamped by the segment, not by a nudge limit
75+ near ( sec . end - sec . start , 0.05 ) ;
76+ sec = one ( 0 , 0 , moveEdge ( adj , sec , 'end' , - 0.04 ) ) ;
77+ near ( sec . end - sec . start , 0.01 ) ;
78+ assert . equal ( sec . crossed , false ) ;
8579} ) ;
8680
8781test ( 'a 0.07 s section shrinks to 0.02 s and is still applied' , ( ) => {
8882 const word = [ { w :'shit' , s :80.9 , e :80.95 } ] ;
8983 const seg = [ { start :0 , end :100 } ] ;
90- let [ sec ] = censorSections ( word , [ 'shit' ] , seg , 0.01 , 0.01 , { } ) ; // 80.89-80.96
91- assert . deepEqual ( [ sec . start , sec . end ] , [ 80.89 , 80.96 ] ) ;
84+ let sec = one ( 0.01 , 0.01 , { } , seg , word ) ;
85+ same ( [ sec . start , sec . end ] , [ 80.89 , 80.96 ] ) ;
9286 let adj = { } ;
9387 for ( const [ edge , d ] of [ [ 'start' , 0.01 ] , [ 'start' , 0.01 ] , [ 'end' , - 0.01 ] , [ 'end' , - 0.01 ] , [ 'end' , - 0.01 ] ] ) {
9488 adj = moveEdge ( adj , sec , edge , d ) ;
95- [ sec ] = censorSections ( word , [ 'shit' ] , seg , 0.01 , 0.01 , adj ) ;
89+ sec = one ( 0.01 , 0.01 , adj , seg , word ) ;
9690 }
97- assert . deepEqual ( [ sec . start , sec . end , sec . crossed , sec . tooShort ] , [ 80.91 , 80.93 , false , false ] ) ;
98- assert . equal ( moveEdge ( adj , sec , 'end' , - 0.02 ) , null ) ; // would leave nothing
91+ same ( [ sec . start , sec . end , sec . crossed , sec . tooShort ] , [ 80.91 , 80.93 , false , false ] ) ;
92+ assert . equal ( moveEdge ( adj , sec , 'end' , - 0.02 ) , null ) ; // would leave nothing
93+ } ) ;
94+
95+ test ( 'hand-set edges go well past one second' , ( ) => {
96+ const sec = one ( 0 , 0 , moveEdge ( { } , one ( 0 , 0 , { } ) , 'end' , 1.5 ) ) ;
97+ same ( [ sec . rawEnd , sec . end ] , [ 3 , 3 ] ) ; // clamped by the segment only
98+ } ) ;
99+
100+ test ( 'clip timeline follows segment order' , ( ) => {
101+ const two = [ { w :'shit' , s :1 , e :1.5 } , { w :'shit' , s :5 , e :5.5 } ] ;
102+ const secs = censorSections ( two , [ 'shit' ] , [ { start :4 , end :6 } , { start :0 , end :2 } ] , 0 , 0 , { } ) ;
103+ same ( secs . map ( ( s ) => [ s . key , s . clipStart ] ) , [ [ '5.000' , 1 ] , [ '1.000' , 3 ] ] ) ;
104+ } ) ;
105+
106+ test ( 'word normalisation matches word_censor.normalize' , ( ) => {
107+ assert . equal ( normalizeWord ( "'Scheiße!'" ) , 'scheiße' ) ;
108+ assert . equal ( normalizeWord ( "Don't," ) , "don't" ) ;
109+ same ( parseWords ( ' fuck, ,shit ' ) , [ 'fuck' , 'shit' ] ) ;
99110} ) ;
0 commit comments