import assert from 'node:assert/strict'; import test from 'node:test'; import { appendSpeechChunkCues, splitSpeechPassages } from './subtitle-generation-chunks'; test('long coverage cuts at nearby speech starts instead of leaving a quiet lead-in', () => { const chunks = splitSpeechPassages( [{ startSeconds: 544.418, endSeconds: 581.581 }], [563.928], [555.07, 567.23, 579.91], ); assert.deepEqual(chunks, [ { startSeconds: 544.418, endSeconds: 567.48 }, { startSeconds: 566.98, endSeconds: 581.581 }, ]); }); test('short audible passages stay intact even with several detected speech starts', () => { assert.deepEqual( splitSpeechPassages( [{ startSeconds: 876.897, endSeconds: 897.812 }], [], [876.9, 881.15, 893.95, 897.99], ), [{ startSeconds: 876.897, endSeconds: 897.812 }], ); }); test('speech anchors outside retained coverage cannot extend a chunk across a silent gap', () => { const chunks = splitSpeechPassages( [{ startSeconds: 100, endSeconds: 142 }], [118], [90, 142, 144], ); assert.deepEqual(chunks, [ { startSeconds: 100, endSeconds: 118.25 }, { startSeconds: 117.75, endSeconds: 138.25 }, { startSeconds: 137.75, endSeconds: 142 }, ]); }); test('long speech splits near a pause with context on both sides and no lost audio', () => { assert.deepEqual(splitSpeechPassages([{ startSeconds: 100, endSeconds: 145 }], [105, 118, 137]), [ { startSeconds: 100, endSeconds: 118.25 }, { startSeconds: 117.75, endSeconds: 137.25 }, { startSeconds: 136.75, endSeconds: 145 }, ]); }); test('uninterrupted speech retains overlapping context without crossing omitted gaps', () => { const chunks = splitSpeechPassages([ { startSeconds: 0, endSeconds: 60 }, { startSeconds: 100, endSeconds: 100.15 }, ]); assert.deepEqual(chunks, [ { startSeconds: 0, endSeconds: 20.25 }, { startSeconds: 19.75, endSeconds: 40.25 }, { startSeconds: 39.75, endSeconds: 60 }, { startSeconds: 100, endSeconds: 100.15 }, ]); }); test('speech fitting one Whisper window stays intact instead of cutting a sentence at 20 seconds', () => { const passage = { startSeconds: 251.71, endSeconds: 275.01 }; assert.deepEqual(splitSpeechPassages([passage], [271.327]), [passage]); }); test('chunk stitching ignores punctuation differences without merging separate repetitions', () => { const cues = [{ startTime: 19.7, endTime: 21.2, text: 'ありがとう' }]; appendSpeechChunkCues(cues, [ { startTime: 19.8, endTime: 21.3, text: 'ありがとう。' }, { startTime: 22, endTime: 23, text: 'ありがとう!' }, ]); assert.deepEqual(cues, [ { startTime: 19.7, endTime: 21.3, text: 'ありがとう' }, { startTime: 22, endTime: 23, text: 'ありがとう!' }, ]); }); test('chunk stitching removes matching overlap cues but retains repeated dialogue', () => { const cues = [{ startTime: 19.7, endTime: 20.2, text: 'はい' }]; appendSpeechChunkCues(cues, [ { startTime: 19.8, endTime: 20.3, text: 'はい' }, { startTime: 21, endTime: 21.5, text: 'はい' }, { startTime: 21.4, endTime: 22, text: 'はい' }, ]); assert.deepEqual(cues, [ { startTime: 19.7, endTime: 20.3, text: 'はい' }, { startTime: 21, endTime: 21.5, text: 'はい' }, { startTime: 21.4, endTime: 22, text: 'はい' }, ]); }); test('chunk stitching matches repeated text to the greatest overlap without leaving a duplicate', () => { const cues = [ { startTime: 10, endTime: 14, text: 'はい' }, { startTime: 13, endTime: 20, text: 'はい' }, ]; appendSpeechChunkCues(cues, [ { startTime: 12, endTime: 21, text: 'はい' }, { startTime: 22, endTime: 23, text: 'はい' }, ]); assert.deepEqual(cues, [ { startTime: 10, endTime: 14, text: 'はい' }, { startTime: 12, endTime: 21, text: 'はい' }, { startTime: 22, endTime: 23, text: 'はい' }, ]); });