Add output camera framing

This commit is contained in:
th committed 2026-06-07 23:12:46 +02:00
1 parent 616ced17ce
commit 23a66c6fdf
19 files changed
+1955 -43

No files matched your search

+33
View File
@@ -71,6 +71,39 @@ function validateRenderRequest(scene, options = {}, limits = DEFAULT_LIMITS) {
}
}
}
if (scene.camera != null) {
const camera = scene.camera
if (typeof camera !== 'object') return { ok: false, error: 'camera must be an object' }
if (camera.enabled) {
const cw = Number(camera.base?.width)
const ch = Number(camera.base?.height)
if (!Number.isFinite(cw) || cw <= 0 || !Number.isFinite(ch) || ch <= 0) {
return { ok: false, error: 'enabled camera must have positive width and height' }
}
}
if (camera.keyframes != null) {
if (!Array.isArray(camera.keyframes)) {
return { ok: false, error: 'camera keyframes must be an array' }
}
keyframes += camera.keyframes.length
if (keyframes > limits.maxKeyframes) {
return { ok: false, error: `scene has more than ${limits.maxKeyframes} keyframes` }
}
for (const kf of camera.keyframes) {
if (!Number.isFinite(Number(kf?.time))) {
return { ok: false, error: 'keyframe times must be finite numbers' }
}
const w = kf?.props?.width
const h = kf?.props?.height
if (w != null && (!Number.isFinite(Number(w)) || Number(w) <= 0)) {
return { ok: false, error: 'camera keyframe width must be positive' }
}
if (h != null && (!Number.isFinite(Number(h)) || Number(h) <= 0)) {
return { ok: false, error: 'camera keyframe height must be positive' }
}
}
}
}
const duration = Number(scene.duration)
if (!Number.isFinite(duration) || duration <= 0 || duration > limits.maxDuration) {
return { ok: false, error: `duration must be between 0 and ${limits.maxDuration} seconds` }
+31
View File
@@ -56,6 +56,37 @@ test('rejects invalid crf and preset', () => {
assert.equal(validateRenderRequest(scene(), { preset: 'turbo' }).ok, false)
})
test('validates enabled camera data', () => {
const valid = validateRenderRequest(scene({
camera: {
enabled: true,
base: { x: 0, y: 0, width: 320, height: 180 },
keyframes: [{ time: 0.5, props: { x: 10 }, easing: 'linear' }],
},
}))
assert.equal(valid.ok, true)
const invalidSize = validateRenderRequest(scene({
camera: {
enabled: true,
base: { x: 0, y: 0, width: 0, height: 180 },
keyframes: [],
},
}))
assert.equal(invalidSize.ok, false)
assert.match(invalidSize.error, /camera/)
const invalidKf = validateRenderRequest(scene({
camera: {
enabled: true,
base: { x: 0, y: 0, width: 320, height: 180 },
keyframes: [{ time: Number.NaN, props: {}, easing: 'linear' }],
},
}))
assert.equal(invalidKf.ok, false)
assert.match(invalidKf.error, /keyframe/)
})
test('computes playback range from in/out or last keyframe', () => {
assert.deepEqual(playbackRange(scene({ inTime: 0.25, outTime: 1.5 })), { start: 0.25, end: 1.5 })
assert.deepEqual(playbackRange(scene()), { start: 0, end: 1 })