Add local background removal and mask tools

This commit is contained in:
achraf
2026-06-07 23:32:26 +02:00
parent f5364bf7a8
commit e29dc6fd6d
23 changed files with 2508 additions and 21 deletions

View File

@@ -0,0 +1,88 @@
/**
* Self-host the @imgly/background-removal model + wasm assets.
*
* @imgly/background-removal is AGPL-3.0. By default it streams its ONNX model
* and onnxruntime-web wasm binaries from staticimgly.com at runtime. To keep
* Plimi's privacy promise (nothing leaves the user's browser, no third-party
* fetches), we mirror the *version-matched* asset bundle into `public/imgly/`
* and point the library at our own origin via `config.publicPath`.
*
* The assets are large (~300MB across all model variants) so they are NOT
* committed to git — this script is run on `predev` / `prebuild` and is
* idempotent: it only downloads chunks that are missing.
*/
import { mkdir, readFile, writeFile, stat } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import path from "node:path";
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const OUT_DIR = path.join(ROOT, "public", "imgly");
// Keep the data version in lock-step with the installed library version,
// otherwise the resources.json format / model keys won't match the runtime.
const { version } = JSON.parse(
await readFile(
path.join(ROOT, "node_modules/@imgly/background-removal/package.json"),
"utf8"
)
);
const BASE = `https://staticimgly.com/@imgly/background-removal-data/${version}/dist/`;
async function exists(p) {
try {
await stat(p);
return true;
} catch {
return false;
}
}
async function download(url, dest) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
}
const buf = Buffer.from(await res.arrayBuffer());
await writeFile(dest, buf);
return buf.length;
}
async function main() {
await mkdir(OUT_DIR, { recursive: true });
console.log(`[imgly] Mirroring background-removal assets v${version}`);
const resourcesPath = path.join(OUT_DIR, "resources.json");
await download(new URL("resources.json", BASE).toString(), resourcesPath);
const resources = JSON.parse(await readFile(resourcesPath, "utf8"));
const chunkNames = new Set();
for (const entry of Object.values(resources)) {
for (const chunk of entry.chunks ?? []) chunkNames.add(chunk.name);
}
let downloaded = 0;
let skipped = 0;
let bytes = 0;
for (const name of chunkNames) {
const dest = path.join(OUT_DIR, name);
if (await exists(dest)) {
skipped++;
continue;
}
bytes += await download(new URL(name, BASE).toString(), dest);
downloaded++;
if (downloaded % 10 === 0) {
console.log(`[imgly] ${downloaded} chunks downloaded...`);
}
}
console.log(
`[imgly] Done: ${downloaded} downloaded, ${skipped} cached ` +
`(${(bytes / 1024 / 1024).toFixed(1)} MB new) → public/imgly/`
);
}
main().catch((err) => {
console.error("[imgly] Asset mirror failed:", err);
process.exit(1);
});