Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
47 lines
2.6 KiB
JavaScript
47 lines
2.6 KiB
JavaScript
// Per-clip effects (stored on scene.effects) and the transition on a clip's outgoing edge. The render reads these as they are.
|
|
const ZOOM = { zoom_ratio: 0.15, zoom_frames: 25, zoom_start_frame: 0 };
|
|
|
|
/** Apply a library effect to a scene. Flips, fill and reverse are switches: applying again turns them off. -> false if the id is unknown. */
|
|
export function applyEffect(sc, id, value) {
|
|
const fx = { ...(sc.effects || {}) };
|
|
const zoom = (way) => Object.assign(fx, { zoom: way, zoom_ratio: fx.zoom_ratio ?? ZOOM.zoom_ratio, zoom_frames: fx.zoom_frames ?? ZOOM.zoom_frames, zoom_start_frame: fx.zoom_start_frame ?? ZOOM.zoom_start_frame });
|
|
const ops = {
|
|
zoom_in: () => zoom("in"), zoom_out: () => zoom("out"),
|
|
blur: () => { fx.blur = value; }, fade_in: () => { fx.fade_in = value; }, fade_out: () => { fx.fade_out = value; },
|
|
flip_h: () => { fx.flip_h = !fx.flip_h; }, flip_v: () => { fx.flip_v = !fx.flip_v; },
|
|
fill_frame: () => { fx.fit = fx.fit === "fill" ? "contain" : "fill"; },
|
|
crop: () => { fx.crop_inset = Math.max(0, Math.min(0.4, (+value || 0) / 100)); },
|
|
reverse: () => { fx.reverse = !fx.reverse; },
|
|
reset: () => { for (const k of Object.keys(fx)) delete fx[k]; },
|
|
};
|
|
if (!ops[id]) return false;
|
|
ops[id]();
|
|
sc.effects = fx;
|
|
return true;
|
|
}
|
|
|
|
/** Put a library transition on the clip's outgoing edge. */
|
|
export function applyTransition(sc, item, value) {
|
|
if (!item) return false;
|
|
sc.video_transition = item.type || item.id;
|
|
sc.transition_frames = value != null ? Math.round(value) : Math.round((item.param && item.param.default) || 16);
|
|
return true;
|
|
}
|
|
|
|
export const clearTransition = (sc) => { delete sc.video_transition; delete sc.transition_frames; return true; };
|
|
|
|
const LABELS = [
|
|
["reverse", () => "◀ reverse"], ["flip_h", () => "⇄ flip"], ["flip_v", () => "⇅ flip"],
|
|
["fit", (v) => (v === "fill" ? "fill" : null)], ["crop_inset", (v) => (v > 0 ? `crop ${Math.round(v * 100)}%` : null)],
|
|
["zoom", (v) => (v === "in" ? "zoom in" : v === "out" ? "zoom out" : null)], ["blur", (v) => (v > 0 ? `blur ${(+v).toFixed(2)}` : null)],
|
|
["fade_in", (v) => (v > 0 ? `fade in ${v}s` : null)], ["fade_out", (v) => (v > 0 ? `fade out ${v}s` : null)],
|
|
];
|
|
|
|
/** What is on this clip, as short tags (so a switch can be seen to be on). */
|
|
export function tags(sc) {
|
|
const fx = sc.effects || {}, out = [];
|
|
for (const [key, fmt] of LABELS) { const raw = fx[key]; if (raw !== undefined && raw !== null && raw !== false && raw !== "") { const t = fmt(raw); if (t) out.push(t); } }
|
|
if (sc.video_transition) out.push(`→ ${sc.video_transition}`);
|
|
return out;
|
|
}
|