/* Script to generate test API json from the ComfyUI_examples repo. Requires the repo to be cloned to the tests-ui directory or specified via the EXAMPLE_REPO_PATH env var. */ import chalk from "chalk"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "node:url"; import { getFromPngBuffer } from "@/scripts/metadata/png"; import { getFromFlacBuffer } from "@/scripts/metadata/flac"; import dotenv from "dotenv"; dotenv.config(); const dirname = path.dirname(fileURLToPath(import.meta.url)); const repoPath = process.env.EXAMPLE_REPO_PATH || path.resolve(dirname, "ComfyUI_examples"); const workflowsPath = path.resolve(dirname, "workflows", "examples"); if (!fs.existsSync(repoPath)) { console.error( `ComfyUI_examples repo not found. Please clone this to ${repoPath} or set the EXAMPLE_REPO_PATH env var (see .env_example) and re-run.` ); } if (!fs.existsSync(workflowsPath)) { await fs.promises.mkdir(workflowsPath); } async function* getFiles( dir: string, ...exts: string[] ): AsyncGenerator { const dirents = await fs.promises.readdir(dir, { withFileTypes: true }); for (const dirent of dirents) { const res = path.resolve(dir, dirent.name); if (dirent.isDirectory()) { yield* getFiles(res, ...exts); } else if (exts.includes(path.extname(res))) { yield res; } } } async function validateMetadata(metadata: Record) { const check = (prop: "prompt" | "workflow") => { const v = metadata?.[prop]; if (!v) throw `${prop} not found in metadata`; try { JSON.parse(v); } catch (error) { throw `${prop} invalid json: ${error.message}`; } return v; }; return { prompt: check("prompt"), workflow: check("workflow") }; } async function hasExampleChanged( existingFilePath: string, exampleJson: string ) { return exampleJson !== (await fs.promises.readFile(existingFilePath, "utf8")); } // Example images to ignore as they don't contain workflows const ignore = [ "unclip_sunset.png", "unclip_mountains.png", "inpaint_yosemite_inpaint_example.png", "controlnet_shark_depthmap.png", "controlnet_pose_worship.png", "controlnet_pose_present.png", "controlnet_input_scribble_example.png", "controlnet_house_scribble.png", ]; // Find all existing examples so we can check if any are removed/changed const existing = new Set( (await fs.promises.readdir(workflowsPath, { withFileTypes: true })) .filter((d) => d.isFile()) .map((d) => path.resolve(workflowsPath, d.name)) ); const results = { new: [], changed: [], unchanged: [], missing: [], failed: [], }; let total = 0; for await (const file of getFiles(repoPath, ".png", ".flac")) { const cleanedName = path .relative(repoPath, file) .replaceAll("/", "_") .replaceAll("\\", "_"); if (ignore.includes(cleanedName)) continue; total++; let metadata: { prompt: string; workflow: string }; try { const { buffer } = await fs.promises.readFile(file); switch (path.extname(file)) { case ".png": metadata = await validateMetadata(getFromPngBuffer(buffer)); break; case ".flac": metadata = await validateMetadata(getFromFlacBuffer(buffer)); break; } const outPath = path.resolve(workflowsPath, cleanedName + ".json"); const exampleJson = JSON.stringify(metadata); if (existing.has(outPath)) { existing.delete(outPath); if (await hasExampleChanged(outPath, exampleJson)) { results.changed.push(outPath); } else { // Unchanged, no point in re-saving results.unchanged.push(outPath); continue; } } else { results.new.push(outPath); } await fs.promises.writeFile(outPath, exampleJson, "utf8"); } catch (error) { results.failed.push({ file, error }); } } // Any workflows left in the existing set are now missing, these will want checking and manually removing results.missing.push(...existing); const c = (v: number, gt0: "red" | "yellow" | "green") => chalk[v > 0 ? gt0 : "gray"](v); console.log(`Processed ${chalk.green(total)} examples`); console.log(` ${chalk.gray(results.unchanged.length)} unchanged`); console.log(` ${c(results.changed.length, "yellow")} changed`); console.log(` ${c(results.new.length, "green")} new`); console.log(` ${c(results.missing.length, "red")} missing`); console.log(` ${c(results.failed.length, "red")} failed`); if (results.missing.length) { console.log(); console.log( chalk.red( "The following examples are missing and require manual reviewing & removal:" ) ); for (const m of results.missing) { console.log(m); } } if (results.failed.length) { console.log(); console.log(chalk.red("The following examples failed to extract:")); for (const m of results.failed) { console.log(m.file); console.error(m.error); console.log(); } }