Files
aigen/shared/studio2/prompt.mjs
T

45 lines
3.9 KiB
JavaScript

export const cameraMoves = ['hold','dolly-in','dolly-out','pan-left','pan-right','tilt-up','tilt-down','zoom-in','zoom-out','orbit-left','orbit-right']
export const soundscapeOptions = ['quiet room','distant birds','fabric rustle','footsteps','soft cloth','city hush','rain on glass','room tone only','no extra SFX']
export function normalizeBeat(beat = {}) {
let moves = [...new Set((beat.moves || []).filter(m => cameraMoves.includes(m)))]
if (moves.includes('hold') || !moves.length) moves = ['hold']
const duration = Number(beat.durationSec)
return { moves, amplitude: ['subtle','moderate','large'].includes(beat.amplitude) ? beat.amplitude : 'subtle', speed: ['slow','medium','fast'].includes(beat.speed) ? beat.speed : 'slow', durationSec: beat.durationSec == null ? null : Number.isFinite(duration) ? Math.max(.5, Math.round(duration * 2) / 2) : null }
}
export function toggleMove(beat, move) {
if (!cameraMoves.includes(move)) return normalizeBeat(beat)
if (move === 'hold') return normalizeBeat({...beat,moves:['hold']})
const moves = beat.moves.filter(m => m !== 'hold')
return normalizeBeat({...beat,moves:moves.includes(move) ? moves.filter(m => m !== move) : [...moves,move]})
}
export function sectionsFrom(value = {}) {
if (typeof value === 'string') value = {action:value}
value ||= {}
const s = Object.fromEntries(['action','camera','soundscape','music','locks'].map(k => [k,typeof value[k] === 'string' ? value[k] : '']))
const legacy = value.cameraChip
s.cameraBeats = Array.isArray(value.cameraBeats) ? value.cameraBeats.map(normalizeBeat) : legacy ? [normalizeBeat({...legacy,moves:[legacy.family === 'static' ? 'hold' : legacy.family.replaceAll(' ','-')]})] : []
s.soundscapeChips = [...new Set((value.soundscapeChips || []).filter(x => soundscapeOptions.includes(x)))]
s.soundscapeLevel = value.soundscapeLevel === 'crush' ? 'crush' : 'normal'
const d = typeof value.dialogue === 'object' && value.dialogue ? value.dialogue : {mode:value.dialogue ? 'spoken to camera' : 'none',line:value.dialogue || ''}
s.dialogue = {mode:['none','spoken to camera','spoken off-screen','whisper'].includes(d.mode) ? d.mode : 'none',language:String(d.language || 'English'),speaker:String(d.speaker || 'S1'),line:String(d.line || '')}
s.musicChip = ['N/A','none','low underscore'].includes(value.musicChip) ? value.musicChip : 'N/A'
return s
}
export function compileSections(value) {
const s = sectionsFrom(value)
const camera = s.cameraBeats.length ? ['One continuous take.', ...s.cameraBeats.map((b,i) => {
const hold = b.moves[0] === 'hold' || b.moves.includes('hold')
if (hold) {
return `${i ? 'Then' : 'First'}, hold the camera locked on a tripod.`
}
const move = b.moves.map(m=>m.replaceAll('-',' ')).join(' and ') + (b.moves.length > 1 ? ' simultaneously' : '')
const timing = b.durationSec == null ? 'through the end of the remaining clip' : i === 0 ? `for the first ${b.durationSec} seconds` : `for ${b.durationSec} seconds`
return `${i ? 'Then' : 'First'}, ${move}, ${{subtle:'small',moderate:'medium',large:'large'}[b.amplitude]} amplitude, ${b.speed} speed, ${timing}.`
}), 'No cuts. No new setup.'].join(' ') : s.camera
const sound = [s.soundscapeChips.length ? `${s.soundscapeChips.join(', ')}.${s.soundscapeLevel === 'crush' ? ' Crushed soundscape dynamics.' : ''}` : '',s.soundscape].filter(Boolean).join(' ')
const d=s.dialogue, line=d.line.trim()
const dialogue = d.mode !== 'none' && line ? `${d.mode === 'spoken off-screen' ? 'Off-screen voice; on-screen lips remain closed. ' : ''}<Subject 1> (${d.speaker}) ${d.mode === 'whisper' ? 'whispers' : 'says'} <d>[${d.language}]${line}</d>` : ''
const music = s.music || (s.musicChip === 'N/A' ? 'non_diegetic_music: N/A' : s.musicChip === 'none' ? 'No non-diegetic music.' : 'A low non-diegetic musical underscore.')
return [s.action,camera && `Camera: ${camera}`,sound && `Soundscape: ${sound}`,dialogue,music,s.locks && `Locks: ${s.locks}`].filter(Boolean).join('\n\n')
}