This commit is contained in:
Codex
2026-11-13 11:47:20 +08:00
parent 702671e61b
commit f6546ecaa6
3 changed files with 39 additions and 7 deletions
-6
View File
@@ -69,12 +69,6 @@ coverage/
*.[oO][gG][vV]
# Runtime databases, secrets and local configuration.
*.db
*.db-*
*.sqlite
*.sqlite-*
*.sqlite3
*.sqlite3-*
*.log
*.bak
*.pem
@@ -1,6 +1,6 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {buildStoryboardPlan,dialogueTimingProblems,sectionBatchProblems,sectionCoverage,spokenLiterals,splitSpeech,speechSeconds,fitDraftDialogueTiming} from '../src/utils/storyboard-coverage.js'
import {buildStoryboardPlan,dialogueTimingProblems,sectionBatchProblems,sectionCoverage,spokenLiterals,splitSpeech,speechSeconds,fitDraftDialogueTiming,coverageAdvanced,resumableDraftPrefix} from '../src/utils/storyboard-coverage.js'
const dialogue='请先检查每一扇窗户是否关好,再把桌上的图纸按照编号摆放整齐,确认材料数量以后我们才能开始修理屋顶。'
test('long dialogue cannot fit in a four-second timeline slot even with a long total duration',()=>{
@@ -70,3 +70,36 @@ test('coverage requires repeated speech in original order',()=>{
shot.video_prompt+='甲说:「准备好了。」'
assert.equal(sectionCoverage(section,[shot]).ready,true)
})
test('shouting and cries with intervening performance details are spoken dialogue',()=>{
for(const cue of ['怒吼','大叫','咆哮','惊呼','嘀咕','低语','哭诉','惨叫着猛甩手,脸上满是惊恐']) {
assert.deepEqual(spokenLiterals(`0-9秒:@工人 ${cue}:「先关窗户,再检查工具!」`),['先关窗户,再检查工具!'])
}
const [section]=buildStoryboardPlan('工人:先关窗户,再检查工具!\n同事:啊!')
const shot={shot_number:1,duration:12,source_ids:section.units.map(u=>u.id),video_prompt:'0-8秒:@工人 怒吼:「先关窗户,再检查工具!」\n8-12秒:@同事 惨叫着后退,手遮住脸:「啊!」'}
assert.equal(sectionCoverage(section,[shot]).ready,true)
assert.deepEqual(dialogueTimingProblems(shot),[])
assert.ok(dialogueTimingProblems({...shot,duration:8,video_prompt:`0-8秒:@工人 怒吼:「${dialogue}」`}).length)
})
test('onscreen quotations cannot satisfy dialogue coverage',()=>{
for(const p of ['门牌写着:「先关窗户。」','@工人 指向显示屏,屏幕显示:「先关窗户。」','旁白停止,字幕:「先关窗户。」'])assert.deepEqual(spokenLiterals(p),[])
})
test('repeated shots do not count as progress once time and source quotas are met',()=>{
const [section]=buildStoryboardPlan('工人:先关窗户,再检查工具。')
const shot={shot_number:1,duration:12,source_ids:section.units.map(u=>u.id),video_prompt:'0-12秒:工人看着工具。'}
const before=sectionCoverage(section,[shot])
assert.equal(coverageAdvanced(before,sectionCoverage(section,[shot,{...shot,shot_number:2}])),false)
assert.equal(coverageAdvanced(before,sectionCoverage(section,[{...shot,video_prompt:'0-12秒:工人怒吼:「先关窗户,再检查工具。」'}])),true)
})
test('resuming reuses only validated complete scenes, never inflated failed retries',()=>{
const sections=buildStoryboardPlan('## S01 | 修复室\n工人:工具准备好了。\n## S02 | 屋顶\n同事:先检查安全绳。')
const shots=[{shot_number:1,source_section:0,duration:12,source_ids:sections[0].units.map(u=>u.id),video_prompt:'0-12秒:工人说:「工具准备好了。」'},...Array.from({length:20},(_,i)=>({shot_number:i+2,source_section:1,duration:12,source_ids:sections[1].units.map(u=>u.id),video_prompt:'0-12秒:同事检查工具。'}))]
const prefix=resumableDraftPrefix(sections,shots,1)
assert.equal(prefix.sectionIndex,1);assert.deepEqual(prefix.drafts,[shots[0]])
assert.equal(shots.length,21)
shots[0].video_prompt='0-12秒:工人走进房间。'
assert.equal(resumableDraftPrefix(sections,shots,1).sectionIndex,0)
})
@@ -22,6 +22,11 @@ test('screen text instructions are still rejected, including quoted instructions
assert.ok(storyboardSegmentProblems({shot_number:1,duration:9,video_prompt:p},[]).some(p=>p.includes('画面里出现文字')))
}
})
test('shouted words use the same speech classifier as timing and coverage',()=>{
const p='0-9秒:@高天明怒吼:「血红文字写着什么?」画面中不出现任何文字。'
assert.deepEqual(storyboardSegmentProblems({shot_number:1,duration:9,video_prompt:p,character_ids:[2]},characters),[])
})
test('timeline and character binding checks still apply',()=>{
const problems=storyboardSegmentProblems({shot_number:1,duration:12,video_prompt:'0-9秒:@高天明说:「血红文字。」'},characters)
assert.ok(problems.some(p=>p.includes('时间轴')))