← Files Pray Production StudioARCHIVED FILE
skills/pps-ads/packages/remotion-kit/src/ad/audio-plan.ts
1.7 KB · Oct 8, 2026 · 06:29 UTC
import { clipSecondsFor } from '../grammar'
import { frames } from '../motion/timing'
/** The fields the timing plan reads off a beat; kept import-free of React so the unit tests can load it. */
interface TimedBeat {
start: number
end: number
clipUrl?: string
voiceoverUrl?: string
voiceoverSeconds?: number
}
export interface FrameWindow {
start: number
end: number
}
/**
* Where the music ducks under voiceover, in frames. A take starts on its beat but is not cut at the
* beat's end: it plays for its own length (`voiceoverSeconds`, filled server-side from the take), or,
* when that length is unknown, to the end of the ad, so the duck never lifts mid-sentence.
*/
export function voiceoverWindows(spec: { lengthSeconds: number; beats: TimedBeat[] }): FrameWindow[] {
const last = frames(spec.lengthSeconds)
return spec.beats
.filter((beat) => beat.voiceoverUrl)
.map((beat) => {
const seconds = beat.voiceoverSeconds ?? spec.lengthSeconds - beat.start
return { start: frames(beat.start), end: Math.min(last, frames(beat.start + seconds)) }
})
}
/** Where a footage clip plays with its own sound, in frames: the music sits lower there. */
export function footageWindows(spec: { beats: TimedBeat[] }): FrameWindow[] {
return spec.beats
.filter((beat) => beat.clipUrl)
.map((beat) => ({ start: frames(beat.start), end: frames(beat.end) }))
}
/**
* Frames to skip at the head of a footage clip. A clip is generated at clipSecondsFor(beat) seconds,
* longer than its beat, and its action peaks at the end, so the beat plays the clip's LAST seconds.
*/
export function clipTrimFrames(beatSeconds: number): number {
return frames(clipSecondsFor(beatSeconds) - beatSeconds)
}
SHA-256: 5038c42373806cf1d2f01e36322391cb35068b6d49a1f200fb6c352a62343da0