Shot 1 can run text-to-video; later shots still extend from the last frame. Recommend output is also normalized into shot-script cards. Co-authored-by: Cursor <cursoragent@cursor.com>
63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
import { parseShotScript } from './parseShots'
|
||
|
||
export function parseEditPasses(text: string): string[] | null {
|
||
const src = String(text || '').replace(/\r\n/g, '\n')
|
||
const marker = /^[ \t]*---pass\s+(\d+)---[ \t]*$/gim
|
||
const markers = [...src.matchAll(marker)]
|
||
if (!markers.length) return null
|
||
const parts: string[] = []
|
||
const preamble = src.slice(0, markers[0].index || 0).trim()
|
||
for (let i = 0; i < markers.length; i++) {
|
||
const from = (markers[i].index || 0) + markers[i][0].length
|
||
const to = i + 1 < markers.length ? (markers[i + 1].index || src.length) : src.length
|
||
const body = src.slice(from, to).trim()
|
||
if (body) parts.push(body)
|
||
}
|
||
if (!parts.length) return null
|
||
if (preamble && preamble.length > 40) {
|
||
parts[0] = `${preamble}\n\n${parts[0]}`
|
||
}
|
||
return parts
|
||
}
|
||
|
||
export function looksLikeShotScript(text: string) {
|
||
return /^[ \t]*shot\s+[2-9]\d*\b/im.test(String(text || ''))
|
||
}
|
||
|
||
function stripFences(text: string) {
|
||
return String(text || '')
|
||
.replace(/^\s*```(?:[\w-]+)?\s*\n?/, '')
|
||
.replace(/\n?```\s*$/, '')
|
||
.trim()
|
||
}
|
||
|
||
function splitInlineShotMarkers(text: string) {
|
||
return String(text || '')
|
||
.replace(/\r\n/g, '\n')
|
||
.replace(/(^|[^A-Za-z])shot\s+(\d+)\s*[:.)\-]?\s*/gi, '$1\n\nshot $2\n')
|
||
.replace(/\n{3,}/g, '\n\n')
|
||
.trim()
|
||
}
|
||
|
||
function formatShotBody(body: string) {
|
||
let text = String(body || '').trim()
|
||
text = text.replace(/^\s*[:.\-–)]\s*/, '')
|
||
text = text.replace(/\s*\[SHOT CONFIGURATION\]\s*/gi, '\n\n[SHOT CONFIGURATION]\n')
|
||
text = text.replace(/\s*\[SUBJECT DIRECTION(?:\s*&\s*ACTION)?\]\s*/gi, '\n\n[SUBJECT DIRECTION & ACTION]\n')
|
||
text = text.replace(/\s*\[AUDIO CUES\]\s*/gi, '\n\n[AUDIO CUES]\n')
|
||
text = text.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim()
|
||
return text
|
||
}
|
||
|
||
export function normalizeShotScript(text: string) {
|
||
const src = splitInlineShotMarkers(stripFences(text))
|
||
const shots = parseShotScript(src)
|
||
if (!shots.length) return src
|
||
if (shots.length === 1 && !/^[ \t]*shot\s+\d+\b/im.test(src)) {
|
||
return formatShotBody(shots[0].prompt)
|
||
}
|
||
return shots
|
||
.map(shot => `shot ${shot.n}\n${formatShotBody(shot.prompt)}`.trim())
|
||
.join('\n\n')
|
||
}
|