Files
olivv-cs-ComfyUI-FunPack/app/ui/features/render/index.js
T
Claude d42bcd269f Render: playing at the clips' real frame rate is an option (Settings), off by default
The final video keeps the project's frame rate unless 'Keep the clips' own frame rate' is switched on.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tw9fjXAUznBpkWSZm9vtPL
2026-11-04 07:09:45 +00:00

47 lines
2.5 KiB
JavaScript

// Render: stitch the cut into one file and keep it in the media bin.
import { offer } from "../../shell/actions.js";
import { composer as c } from "../../composer/composer.js";
import { call } from "../../shell/api.js";
import { clipSpecs } from "./clips.js";
import { RENDER, runJob } from "./job.js";
export default {
id: "render",
mount: "timeline.actions",
needs: ["project"],
modules: ["render"],
setup({ host, app }) {
const p = app.project;
let busy = false, node = null;
const tell = (text) => c.toast.warn({ text });
async function render() {
const open = p.project, { clips, missing } = clipSpecs(open);
if (!clips.length) return tell("Nothing to render: generate at least one clip first.");
if (missing) tell(`${missing} scene(s) have no render yet and are left out.`);
busy = true; draw();
try {
await p.flush();
const state = await runJob(`${RENDER}/projects/${open.id}/render`, { clips, real_fps: p.pref("renderRealFps", false) === true });
if (state.state !== "done") return tell(state.detail || "The render failed.");
const { media } = await call("POST", `${RENDER}/import-clip`, { clip: state.media });
if (app.say) app.say("media"); // the bin shows it now
c.toast.good({ text: `Rendered: saved to the media bin as ${media.name}.` });
} catch (err) { tell(err.message); } finally { busy = false; draw(); }
}
function draw() {
const next = c.button.sm({ label: busy ? "Rendering…" : "⧉ Render", tone: "render", title: "Join the generated clips into one video and save it in the media bin", disabled: busy || !p.project, onClick: render }).node;
if (node) node.replaceWith(next); else host.append(next);
node = next;
}
app.editorSettings.push(() => c.region.stack({ gap: "md", children: [
c.label.section({ text: "Render" }),
c.toggle.default({ label: "Keep the clips' own frame rate", hint: "Off: the final video plays at the project's frame rate. On: it plays at the fastest rate the clips really have, for example after Interpolate, so no frames are dropped.",
checked: p.pref("renderRealFps", false) === true, onChange: (v) => p.setPref("renderRealFps", v) })] }));
draw();
const offered = offer(app, { id: "render-final", label: "Render", icon: "⧉", run: () => { if (!busy && p.project) render(); } });
const off = app.on(() => { if (Boolean(p.project) === node.disabled) draw(); });
return () => { off(); offered(); };
},
};