diff --git a/README.md b/README.md index e1f99279..693a5734 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,18 @@ A set of ComfyUI nodes for experimenting with video generation workflows based o > > With this said - FunPack is provided "as is". Stable, uninterrupted work is NOT guaranteed even on main branch. But that doesn't mean bugs won't be fixed - you are free to file a bug report and I will most likely fix it in the nearest time. +## What it looks like + +FunPack 3.x ships the **Cutting Room** - a full non-linear video editor living in your browser, inside ComfyUI. A real timeline with per-scene prompts, ratings and audio lanes, a program monitor, a media bin, and one-click generation driving FunPack Studio + the Scene Chain Sampler - no graph wiring required. + +![FunPack Cutting Room - timeline, preview monitor, media bin, and scene inspector](docs/images/cutting-room.png) + +| The Composer - global prompt, shortcuts, split markers, `$variables` | Engine Settings - Studio + Chain Sampler knobs without touching a graph | +| --- | --- | +| ![Composer window](docs/images/composer.png) | ![Engine Settings dialog](docs/images/engine-settings.png) | + +Want a guided look at every panel? Open the editor and press **Help ▸ Welcome tour** - the same tour also auto-generates a full annotated gallery and walkthrough video via [`tools/showcase`](tools/showcase). + ## Dev and other non-main branches The `dev` branch is intended for testing unfinished changes, implementing new logic and basically, flipping everything just because I can. It can be broken, renamed, or changed without warning. diff --git a/docs/images/composer.png b/docs/images/composer.png new file mode 100644 index 00000000..246c09b1 Binary files /dev/null and b/docs/images/composer.png differ diff --git a/docs/images/cutting-room.png b/docs/images/cutting-room.png new file mode 100644 index 00000000..d1d5091e Binary files /dev/null and b/docs/images/cutting-room.png differ diff --git a/docs/images/engine-settings.png b/docs/images/engine-settings.png new file mode 100644 index 00000000..63ed99c0 Binary files /dev/null and b/docs/images/engine-settings.png differ diff --git a/tools/showcase/capture_clean.mjs b/tools/showcase/capture_clean.mjs new file mode 100644 index 00000000..27acd292 --- /dev/null +++ b/tools/showcase/capture_clean.mjs @@ -0,0 +1,86 @@ +// Clean (no tour card, no spotlight dim) hero shots of the Cutting Room, using the +// same ?mode=tour mocked demo boot as capture.mjs — no ComfyUI, no models, no GPU. +// Output: out/hero-*.png. Run after capture.mjs (or standalone): `npm run capture:clean`. + +import { chromium } from "playwright"; +import http from "node:http"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const FRONTEND = path.resolve(__dirname, "../../movie_editor/frontend"); +const OUT = path.resolve(__dirname, "out"); +const VIEW = { width: 1440, height: 900 }; + +const MIME = { + ".html": "text/html", ".js": "text/javascript", ".css": "text/css", + ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png", + ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", + ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".map": "application/json", +}; + +function serve(dir) { + const server = http.createServer((req, res) => { + const urlPath = decodeURIComponent((req.url || "/").split("?")[0]); + const fp = path.normalize(path.join(dir, urlPath === "/" ? "/index.html" : urlPath)); + if (!fp.startsWith(dir)) { res.writeHead(403); return res.end("forbidden"); } + fs.readFile(fp, (err, data) => { + if (err) { res.writeHead(404); return res.end("not found"); } + res.writeHead(200, { "content-type": MIME[path.extname(fp).toLowerCase()] || "application/octet-stream" }); + res.end(data); + }); + }); + return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve(server))); +} + +async function main() { + fs.mkdirSync(OUT, { recursive: true }); + const server = await serve(FRONTEND); + const { port } = server.address(); + const browser = await chromium.launch(); + const page = await browser.newPage({ viewport: VIEW, deviceScaleFactor: 2 }); + await page.goto(`http://127.0.0.1:${port}/index.html?mode=tour`, { waitUntil: "load" }); + await page.waitForFunction( + () => window.Store && document.getElementById("workspace"), + null, { timeout: 30000 }, + ); + await page.waitForTimeout(1800); + + // If the tour auto-opened, strip its overlay — the mocked demo data stays. + const strip = () => page.evaluate(() => document.getElementById("tour-root")?.remove()); + await strip(); + + // 1. Hero: the full workspace with the first generative clip selected. + await page.evaluate(() => { + const first = (window.Store.get().project?.scenes || [])[0]; + if (first) window.Store.selectScene(first.id); + }); + await page.waitForTimeout(600); + await strip(); + await page.screenshot({ path: path.join(OUT, "hero-cutting-room.png") }); + console.log("hero-cutting-room.png"); + + // 2. Composer: global prompt + shortcut/split libraries in the floating window. + await page.evaluate(() => { if (!window.Composer.isOpen()) window.Composer.toggle(); }); + await page.waitForTimeout(700); + await strip(); + await page.screenshot({ path: path.join(OUT, "hero-composer.png") }); + console.log("hero-composer.png"); + + // 3. Engine Settings: Studio + Chain Sampler knobs without touching a graph. + await page.evaluate(() => { + if (window.Composer.isOpen()) window.Composer.toggle(); + window.EngineSettingsModal.open(); + }); + await page.waitForTimeout(700); + await strip(); + await page.screenshot({ path: path.join(OUT, "hero-engine-settings.png") }); + console.log("hero-engine-settings.png"); + + await browser.close(); + server.close(); + console.log(`Done — clean shots in ${OUT}`); +} + +main().catch((e) => { console.error(e); process.exit(1); }); diff --git a/tools/showcase/package.json b/tools/showcase/package.json index 29d55dd8..6a41ed70 100644 --- a/tools/showcase/package.json +++ b/tools/showcase/package.json @@ -5,7 +5,8 @@ "version": "1.0.0", "description": "Auto-generate the FunPack Cutting Room feature showcase (annotated gallery + walkthrough video) by driving the built-in in-app tour. No models or GPU required.", "scripts": { - "capture": "node capture.mjs" + "capture": "node capture.mjs", + "capture:clean": "node capture_clean.mjs" }, "dependencies": { "playwright": "^1.50.0"