Compare commits
13
Commits
3a977c7438
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bd87dbf23 | ||
|
|
608d6958b1 | ||
|
|
4e43728583 | ||
|
|
3f4a6b754c | ||
|
|
f49718a260 | ||
|
|
b4a790473c | ||
|
|
e96d7cc204 | ||
|
|
eca834a7f0 | ||
|
|
b8523cb729 | ||
|
|
851e255641 | ||
|
|
b70f95ede5 | ||
|
|
f58c96c20f | ||
|
|
668728348b |
@@ -1,4 +1,4 @@
|
||||
# Caddy 2 Configuration for EditmaskwithAI
|
||||
# Caddy 2 Configuration for PaintPlus
|
||||
# ==========================================
|
||||
#
|
||||
# SETUP INSTRUCTIONS:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# =============================================================================
|
||||
# EditmaskwithAI — GPU Container (NVIDIA CUDA)
|
||||
# PaintPlus — GPU Container (NVIDIA CUDA)
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.gpu.yml up --build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# EditmaskwithAI
|
||||
# PaintPlus
|
||||
|
||||
A self-hosted, web-based AI photo editor. Paint over any object, describe what you want, and the AI replaces just that region — every pixel outside your selection stays untouched.
|
||||
|
||||
@@ -7,11 +7,11 @@ A self-hosted, web-based AI photo editor. Paint over any object, describe what y
|
||||
### GPU machine (recommended — free inference, best quality)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/outis1one/editmaskwithai
|
||||
cd editmaskwithai
|
||||
git clone https://github.com/outis1one/paintplus
|
||||
cd paintplus
|
||||
|
||||
# One-time setup: installs nvidia-container-toolkit, configures Docker,
|
||||
# and sets up a permanent DNS fix so the container can download models.
|
||||
# sets up a permanent DNS fix, and prefetches all AI models on the host.
|
||||
chmod +x install-local-gpu.sh
|
||||
./install-local-gpu.sh
|
||||
|
||||
@@ -22,15 +22,15 @@ chmod +x bring-up-local-gpu.sh
|
||||
|
||||
Open **http://localhost:3080**
|
||||
|
||||
**First startup downloads the AI model for your GPU (~13 GB, one time).** Models are cached in `./data/hf_cache/` and survive rebuilds.
|
||||
**Models (~13 GB total, one time) download automatically on the host**, outside Docker — both scripts call `./prefetch-models.sh` for you, since in-container DNS is unreliable on some hosts. They're cached in `./data/hf_cache/` and `./data/models/`, and survive rebuilds.
|
||||
|
||||
---
|
||||
|
||||
### Cloud API (no GPU required)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/outis1one/editmaskwithai
|
||||
cd editmaskwithai
|
||||
git clone https://github.com/outis1one/paintplus
|
||||
cd paintplus
|
||||
cp .env.example .env
|
||||
# Edit .env: set AI_PROVIDER and your API key (see .env.example for options)
|
||||
docker compose up -d --build
|
||||
@@ -140,7 +140,7 @@ docker compose logs -f
|
||||
## File structure
|
||||
|
||||
```
|
||||
EditmaskwithAI/
|
||||
PaintPlus/
|
||||
├── backend/
|
||||
│ ├── app/
|
||||
│ │ ├── routers/ # API endpoints (ai_tools, print_tools, …)
|
||||
@@ -163,7 +163,7 @@ EditmaskwithAI/
|
||||
├── Dockerfile.gpu
|
||||
├── install-local-gpu.sh # One-time GPU host setup
|
||||
├── bring-up-local-gpu.sh # Start/stop the GPU container
|
||||
├── prefetch-models.sh # Download AI models on the host (DNS-blocked workaround)
|
||||
├── prefetch-models.sh # Download AI models on the host (called automatically; also runnable standalone)
|
||||
└── .env.example
|
||||
```
|
||||
|
||||
@@ -232,7 +232,7 @@ You can also pick a specific model per-edit from the Remove Background dialog's
|
||||
|
||||
**AI models not downloading (container DNS blocked)**
|
||||
|
||||
Easiest fix: download the models on the host instead of inside the container — they land in `./data/`, which is already bind-mounted into the container, so it picks them up with no rebuild:
|
||||
`install-local-gpu.sh` and `bring-up-local-gpu.sh` already run this for you automatically on every start, so you normally don't need to think about it. If a model still didn't download (no network at the time, etc.), re-run it manually — it lands in `./data/`, which is already bind-mounted into the container, so it's picked up with no rebuild:
|
||||
|
||||
```bash
|
||||
./prefetch-models.sh # SAM + U2Net + BEN2 + BiRefNet-HR (~1.5GB)
|
||||
|
||||
+17
-6
@@ -103,20 +103,31 @@ def health():
|
||||
STATIC_DIR = Path("/app/static")
|
||||
|
||||
|
||||
class NoCacheStaticFiles(StaticFiles):
|
||||
"""webpack outputs a fixed 'bundle.js' filename (no content hash), so
|
||||
browsers can keep serving a stale cached copy after a rebuild unless
|
||||
forced to revalidate on every request."""
|
||||
|
||||
def file_response(self, *args, **kwargs):
|
||||
response = super().file_response(*args, **kwargs)
|
||||
response.headers["Cache-Control"] = "no-cache"
|
||||
return response
|
||||
|
||||
|
||||
# Serve static assets - mount subdirectories if they exist
|
||||
if STATIC_DIR.exists():
|
||||
# React-style assets folder
|
||||
if (STATIC_DIR / "assets").exists():
|
||||
app.mount("/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="assets")
|
||||
# miniPaint dist folder (webpack bundle)
|
||||
# miniPaint dist folder (webpack bundle) - no-cache so code updates are picked up immediately
|
||||
if (STATIC_DIR / "dist").exists():
|
||||
app.mount("/dist", StaticFiles(directory=STATIC_DIR / "dist"), name="dist")
|
||||
app.mount("/dist", NoCacheStaticFiles(directory=STATIC_DIR / "dist"), name="dist")
|
||||
# miniPaint images folder
|
||||
if (STATIC_DIR / "images").exists():
|
||||
app.mount("/images", StaticFiles(directory=STATIC_DIR / "images"), name="images")
|
||||
# miniPaint CSS folder
|
||||
# miniPaint CSS folder - no-cache, same reasoning as /dist
|
||||
if (STATIC_DIR / "src").exists():
|
||||
app.mount("/src", StaticFiles(directory=STATIC_DIR / "src"), name="src")
|
||||
app.mount("/src", NoCacheStaticFiles(directory=STATIC_DIR / "src"), name="src")
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
@@ -124,7 +135,7 @@ async def serve_spa():
|
||||
"""Serve miniPaint index.html"""
|
||||
index_path = STATIC_DIR / "index.html"
|
||||
if index_path.exists():
|
||||
return FileResponse(index_path)
|
||||
return FileResponse(index_path, headers={"Cache-Control": "no-cache"})
|
||||
return HTMLResponse("<h1>Frontend not built. Run npm build in frontend/</h1>")
|
||||
|
||||
|
||||
@@ -146,6 +157,6 @@ async def serve_spa_routes(request: Request, full_path: str):
|
||||
# Otherwise serve index.html
|
||||
index_path = STATIC_DIR / "index.html"
|
||||
if index_path.exists():
|
||||
return FileResponse(index_path)
|
||||
return FileResponse(index_path, headers={"Cache-Control": "no-cache"})
|
||||
|
||||
return HTMLResponse("<h1>Frontend not built</h1>", status_code=404)
|
||||
|
||||
@@ -369,7 +369,10 @@ async def _remove_background_ben2(img: Image.Image) -> bytes:
|
||||
_ben2_model.to(device).eval()
|
||||
print("BEN2_Base model loaded")
|
||||
|
||||
result = _ben2_model.inference(img.convert('RGB'), refine_foreground=False)
|
||||
# refine_foreground=True runs BEN2's extra foreground-color refinement pass
|
||||
# (slower, but recovers fine/semi-transparent edge detail instead of a hard
|
||||
# cutout — matters for things like lace, light rays, or fine text borders).
|
||||
result = _ben2_model.inference(img.convert('RGB'), refine_foreground=True)
|
||||
|
||||
buffer = BytesIO()
|
||||
result.save(buffer, format='PNG')
|
||||
|
||||
+17
-3
@@ -4,9 +4,11 @@
|
||||
# Run this each time you want to start the app.
|
||||
# Run ./install-local-gpu.sh once first on a new machine.
|
||||
#
|
||||
# If models fail to download inside the container (DNS/firewall blocked),
|
||||
# run ./prefetch-models.sh first — it downloads them on the host into
|
||||
# ./data/, which this container already bind-mounts, so no rebuild needed.
|
||||
# Before starting, this fetches any missing models on the host (outside
|
||||
# Docker) via ./prefetch-models.sh — in-container DNS/network is unreliable
|
||||
# on some hosts, so this is the default now, not a manual troubleshooting
|
||||
# step. It never blocks startup: if it fails (no network, no python3, etc.)
|
||||
# the container still starts and falls back to its own in-container download.
|
||||
#
|
||||
# Usage:
|
||||
# ./bring-up-local-gpu.sh # start (detached, rebuild if needed)
|
||||
@@ -19,7 +21,19 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Pre-create ./data as the current (non-root) user. Otherwise, on a fresh
|
||||
# checkout, Docker's daemon (root) auto-creates these bind-mount sources on
|
||||
# the first 'up' — leaving them root-owned and blocking this same user from
|
||||
# later writing to them without sudo (e.g. ./prefetch-models.sh). No-op if
|
||||
# they already exist, regardless of current ownership.
|
||||
mkdir -p data/models data/hf_cache data/projects data/patches
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
# Best-effort: fetch any missing models on the host first (see header).
|
||||
./prefetch-models.sh --sdxl \
|
||||
|| echo "⚠ Model prefetch had failures (see above) — continuing anyway, the container will retry in-container."
|
||||
exec docker compose -f docker-compose.gpu.yml up -d --build
|
||||
else
|
||||
exec docker compose -f docker-compose.gpu.yml "$@"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# =============================================================================
|
||||
# EditmaskwithAI — GPU Docker Compose (NVIDIA CUDA)
|
||||
# PaintPlus — GPU Docker Compose (NVIDIA CUDA)
|
||||
#
|
||||
# ── PREREQUISITES ─────────────────────────────────────────────────────────────
|
||||
#
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
# Increment BUILDID to force pip layers to re-run without full --no-cache:
|
||||
# BUILDID=$(date +%s) docker compose -f docker-compose.gpu.yml up --build
|
||||
BUILDID: ${BUILDID:-1}
|
||||
container_name: editmaskwithai-gpu
|
||||
container_name: paintplus-gpu
|
||||
ports:
|
||||
- "${PORT:-3080}:8000"
|
||||
volumes:
|
||||
|
||||
@@ -25,7 +25,7 @@ class Help_about_class {
|
||||
{title: "Smart Select:", html: '<a href="https://github.com/facebookresearch/segment-anything" target="_blank">SAM</a> (Meta AI)'},
|
||||
{title: "Remote AI:", html: 'InvokeAI · ComfyUI · OpenAI (user-configured)'},
|
||||
{title: "", html: '<hr style="margin:8px 0;border-color:#444;">'},
|
||||
{title: "GitHub:", html: '<a href="https://github.com/outis1one/EditmaskwithAI" target="_blank">outis1one/EditmaskwithAI</a>'},
|
||||
{title: "GitHub:", html: '<a href="https://github.com/outis1one/PaintPlus" target="_blank">outis1one/PaintPlus</a>'},
|
||||
],
|
||||
};
|
||||
this.POP.show(settings);
|
||||
|
||||
+35
-4
@@ -19,7 +19,7 @@ if [ "$EUID" -ne 0 ]; then
|
||||
fi
|
||||
|
||||
echo "=================================================="
|
||||
echo " EditmaskwithAI — Local GPU one-time setup"
|
||||
echo " PaintPlus — Local GPU one-time setup"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
@@ -80,6 +80,18 @@ fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ── 0.5. Pre-create ./data with correct ownership ────────────────────────────
|
||||
# Docker's daemon (always root) auto-creates bind-mount source directories on
|
||||
# the first 'compose up' if they don't exist yet — leaving ./data root-owned
|
||||
# and blocking the invoking user from later running ./prefetch-models.sh
|
||||
# without a manual 'sudo chown'. Create it now, owned by the real (non-root)
|
||||
# user, so that problem has no chance to happen on a fresh checkout.
|
||||
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
mkdir -p "$REPO_DIR"/data/{models,hf_cache,projects,patches}
|
||||
chown -R "${SUDO_UID:-$(id -u)}:${SUDO_GID:-$(id -g)}" "$REPO_DIR/data"
|
||||
echo "✓ ./data prepared (writable without sudo)"
|
||||
echo ""
|
||||
|
||||
# ── 1. NVIDIA container toolkit ──────────────────────────────────────────────
|
||||
if command -v nvidia-ctk &>/dev/null; then
|
||||
echo "✓ nvidia-container-toolkit already installed — skipping"
|
||||
@@ -158,12 +170,31 @@ else
|
||||
echo " Minimum driver version: 525"
|
||||
fi
|
||||
|
||||
# ── 6. Prefetch all AI models (host-side, outside Docker) ───────────────────
|
||||
# In-container DNS/network is unreliable on some hosts, so downloading on the
|
||||
# host up front (including SDXL/inpaint, ~13GB) is the default now rather
|
||||
# than a manual troubleshooting step. Best-effort: this script must finish
|
||||
# (and leave the GPU/Docker setup done) even if prefetch fails outright.
|
||||
# Run as the real invoking user, not root, so downloaded files (and any
|
||||
# `pip install --user` side effects) end up owned by that user — this script
|
||||
# itself is already running as root via the sudo re-exec above.
|
||||
echo ""
|
||||
echo "Prefetching AI models (this can take a while for SDXL, ~13GB)..."
|
||||
if [ -n "${SUDO_USER:-}" ]; then
|
||||
sudo -u "$SUDO_USER" -H "$REPO_DIR/prefetch-models.sh" --sdxl \
|
||||
|| echo "⚠ Model prefetch had failures (see above) — continuing anyway, the container will retry in-container."
|
||||
else
|
||||
"$REPO_DIR/prefetch-models.sh" --sdxl \
|
||||
|| echo "⚠ Model prefetch had failures (see above) — continuing anyway, the container will retry in-container."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo " Setup complete."
|
||||
echo " Start the app with: ./bring-up-local-gpu.sh"
|
||||
echo ""
|
||||
echo " If models fail to download inside the container (DNS/firewall"
|
||||
echo " blocked), fetch them on the host first instead:"
|
||||
echo " ./prefetch-models.sh"
|
||||
echo " Models are prefetched automatically by this script and by"
|
||||
echo " bring-up-local-gpu.sh on every start. If any failed above (no"
|
||||
echo " network, etc.), re-run manually any time:"
|
||||
echo " ./prefetch-models.sh --sdxl"
|
||||
echo "=================================================="
|
||||
|
||||
+71
-23
@@ -12,9 +12,11 @@
|
||||
# ./prefetch-models.sh --sdxl # also prefetch SDXL base + inpaint (~13GB)
|
||||
#
|
||||
# Safe to re-run: every download here skips files that already exist
|
||||
# (HuggingFace Hub) or are already present (SAM/U2Net).
|
||||
# (HuggingFace Hub) or are already present (SAM/U2Net). Each model is
|
||||
# independent — one failing (e.g. no network reachable at all) doesn't
|
||||
# block the others from being attempted.
|
||||
|
||||
set -euo pipefail
|
||||
set -uo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
@@ -24,11 +26,32 @@ if ! command -v python3 &>/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! mkdir -p data/models data/hf_cache 2>/dev/null; then
|
||||
echo "✗ Could not create ./data/models or ./data/hf_cache." >&2
|
||||
echo " If ./data/ was already created by Docker (root-owned), re-run with sudo:" >&2
|
||||
echo " sudo ./prefetch-models.sh $*" >&2
|
||||
exit 1
|
||||
# mkdir -p succeeds silently on an already-existing directory even when we
|
||||
# can't write into it, so actually test writability rather than trusting that.
|
||||
check_writable() {
|
||||
mkdir -p "$1" 2>/dev/null
|
||||
touch "$1/.write_test" 2>/dev/null && rm -f "$1/.write_test"
|
||||
}
|
||||
|
||||
NEED_CHOWN=0
|
||||
for d in data/models data/hf_cache; do
|
||||
check_writable "$d" || NEED_CHOWN=1
|
||||
done
|
||||
|
||||
if [ "$NEED_CHOWN" -eq 1 ]; then
|
||||
echo "⚠ ./data isn't writable by $(id -un) — this usually means Docker created it as root on a previous run."
|
||||
echo " Fixing ownership (the container runs as root and will still work fine afterward):"
|
||||
echo " sudo chown -R $(id -u):$(id -g) ./data"
|
||||
if ! sudo chown -R "$(id -u):$(id -g)" ./data; then
|
||||
echo "✗ Could not fix ownership automatically (sudo failed or unavailable)." >&2
|
||||
echo " Run this manually, then re-run this script:" >&2
|
||||
echo " sudo chown -R \$(id -u):\$(id -g) ./data" >&2
|
||||
exit 1
|
||||
fi
|
||||
for d in data/models data/hf_cache; do
|
||||
check_writable "$d" || { echo "✗ Still no write permission in ./$d after chown." >&2; exit 1; }
|
||||
done
|
||||
echo "✓ Fixed."
|
||||
fi
|
||||
|
||||
PREFETCH_SDXL=0
|
||||
@@ -36,36 +59,50 @@ if [ "${1:-}" = "--sdxl" ]; then
|
||||
PREFETCH_SDXL=1
|
||||
fi
|
||||
|
||||
FAILED=()
|
||||
|
||||
echo "=================================================="
|
||||
echo " Prefetching AI models (host-side, no Docker)"
|
||||
echo "=================================================="
|
||||
|
||||
echo ""
|
||||
echo "── SAM (Smart Select) ───────────────────────────────"
|
||||
python3 scripts/download_sam_model.py vit_b
|
||||
python3 scripts/download_sam_model.py vit_b || FAILED+=("SAM")
|
||||
|
||||
echo ""
|
||||
echo "── U2Net (Remove Background fallback) ──────────────"
|
||||
python3 scripts/download_u2net_model.py u2net
|
||||
python3 scripts/download_u2net_model.py u2net || FAILED+=("U2Net")
|
||||
|
||||
echo ""
|
||||
echo "── HuggingFace Hub models (BEN2, BiRefNet-HR) ───────"
|
||||
|
||||
if ! python3 -c "import huggingface_hub" &>/dev/null; then
|
||||
echo "Installing huggingface_hub (lightweight — no torch/GPU needed for this step)..."
|
||||
python3 -m pip install --quiet --user "huggingface_hub>=0.23.0"
|
||||
PIP_ERR=$(python3 -m pip install --quiet --user "huggingface_hub>=0.23.0" 2>&1) || {
|
||||
if echo "$PIP_ERR" | grep -q "externally-managed-environment"; then
|
||||
# PEP 668 (Debian/Ubuntu 12+): --user already keeps this out of
|
||||
# apt-managed system site-packages, so overriding here is safe.
|
||||
echo "System Python is externally managed — retrying with --break-system-packages"
|
||||
python3 -m pip install --quiet --user --break-system-packages "huggingface_hub>=0.23.0" \
|
||||
|| FAILED+=("huggingface_hub install")
|
||||
else
|
||||
echo "$PIP_ERR" >&2
|
||||
FAILED+=("huggingface_hub install")
|
||||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
# HF_HOME must match what the container resolves by default: the bind mount
|
||||
# maps ./data/hf_cache -> /root/.cache/huggingface, and the container never
|
||||
# sets HF_HOME explicitly, so it defaults to ~/.cache/huggingface there.
|
||||
# huggingface_hub itself appends "/hub" to HF_HOME to get the actual cache
|
||||
# root (HF_HUB_CACHE) — setting HF_HOME here (instead of passing --cache-dir
|
||||
# or cache_dir=... directly) lets both sides derive that "/hub" nesting the
|
||||
# same way, rather than us hardcoding it and risking a mismatch.
|
||||
export HF_HOME="$(pwd)/data/hf_cache"
|
||||
if python3 -c "import huggingface_hub" &>/dev/null; then
|
||||
# HF_HOME must match what the container resolves by default: the bind mount
|
||||
# maps ./data/hf_cache -> /root/.cache/huggingface, and the container never
|
||||
# sets HF_HOME explicitly, so it defaults to ~/.cache/huggingface there.
|
||||
# huggingface_hub itself appends "/hub" to HF_HOME to get the actual cache
|
||||
# root (HF_HUB_CACHE) — setting HF_HOME here (instead of passing --cache-dir
|
||||
# or cache_dir=... directly) lets both sides derive that "/hub" nesting the
|
||||
# same way, rather than us hardcoding it and risking a mismatch.
|
||||
export HF_HOME="$(pwd)/data/hf_cache"
|
||||
|
||||
PREFETCH_SDXL="$PREFETCH_SDXL" python3 - << 'PYEOF'
|
||||
PREFETCH_SDXL="$PREFETCH_SDXL" python3 - << 'PYEOF' || FAILED+=("HuggingFace models")
|
||||
import os
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
@@ -81,12 +118,23 @@ for repo_id in repos:
|
||||
snapshot_download(repo_id=repo_id, ignore_patterns=["*.msgpack", "flax_*", "tf_*"])
|
||||
print(f" done: {repo_id}")
|
||||
PYEOF
|
||||
else
|
||||
echo "⚠ Skipping BEN2/BiRefNet-HR — huggingface_hub unavailable (install failed above)"
|
||||
FAILED+=("HuggingFace models")
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo " Done. Models cached under ./data/models and ./data/hf_cache"
|
||||
if [ "$PREFETCH_SDXL" != "1" ]; then
|
||||
echo " (SDXL not included — re-run with --sdxl to also prefetch txt2img/inpaint, ~13GB)"
|
||||
if [ ${#FAILED[@]} -eq 0 ]; then
|
||||
echo " Done. Models cached under ./data/models and ./data/hf_cache"
|
||||
if [ "$PREFETCH_SDXL" != "1" ]; then
|
||||
echo " (SDXL not included — re-run with --sdxl to also prefetch txt2img/inpaint, ~13GB)"
|
||||
fi
|
||||
echo " Start the app: ./bring-up-local-gpu.sh"
|
||||
else
|
||||
echo " Finished with failures: ${FAILED[*]}"
|
||||
echo " If ALL of the above failed, this host can't reach the internet right now"
|
||||
echo " (check: curl -v https://github.com) — that's a host/network issue, not Docker."
|
||||
echo " If only some failed, re-run this script to retry just those."
|
||||
fi
|
||||
echo " Start the app: ./bring-up-local-gpu.sh"
|
||||
echo "=================================================="
|
||||
|
||||
@@ -40,6 +40,20 @@ SAM_MODELS = {
|
||||
}
|
||||
}
|
||||
|
||||
def create_symlink(symlink_path: Path, target_name: str):
|
||||
"""Best-effort convenience symlink. Never raises — a missing/stale
|
||||
symlink is harmless (callers also check the real filename directly),
|
||||
but data/models/ is often root-owned from a prior Docker run, which
|
||||
makes unlink/symlink_to fail with PermissionError for other users."""
|
||||
try:
|
||||
if symlink_path.exists() or symlink_path.is_symlink():
|
||||
symlink_path.unlink()
|
||||
symlink_path.symlink_to(target_name)
|
||||
print(f"Symlink created: {symlink_path} -> {target_name}")
|
||||
except OSError as e:
|
||||
print(f"(skipping symlink: {e})")
|
||||
|
||||
|
||||
def download_with_progress(url: str, dest_path: Path):
|
||||
"""Download file with progress indicator"""
|
||||
print(f"Downloading to: {dest_path}")
|
||||
@@ -88,12 +102,7 @@ def main():
|
||||
print(f"\nModel already exists at: {dest_path}")
|
||||
print("To re-download, delete the file first.")
|
||||
|
||||
# Create symlink for easy access
|
||||
symlink_path = models_dir / 'sam_model.pth'
|
||||
if symlink_path.exists() or symlink_path.is_symlink():
|
||||
symlink_path.unlink()
|
||||
symlink_path.symlink_to(dest_path.name)
|
||||
print(f"Symlink created: {symlink_path} -> {dest_path.name}")
|
||||
create_symlink(models_dir / 'sam_model.pth', dest_path.name)
|
||||
return
|
||||
|
||||
print(f"\nDownloading SAM {model_type.upper()} ({model_info['size']})...")
|
||||
@@ -103,17 +112,13 @@ def main():
|
||||
try:
|
||||
download_with_progress(model_info['url'], dest_path)
|
||||
|
||||
# Create symlink for easy access
|
||||
symlink_path = models_dir / 'sam_model.pth'
|
||||
if symlink_path.exists() or symlink_path.is_symlink():
|
||||
symlink_path.unlink()
|
||||
symlink_path.symlink_to(dest_path.name)
|
||||
create_symlink(symlink_path, dest_path.name)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("SUCCESS!")
|
||||
print(f"Model saved to: {dest_path}")
|
||||
print(f"Symlink: {symlink_path}")
|
||||
print("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -6,7 +6,7 @@ These are public domain images from Wikimedia Commons of classical sculptures.
|
||||
Run this script to populate the eye catalog with example eyes.
|
||||
|
||||
Usage:
|
||||
cd /home/user/EditmaskwithAI
|
||||
cd /home/user/PaintPlus
|
||||
python scripts/download_sample_eyes.py
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user