serverless: qwen-edit-nsfw + faceswap-lissie endpoints (configs, clients, docs); handler fixes (passthrough LoadImage rewrite, seed range/map, output cleanup); .git stripped from node layers
Browse files- serverless/README.md +17 -4
- serverless/build.sh +13 -3
- serverless/client/README.md +54 -62
- serverless/client/faceswap_lissie_async.py +145 -0
- serverless/client/faceswap_lissie_sync.py +146 -0
- serverless/client/new-faceswap-lissie-v4.json +337 -0
- serverless/client/qwen-edit-nsfw.json +234 -0
- serverless/client/qwen_edit_nsfw_async.py +145 -0
- serverless/client/qwen_edit_nsfw_sync.py +145 -0
- serverless/client/qwen_edit_sync.py +15 -0
- serverless/endpoints/faceswap-lissie/endpoint.json +22 -0
- serverless/endpoints/faceswap-lissie/params/new-faceswap-lissie-v4.json +19 -0
- serverless/endpoints/faceswap-lissie/workflows/new-faceswap-lissie-v4.json +337 -0
- serverless/endpoints/qwen-edit-nsfw/endpoint.json +18 -0
- serverless/endpoints/qwen-edit-nsfw/params/qwen-edit-nsfw.json +20 -0
- serverless/endpoints/qwen-edit-nsfw/workflows/qwen-edit-nsfw.json +234 -0
- serverless/handler.py +24 -2
- serverless/tests/endpoint_test.py +4 -2
- serverless/tests/local_test.py +4 -2
serverless/README.md
CHANGED
|
@@ -9,10 +9,23 @@ Images: `plx1029/comfyui-serverless` on Docker Hub. Current endpoint:
|
|
| 9 |
| endpoint | image tag | RunPod endpoint id | contents |
|
| 10 |
|---|---|---|---|
|
| 11 |
| qwen-edit-turbo | `qwen-edit-turbo-v1` | `dom5lwr0o5wq6u` | Qwen-Image-Edit-2511 **bf16** + Lightning 4/8-step loras + Skin-Fix/Amateur loras, workflow `qwen-edit-turbo-v4` |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
## Layout
|
| 18 |
|
|
|
|
| 9 |
| endpoint | image tag | RunPod endpoint id | contents |
|
| 10 |
|---|---|---|---|
|
| 11 |
| qwen-edit-turbo | `qwen-edit-turbo-v1` | `dom5lwr0o5wq6u` | Qwen-Image-Edit-2511 **bf16** + Lightning 4/8-step loras + Skin-Fix/Amateur loras, workflow `qwen-edit-turbo-v4` |
|
| 12 |
+
| qwen-edit-nsfw | `qwen-edit-nsfw-v1` | `tplcz9jtzihoxa` | Qwen-Rapid-AIO-NSFW v11.4 + next-scene lora, RES4LYF sampler, workflow `qwen-edit-nsfw` |
|
| 13 |
+
| faceswap-lissie | `faceswap-lissie-v1` | `qkeibkeoefcrk3` | Flux-2-Klein 9B + Qwen3-8B TE + bfs-head/lissie loras + Qwen3-VL-4B + RMBG-2.0 (baked), workflow `new-faceswap-lissie-v4` |
|
| 14 |
+
|
| 15 |
+
RunPod templates `serverless-<endpoint>-v1`, endpoints `serverless-<endpoint>`:
|
| 16 |
+
H100 80GB (SXM/PCIe/NVL), FlashBoot, min 0 / max 2 workers, idle timeout 60 s,
|
| 17 |
+
QUEUE_DELAY scaler. All endpoints are **workflow-agnostic**: they run their
|
| 18 |
+
baked workflow by default, any other workflow whose models/nodes are in the
|
| 19 |
+
image via `"workflow"`, or a fully custom API-format graph via
|
| 20 |
+
`"workflow_json"` (uploaded images are matched to `LoadImage` nodes by name).
|
| 21 |
+
|
| 22 |
+
Serverless-specific workflow tweaks (baked copies under `endpoints/*/workflows/`
|
| 23 |
+
differ from `workflows/api/` in exactly these ways):
|
| 24 |
+
- `qwen-edit-nsfw`: removed the KJNodes `WidgetToString` node (it reads
|
| 25 |
+
UI-only metadata and crashes headless); the Image-Saver metadata node gets a
|
| 26 |
+
static model-name string instead. Behavior is otherwise identical.
|
| 27 |
+
- `faceswap-lissie`: unchanged, but the endpoint defaults `keep_vl_loaded=true`
|
| 28 |
+
so Qwen3-VL stays in VRAM between jobs (~10 s faster warm jobs).
|
| 29 |
|
| 30 |
## Layout
|
| 31 |
|
serverless/build.sh
CHANGED
|
@@ -42,7 +42,9 @@ command -v pigz >/dev/null || { echo "pigz not installed"; exit 1; }
|
|
| 42 |
echo "$DOCKER_HUB_ACCESS_TOKEN" | crane auth login index.docker.io -u "$USER" --password-stdin
|
| 43 |
|
| 44 |
mkdir -p "$BUILD_DIR"
|
| 45 |
-
|
|
|
|
|
|
|
| 46 |
|
| 47 |
layer_from_paths() { # $1 = output tgz, rest = paths relative to /
|
| 48 |
local out="$1"; shift
|
|
@@ -73,15 +75,23 @@ build_models() {
|
|
| 73 |
layers+=(-f "$BUILD_DIR/$ENDPOINT-nodes.tgz")
|
| 74 |
fi
|
| 75 |
|
| 76 |
-
# one layer per model file (content-addressed => pushed exactly once, ever)
|
| 77 |
while read -r m; do
|
| 78 |
-
[ -
|
| 79 |
local tgz="$BUILD_DIR/model-$(basename "$m").tgz"
|
| 80 |
echo "-- model layer: $m"
|
| 81 |
layer_from_paths "$tgz" "workspace/ComfyUI/$m"
|
| 82 |
layers+=(-f "$tgz")
|
| 83 |
done < <(python3 -c "import json;print('\n'.join(json.load(open('$EP_DIR/endpoint.json'))['models']))")
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
crane append -b "$BASE" "${layers[@]}" -t "$tag"
|
| 86 |
echo "== pushed $tag"
|
| 87 |
}
|
|
|
|
| 42 |
echo "$DOCKER_HUB_ACCESS_TOKEN" | crane auth login index.docker.io -u "$USER" --password-stdin
|
| 43 |
|
| 44 |
mkdir -p "$BUILD_DIR"
|
| 45 |
+
# .git excluded: images are immutable, git history is dead weight (RES4LYF's
|
| 46 |
+
# alone is 260 MB) and Manager-updates don't apply to serverless workers
|
| 47 |
+
TAR_OPTS=(--owner=0 --group=0 --exclude='__pycache__' --exclude='*.pyc' --exclude='.git')
|
| 48 |
|
| 49 |
layer_from_paths() { # $1 = output tgz, rest = paths relative to /
|
| 50 |
local out="$1"; shift
|
|
|
|
| 75 |
layers+=(-f "$BUILD_DIR/$ENDPOINT-nodes.tgz")
|
| 76 |
fi
|
| 77 |
|
| 78 |
+
# one layer per model file/dir (content-addressed => pushed exactly once, ever)
|
| 79 |
while read -r m; do
|
| 80 |
+
[ -e "$WS/ComfyUI/$m" ] || { echo "missing model $WS/ComfyUI/$m — download it first"; exit 1; }
|
| 81 |
local tgz="$BUILD_DIR/model-$(basename "$m").tgz"
|
| 82 |
echo "-- model layer: $m"
|
| 83 |
layer_from_paths "$tgz" "workspace/ComfyUI/$m"
|
| 84 |
layers+=(-f "$tgz")
|
| 85 |
done < <(python3 -c "import json;print('\n'.join(json.load(open('$EP_DIR/endpoint.json'))['models']))")
|
| 86 |
|
| 87 |
+
# extra prebuilt layers (e.g. a pip-deps delta captured on the build pod)
|
| 88 |
+
while read -r x; do
|
| 89 |
+
[ -z "$x" ] && continue
|
| 90 |
+
[ -s "$BUILD_DIR/$x" ] || { echo "missing extra layer $BUILD_DIR/$x"; exit 1; }
|
| 91 |
+
echo "-- extra layer: $x"
|
| 92 |
+
layers+=(-f "$BUILD_DIR/$x")
|
| 93 |
+
done < <(python3 -c "import json;print('\n'.join(json.load(open('$EP_DIR/endpoint.json')).get('extra_layers',[])))")
|
| 94 |
+
|
| 95 |
crane append -b "$BASE" "${layers[@]}" -t "$tag"
|
| 96 |
echo "== pushed $tag"
|
| 97 |
}
|
serverless/client/README.md
CHANGED
|
@@ -1,81 +1,73 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
| 6 |
|
| 7 |
-
|
|
| 8 |
-
|---|---|
|
| 9 |
-
| `qwen_edit_sync.py`
|
| 10 |
-
| `
|
| 11 |
-
|
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
```bash
|
| 16 |
-
export RUNPOD_API_KEY=...
|
| 17 |
|
| 18 |
-
#
|
| 19 |
-
python qwen_edit_sync.py \
|
| 20 |
-
--
|
| 21 |
-
--prompt "replace the outfit of the person in the first image with the outfit in the second image" \
|
| 22 |
--mode turbo-8 --out ./results
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
```
|
| 28 |
|
| 29 |
-
|
| 30 |
-
switches off automatically).
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
| 40 |
-
| `--prompt` | required | edit instruction |
|
| 41 |
-
| `--mode` | `turbo-8` | `turbo-4` (fastest) / `turbo-8` / `quality` (12-step, cfg 3) |
|
| 42 |
-
| `--seed` | random | reproducibility; the used seed is always printed |
|
| 43 |
-
| `--input-max-dim` | 2048 | main image is resized to fit this before editing |
|
| 44 |
-
| `--ref-max-dim` | 1024 | same for the reference image |
|
| 45 |
-
| `--output-max-dim` | 2560 | result upscale target |
|
| 46 |
-
| `--lora-skin-fix [--lora-skin-fix-strength]` | off / 1.0 | optional Skin-Fix lora |
|
| 47 |
-
| `--lora-amateur [--lora-amateur-strength]` | off / 1.0 | optional Amateur-Photo lora |
|
| 48 |
-
| `--workflow` | `qwen-edit-turbo-v4` | which baked workflow to run |
|
| 49 |
-
| `--set NODE.INPUT=VALUE` | — | raw graph override, repeatable (e.g. `--set 43.cfg=1.5`) |
|
| 50 |
-
| `--workflow-json FILE` | — | run a full custom API-format graph instead |
|
| 51 |
-
| `--out` | `.` | where to save result images |
|
| 52 |
-
| `--timeout` | 600 / 1800 | max seconds to wait |
|
| 53 |
-
|
| 54 |
-
`qwen_edit_async.py` extras: `--no-wait` (print job id and exit), `--job-id ID`
|
| 55 |
-
(poll/collect an existing job), `--poll SECONDS`, `--cancel JOB_ID`.
|
| 56 |
-
|
| 57 |
-
## What to expect (measured 2026-07-23)
|
| 58 |
-
|
| 59 |
-
| situation | delay | execution |
|
| 60 |
|---|---|---|
|
| 61 |
-
|
|
| 62 |
-
|
|
| 63 |
-
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
|
| 70 |
-
|
| 71 |
|
| 72 |
```bash
|
| 73 |
-
curl -s -X POST "https://api.runpod.ai/v2/
|
| 74 |
-H "Authorization: Bearer $RUNPOD_API_KEY" -H "Content-Type: application/json" \
|
| 75 |
-
-d '{"input": {"images": [{"name": "a.jpg", "image": "
|
| 76 |
-
"params": {"prompt": "
|
| 77 |
```
|
| 78 |
-
|
| 79 |
-
Response: `output.images[].data` is a base64 PNG; `output.seed`,
|
| 80 |
-
`output.timings` included. See `../README.md` for the full API schema and how
|
| 81 |
-
the endpoint image is built.
|
|
|
|
| 1 |
+
# Serverless client scripts
|
| 2 |
|
| 3 |
+
Standalone callers (Python 3.8+, stdlib only — no pip installs) for the
|
| 4 |
+
deployed RunPod endpoints. Each endpoint has a sync script (`/runsync`, blocks
|
| 5 |
+
until done) and an async one (`/run` — submit, poll, fire-and-forget with
|
| 6 |
+
`--no-wait`, collect with `--job-id`, `--cancel`).
|
| 7 |
|
| 8 |
+
| endpoint | id | sync | async | workflow copy |
|
| 9 |
+
|---|---|---|---|---|
|
| 10 |
+
| qwen-edit-turbo | `dom5lwr0o5wq6u` | `qwen_edit_sync.py` | `qwen_edit_async.py` | `qwen-edit-turbo-v4.json` |
|
| 11 |
+
| qwen-edit-nsfw | `tplcz9jtzihoxa` | `qwen_edit_nsfw_sync.py` | `qwen_edit_nsfw_async.py` | `qwen-edit-nsfw.json` |
|
| 12 |
+
| faceswap-lissie | `qkeibkeoefcrk3` | `faceswap_lissie_sync.py` | `faceswap_lissie_async.py` | `new-faceswap-lissie-v4.json` |
|
| 13 |
|
| 14 |
+
Endpoint ids are baked in as defaults; override with `--endpoint-id`. Auth via
|
| 15 |
+
`--api-key` or `RUNPOD_API_KEY` env. Results are saved into `--out` (default
|
| 16 |
+
`.`); the seed used is always printed so runs are reproducible with `--seed`.
|
| 17 |
+
|
| 18 |
+
## Examples
|
| 19 |
|
| 20 |
```bash
|
| 21 |
+
export RUNPOD_API_KEY=...
|
| 22 |
|
| 23 |
+
# turbo outfit transfer (two images)
|
| 24 |
+
python qwen_edit_sync.py --image person.jpg --ref-image outfit.png \
|
| 25 |
+
--prompt "replace the outfit of the person in image 1 with the outfit in image 2" \
|
|
|
|
| 26 |
--mode turbo-8 --out ./results
|
| 27 |
|
| 28 |
+
# nsfw single-image edit, 8 steps
|
| 29 |
+
python qwen_edit_nsfw_sync.py --image person.jpg \
|
| 30 |
+
--prompt "replace the outfit with ..." --out ./results
|
| 31 |
+
|
| 32 |
+
# head swap: face from face.jpg onto scene.jpg
|
| 33 |
+
python faceswap_lissie_sync.py --body-image scene.jpg --face-image face.jpg \
|
| 34 |
+
--out ./results
|
| 35 |
+
|
| 36 |
+
# any of them, fire-and-forget
|
| 37 |
+
job=$(python qwen_edit_nsfw_async.py --image a.jpg --prompt "..." --no-wait)
|
| 38 |
+
python qwen_edit_nsfw_async.py --job-id "$job" --out ./results
|
| 39 |
```
|
| 40 |
|
| 41 |
+
## Common arguments (all scripts)
|
|
|
|
| 42 |
|
| 43 |
+
`--api-key`, `--endpoint-id`, `--seed`, `--out`, `--timeout`,
|
| 44 |
+
`--set NODE.INPUT=VALUE` (raw graph override, repeatable),
|
| 45 |
+
`--workflow NAME` (another workflow baked in that image),
|
| 46 |
+
`--workflow-json FILE` (**full custom API-format graph** — the endpoints are
|
| 47 |
+
workflow-agnostic; uploaded images are matched to your graph's `LoadImage`
|
| 48 |
+
nodes by filename). Async extras: `--no-wait`, `--job-id`, `--poll`, `--cancel`.
|
| 49 |
|
| 50 |
+
Per-endpoint knobs are documented in each script's `--help`; the full
|
| 51 |
+
param-name → node mapping lives in `../endpoints/<endpoint>/params/`.
|
| 52 |
+
|
| 53 |
+
## Expectations (measured 2026-07-23/24, H100)
|
| 54 |
+
|
| 55 |
+
| endpoint | warm exec | notes |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|---|---|---|
|
| 57 |
+
| qwen-edit-turbo | turbo-4 ≈ 29-42 s, turbo-8 ≈ 60 s | 2048px, 2560px output upscale |
|
| 58 |
+
| qwen-edit-nsfw | ≈ 32 s @ 8 steps, 1 MP | webp output |
|
| 59 |
+
| faceswap-lissie | ≈ 11 s @ 5 steps | VL captioner kept in VRAM |
|
| 60 |
|
| 61 |
+
Cold starts: FlashBoot resume ≈ 0.5 s delay with models still in VRAM; a
|
| 62 |
+
brand-new host pays a one-time image pull (~10-15 min) then ~1-2 min of model
|
| 63 |
+
loading on the first job. Payload limits ~10 MB (`/run`) / ~20 MB (`/runsync`)
|
| 64 |
+
with base64 adding ~33% — keep inputs under ~7 MB combined or use async.
|
| 65 |
|
| 66 |
+
Raw HTTP shape (same for all endpoints):
|
| 67 |
|
| 68 |
```bash
|
| 69 |
+
curl -s -X POST "https://api.runpod.ai/v2/<ENDPOINT_ID>/runsync" \
|
| 70 |
-H "Authorization: Bearer $RUNPOD_API_KEY" -H "Content-Type: application/json" \
|
| 71 |
+
-d '{"input": {"images": [{"name": "a.jpg", "image": "<base64>"}],
|
| 72 |
+
"params": {"prompt": "..."}}}'
|
| 73 |
```
|
|
|
|
|
|
|
|
|
|
|
|
serverless/client/faceswap_lissie_async.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Call the faceswap-lissie RunPod serverless endpoint ASYNCHRONOUSLY (/run).
|
| 3 |
+
|
| 4 |
+
Standalone: python 3.8+, stdlib only. Same arguments as
|
| 5 |
+
faceswap_lissie_sync.py plus:
|
| 6 |
+
--no-wait submit only; print the job id and exit
|
| 7 |
+
--job-id ID skip submission; poll/collect an existing job
|
| 8 |
+
--poll SECONDS poll interval (default 3)
|
| 9 |
+
--cancel JOB_ID cancel a queued/running job and exit
|
| 10 |
+
|
| 11 |
+
# fire-and-forget, collect later
|
| 12 |
+
python faceswap_lissie_async.py --body-image scene.jpg --face-image face.jpg --no-wait
|
| 13 |
+
python faceswap_lissie_async.py --job-id JOB_ID --out ./results
|
| 14 |
+
"""
|
| 15 |
+
import argparse
|
| 16 |
+
import base64
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import pathlib
|
| 20 |
+
import sys
|
| 21 |
+
import time
|
| 22 |
+
import urllib.error
|
| 23 |
+
import urllib.request
|
| 24 |
+
|
| 25 |
+
DEFAULT_ENDPOINT = "qkeibkeoefcrk3"
|
| 26 |
+
TERMINAL = ("COMPLETED", "FAILED", "CANCELLED", "TIMED_OUT")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def api(args, method, path, payload=None, timeout=90):
|
| 30 |
+
req = urllib.request.Request(
|
| 31 |
+
f"https://api.runpod.ai/v2/{args.endpoint_id}/{path}", method=method,
|
| 32 |
+
data=json.dumps(payload).encode() if payload is not None else None,
|
| 33 |
+
headers={"Content-Type": "application/json",
|
| 34 |
+
"Authorization": f"Bearer {args.api_key}"})
|
| 35 |
+
try:
|
| 36 |
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
| 37 |
+
return json.load(r)
|
| 38 |
+
except urllib.error.HTTPError as e:
|
| 39 |
+
sys.exit(f"ERROR: HTTP {e.code} on /{path}: {e.read().decode(errors='replace')[:2000]}")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def main():
|
| 43 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 44 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 45 |
+
ap.add_argument("--api-key", default=os.environ.get("RUNPOD_API_KEY"))
|
| 46 |
+
ap.add_argument("--endpoint-id", default=DEFAULT_ENDPOINT)
|
| 47 |
+
ap.add_argument("--body-image")
|
| 48 |
+
ap.add_argument("--face-image")
|
| 49 |
+
ap.add_argument("--instruction")
|
| 50 |
+
ap.add_argument("--expression-prompt")
|
| 51 |
+
ap.add_argument("--negative-prompt")
|
| 52 |
+
ap.add_argument("--steps", type=int)
|
| 53 |
+
ap.add_argument("--cfg", type=float)
|
| 54 |
+
ap.add_argument("--seed", type=int)
|
| 55 |
+
ap.add_argument("--rmbg-sensitivity", type=float)
|
| 56 |
+
ap.add_argument("--rmbg-process-res", type=int)
|
| 57 |
+
ap.add_argument("--workflow")
|
| 58 |
+
ap.add_argument("--set", action="append", metavar="NODE.INPUT=VALUE")
|
| 59 |
+
ap.add_argument("--workflow-json")
|
| 60 |
+
ap.add_argument("--out", default=".")
|
| 61 |
+
ap.add_argument("--timeout", type=float, default=1800)
|
| 62 |
+
ap.add_argument("--poll", type=float, default=3.0)
|
| 63 |
+
ap.add_argument("--no-wait", action="store_true")
|
| 64 |
+
ap.add_argument("--job-id")
|
| 65 |
+
ap.add_argument("--cancel", metavar="JOB_ID")
|
| 66 |
+
args = ap.parse_args()
|
| 67 |
+
if not args.api_key:
|
| 68 |
+
sys.exit("ERROR: pass --api-key or set RUNPOD_API_KEY")
|
| 69 |
+
|
| 70 |
+
if args.cancel:
|
| 71 |
+
print(json.dumps(api(args, "POST", f"cancel/{args.cancel}"), indent=2))
|
| 72 |
+
return
|
| 73 |
+
|
| 74 |
+
if args.job_id:
|
| 75 |
+
job_id = args.job_id
|
| 76 |
+
else:
|
| 77 |
+
if not args.body_image or not args.face_image:
|
| 78 |
+
sys.exit("ERROR: --body-image and --face-image are required to submit a job")
|
| 79 |
+
params = {"body_image": os.path.basename(args.body_image),
|
| 80 |
+
"face_image": os.path.basename(args.face_image)}
|
| 81 |
+
for cli, p in [("instruction", "instruction"), ("expression_prompt", "expression_prompt"),
|
| 82 |
+
("negative_prompt", "negative_prompt"), ("steps", "steps"),
|
| 83 |
+
("cfg", "cfg"), ("seed", "seed"),
|
| 84 |
+
("rmbg_sensitivity", "rmbg_sensitivity"),
|
| 85 |
+
("rmbg_process_res", "rmbg_process_res")]:
|
| 86 |
+
v = getattr(args, cli)
|
| 87 |
+
if v is not None:
|
| 88 |
+
params[p] = v
|
| 89 |
+
payload = {
|
| 90 |
+
"images": [
|
| 91 |
+
{"name": os.path.basename(args.body_image),
|
| 92 |
+
"image": base64.b64encode(open(args.body_image, "rb").read()).decode()},
|
| 93 |
+
{"name": os.path.basename(args.face_image),
|
| 94 |
+
"image": base64.b64encode(open(args.face_image, "rb").read()).decode()},
|
| 95 |
+
],
|
| 96 |
+
"params": params,
|
| 97 |
+
}
|
| 98 |
+
if args.workflow:
|
| 99 |
+
payload["workflow"] = args.workflow
|
| 100 |
+
if args.workflow_json:
|
| 101 |
+
payload["workflow_json"] = json.load(open(args.workflow_json))
|
| 102 |
+
overrides = {}
|
| 103 |
+
for s in args.set or []:
|
| 104 |
+
k, v = s.split("=", 1)
|
| 105 |
+
try:
|
| 106 |
+
overrides[k] = json.loads(v)
|
| 107 |
+
except json.JSONDecodeError:
|
| 108 |
+
overrides[k] = v
|
| 109 |
+
if overrides:
|
| 110 |
+
payload["set"] = overrides
|
| 111 |
+
job = api(args, "POST", "run", {"input": payload})
|
| 112 |
+
job_id = job["id"]
|
| 113 |
+
print(f"# submitted job {job_id}", file=sys.stderr)
|
| 114 |
+
if args.no_wait:
|
| 115 |
+
print(job_id)
|
| 116 |
+
return
|
| 117 |
+
|
| 118 |
+
t0 = time.monotonic()
|
| 119 |
+
while True:
|
| 120 |
+
result = api(args, "GET", f"status/{job_id}")
|
| 121 |
+
status = result.get("status")
|
| 122 |
+
if status in TERMINAL:
|
| 123 |
+
break
|
| 124 |
+
if time.monotonic() - t0 > args.timeout:
|
| 125 |
+
sys.exit(f"ERROR: timed out after {args.timeout}s (job {job_id} still collectable with --job-id)")
|
| 126 |
+
print(f"# {status} ... {time.monotonic()-t0:.0f}s", file=sys.stderr)
|
| 127 |
+
time.sleep(args.poll)
|
| 128 |
+
|
| 129 |
+
if status != "COMPLETED":
|
| 130 |
+
sys.exit(f"ERROR: {json.dumps(result, indent=2)[:3000]}")
|
| 131 |
+
output = result["output"]
|
| 132 |
+
if "error" in output:
|
| 133 |
+
sys.exit(f"ERROR from handler: {output['error']}")
|
| 134 |
+
out_dir = pathlib.Path(args.out)
|
| 135 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 136 |
+
for i, img in enumerate(output["images"]):
|
| 137 |
+
p = out_dir / f"{int(time.time())}-{i}-{img['filename']}"
|
| 138 |
+
p.write_bytes(base64.b64decode(img["data"]))
|
| 139 |
+
print(p)
|
| 140 |
+
print(f"# seed={output.get('seed')} delay={result.get('delayTime')}ms "
|
| 141 |
+
f"exec={result.get('executionTime')}ms", file=sys.stderr)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
if __name__ == "__main__":
|
| 145 |
+
main()
|
serverless/client/faceswap_lissie_sync.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Call the faceswap-lissie RunPod serverless endpoint SYNCHRONOUSLY (/runsync).
|
| 3 |
+
|
| 4 |
+
Standalone: python 3.8+, stdlib only. Flux-2-Klein head/face swap
|
| 5 |
+
(workflow new-faceswap-lissie-v4): puts the head from --face-image onto the
|
| 6 |
+
body/scene of --body-image.
|
| 7 |
+
|
| 8 |
+
python faceswap_lissie_sync.py \
|
| 9 |
+
--api-key $RUNPOD_API_KEY \
|
| 10 |
+
--body-image scene.jpg --face-image face.jpg \
|
| 11 |
+
--out ./results
|
| 12 |
+
|
| 13 |
+
All arguments:
|
| 14 |
+
--api-key RunPod API key (or set RUNPOD_API_KEY)
|
| 15 |
+
--endpoint-id RunPod endpoint id (default: ENDPOINT_DEFAULT below)
|
| 16 |
+
--body-image image supplying body/scene/pose (required)
|
| 17 |
+
--face-image image supplying the head/face (required)
|
| 18 |
+
--instruction head-swap instruction text (sane default baked in)
|
| 19 |
+
--expression-prompt what the QwenVL captioner is asked about the face
|
| 20 |
+
--negative-prompt negative conditioning text
|
| 21 |
+
--steps sampler steps (default 5)
|
| 22 |
+
--cfg guidance (default 1.0)
|
| 23 |
+
--seed integer seed (default: random, printed back)
|
| 24 |
+
--rmbg-sensitivity / --rmbg-process-res background-removal tuning
|
| 25 |
+
--workflow baked workflow name (default new-faceswap-lissie-v4)
|
| 26 |
+
--set NODE.INPUT=VALUE raw graph override, repeatable
|
| 27 |
+
--workflow-json FILE full API-format graph passthrough
|
| 28 |
+
--out output directory (default .)
|
| 29 |
+
--timeout max seconds (default 600)
|
| 30 |
+
"""
|
| 31 |
+
import argparse
|
| 32 |
+
import base64
|
| 33 |
+
import json
|
| 34 |
+
import os
|
| 35 |
+
import pathlib
|
| 36 |
+
import sys
|
| 37 |
+
import time
|
| 38 |
+
import urllib.error
|
| 39 |
+
import urllib.request
|
| 40 |
+
|
| 41 |
+
DEFAULT_ENDPOINT = "qkeibkeoefcrk3"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def main():
|
| 45 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 46 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 47 |
+
ap.add_argument("--api-key", default=os.environ.get("RUNPOD_API_KEY"))
|
| 48 |
+
ap.add_argument("--endpoint-id", default=DEFAULT_ENDPOINT)
|
| 49 |
+
ap.add_argument("--body-image", required=True)
|
| 50 |
+
ap.add_argument("--face-image", required=True)
|
| 51 |
+
ap.add_argument("--instruction")
|
| 52 |
+
ap.add_argument("--expression-prompt")
|
| 53 |
+
ap.add_argument("--negative-prompt")
|
| 54 |
+
ap.add_argument("--steps", type=int)
|
| 55 |
+
ap.add_argument("--cfg", type=float)
|
| 56 |
+
ap.add_argument("--seed", type=int)
|
| 57 |
+
ap.add_argument("--rmbg-sensitivity", type=float)
|
| 58 |
+
ap.add_argument("--rmbg-process-res", type=int)
|
| 59 |
+
ap.add_argument("--workflow")
|
| 60 |
+
ap.add_argument("--set", action="append", metavar="NODE.INPUT=VALUE")
|
| 61 |
+
ap.add_argument("--workflow-json")
|
| 62 |
+
ap.add_argument("--out", default=".")
|
| 63 |
+
ap.add_argument("--timeout", type=float, default=600)
|
| 64 |
+
args = ap.parse_args()
|
| 65 |
+
if not args.api_key:
|
| 66 |
+
sys.exit("ERROR: pass --api-key or set RUNPOD_API_KEY")
|
| 67 |
+
|
| 68 |
+
params = {"body_image": os.path.basename(args.body_image),
|
| 69 |
+
"face_image": os.path.basename(args.face_image)}
|
| 70 |
+
for cli, p in [("instruction", "instruction"), ("expression_prompt", "expression_prompt"),
|
| 71 |
+
("negative_prompt", "negative_prompt"), ("steps", "steps"),
|
| 72 |
+
("cfg", "cfg"), ("seed", "seed"),
|
| 73 |
+
("rmbg_sensitivity", "rmbg_sensitivity"),
|
| 74 |
+
("rmbg_process_res", "rmbg_process_res")]:
|
| 75 |
+
v = getattr(args, cli)
|
| 76 |
+
if v is not None:
|
| 77 |
+
params[p] = v
|
| 78 |
+
|
| 79 |
+
payload = {
|
| 80 |
+
"images": [
|
| 81 |
+
{"name": os.path.basename(args.body_image),
|
| 82 |
+
"image": base64.b64encode(open(args.body_image, "rb").read()).decode()},
|
| 83 |
+
{"name": os.path.basename(args.face_image),
|
| 84 |
+
"image": base64.b64encode(open(args.face_image, "rb").read()).decode()},
|
| 85 |
+
],
|
| 86 |
+
"params": params,
|
| 87 |
+
}
|
| 88 |
+
if args.workflow:
|
| 89 |
+
payload["workflow"] = args.workflow
|
| 90 |
+
if args.workflow_json:
|
| 91 |
+
payload["workflow_json"] = json.load(open(args.workflow_json))
|
| 92 |
+
overrides = {}
|
| 93 |
+
for s in args.set or []:
|
| 94 |
+
k, v = s.split("=", 1)
|
| 95 |
+
try:
|
| 96 |
+
overrides[k] = json.loads(v)
|
| 97 |
+
except json.JSONDecodeError:
|
| 98 |
+
overrides[k] = v
|
| 99 |
+
if overrides:
|
| 100 |
+
payload["set"] = overrides
|
| 101 |
+
|
| 102 |
+
req = urllib.request.Request(
|
| 103 |
+
f"https://api.runpod.ai/v2/{args.endpoint_id}/runsync",
|
| 104 |
+
data=json.dumps({"input": payload}).encode(),
|
| 105 |
+
headers={"Content-Type": "application/json",
|
| 106 |
+
"Authorization": f"Bearer {args.api_key}"})
|
| 107 |
+
t0 = time.monotonic()
|
| 108 |
+
try:
|
| 109 |
+
with urllib.request.urlopen(req, timeout=args.timeout) as r:
|
| 110 |
+
result = json.load(r)
|
| 111 |
+
except urllib.error.HTTPError as e:
|
| 112 |
+
sys.exit(f"ERROR: HTTP {e.code}: {e.read().decode(errors='replace')[:2000]}")
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# /runsync answers early (IN_QUEUE/IN_PROGRESS) when a job outlives the
|
| 116 |
+
# sync window (~90 s), e.g. during a cold start — fall back to polling
|
| 117 |
+
TERMINAL = ("COMPLETED", "FAILED", "CANCELLED", "TIMED_OUT")
|
| 118 |
+
while result.get("status") not in TERMINAL and result.get("id"):
|
| 119 |
+
if time.monotonic() - t0 > args.timeout:
|
| 120 |
+
sys.exit(f"ERROR: timed out after {args.timeout}s "
|
| 121 |
+
f"(job {result['id']} status {result.get('status')})")
|
| 122 |
+
time.sleep(3)
|
| 123 |
+
poll = urllib.request.Request(
|
| 124 |
+
f"https://api.runpod.ai/v2/{args.endpoint_id}/status/{result['id']}",
|
| 125 |
+
headers={"Authorization": f"Bearer {args.api_key}"})
|
| 126 |
+
with urllib.request.urlopen(poll, timeout=90) as r:
|
| 127 |
+
result = json.load(r)
|
| 128 |
+
|
| 129 |
+
if result.get("status") != "COMPLETED":
|
| 130 |
+
sys.exit(f"ERROR: {json.dumps(result, indent=2)[:3000]}")
|
| 131 |
+
output = result["output"]
|
| 132 |
+
if "error" in output:
|
| 133 |
+
sys.exit(f"ERROR from handler: {output['error']}")
|
| 134 |
+
out_dir = pathlib.Path(args.out)
|
| 135 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 136 |
+
for i, img in enumerate(output["images"]):
|
| 137 |
+
p = out_dir / f"{int(time.time())}-{i}-{img['filename']}"
|
| 138 |
+
p.write_bytes(base64.b64decode(img["data"]))
|
| 139 |
+
print(p)
|
| 140 |
+
print(f"# seed={output.get('seed')} wall={time.monotonic()-t0:.1f}s "
|
| 141 |
+
f"delay={result.get('delayTime')}ms exec={result.get('executionTime')}ms",
|
| 142 |
+
file=sys.stderr)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
if __name__ == "__main__":
|
| 146 |
+
main()
|
serverless/client/new-faceswap-lissie-v4.json
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"1": {
|
| 3 |
+
"class_type": "LoadImage",
|
| 4 |
+
"inputs": {
|
| 5 |
+
"image": "body.jpg"
|
| 6 |
+
}
|
| 7 |
+
},
|
| 8 |
+
"12": {
|
| 9 |
+
"class_type": "CLIPTextEncode",
|
| 10 |
+
"inputs": {
|
| 11 |
+
"clip": [
|
| 12 |
+
"8",
|
| 13 |
+
0
|
| 14 |
+
],
|
| 15 |
+
"text": [
|
| 16 |
+
"32",
|
| 17 |
+
0
|
| 18 |
+
]
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"13": {
|
| 22 |
+
"class_type": "CLIPTextEncode",
|
| 23 |
+
"inputs": {
|
| 24 |
+
"clip": [
|
| 25 |
+
"8",
|
| 26 |
+
0
|
| 27 |
+
],
|
| 28 |
+
"text": "bad quality, noise, blurry, worst quality, low resolution, blur, distortion, unnatural blending, cartoon, illustration, painting"
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
"14": {
|
| 32 |
+
"class_type": "VAELoader",
|
| 33 |
+
"inputs": {
|
| 34 |
+
"vae_name": "flux2-vae.safetensors"
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"15": {
|
| 38 |
+
"class_type": "VAEEncode",
|
| 39 |
+
"inputs": {
|
| 40 |
+
"pixels": [
|
| 41 |
+
"4",
|
| 42 |
+
0
|
| 43 |
+
],
|
| 44 |
+
"vae": [
|
| 45 |
+
"14",
|
| 46 |
+
0
|
| 47 |
+
]
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
+
"16": {
|
| 51 |
+
"class_type": "VAEEncode",
|
| 52 |
+
"inputs": {
|
| 53 |
+
"pixels": [
|
| 54 |
+
"5",
|
| 55 |
+
0
|
| 56 |
+
],
|
| 57 |
+
"vae": [
|
| 58 |
+
"14",
|
| 59 |
+
0
|
| 60 |
+
]
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"17": {
|
| 64 |
+
"class_type": "ReferenceLatent",
|
| 65 |
+
"inputs": {
|
| 66 |
+
"conditioning": [
|
| 67 |
+
"12",
|
| 68 |
+
0
|
| 69 |
+
],
|
| 70 |
+
"latent": [
|
| 71 |
+
"15",
|
| 72 |
+
0
|
| 73 |
+
]
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"18": {
|
| 77 |
+
"class_type": "ReferenceLatent",
|
| 78 |
+
"inputs": {
|
| 79 |
+
"conditioning": [
|
| 80 |
+
"17",
|
| 81 |
+
0
|
| 82 |
+
],
|
| 83 |
+
"latent": [
|
| 84 |
+
"16",
|
| 85 |
+
0
|
| 86 |
+
]
|
| 87 |
+
}
|
| 88 |
+
},
|
| 89 |
+
"19": {
|
| 90 |
+
"class_type": "ReferenceLatent",
|
| 91 |
+
"inputs": {
|
| 92 |
+
"conditioning": [
|
| 93 |
+
"13",
|
| 94 |
+
0
|
| 95 |
+
],
|
| 96 |
+
"latent": [
|
| 97 |
+
"15",
|
| 98 |
+
0
|
| 99 |
+
]
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"2": {
|
| 103 |
+
"class_type": "LoadImage",
|
| 104 |
+
"inputs": {
|
| 105 |
+
"image": "face.jpg"
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"20": {
|
| 109 |
+
"class_type": "ReferenceLatent",
|
| 110 |
+
"inputs": {
|
| 111 |
+
"conditioning": [
|
| 112 |
+
"19",
|
| 113 |
+
0
|
| 114 |
+
],
|
| 115 |
+
"latent": [
|
| 116 |
+
"16",
|
| 117 |
+
0
|
| 118 |
+
]
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
"21": {
|
| 122 |
+
"class_type": "CFGGuider",
|
| 123 |
+
"inputs": {
|
| 124 |
+
"cfg": 1.0,
|
| 125 |
+
"model": [
|
| 126 |
+
"9",
|
| 127 |
+
0
|
| 128 |
+
],
|
| 129 |
+
"negative": [
|
| 130 |
+
"20",
|
| 131 |
+
0
|
| 132 |
+
],
|
| 133 |
+
"positive": [
|
| 134 |
+
"18",
|
| 135 |
+
0
|
| 136 |
+
]
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
"22": {
|
| 140 |
+
"class_type": "KSamplerSelect",
|
| 141 |
+
"inputs": {
|
| 142 |
+
"sampler_name": "euler"
|
| 143 |
+
}
|
| 144 |
+
},
|
| 145 |
+
"23": {
|
| 146 |
+
"class_type": "Flux2Scheduler",
|
| 147 |
+
"inputs": {
|
| 148 |
+
"height": [
|
| 149 |
+
"6",
|
| 150 |
+
1
|
| 151 |
+
],
|
| 152 |
+
"steps": 5,
|
| 153 |
+
"width": [
|
| 154 |
+
"6",
|
| 155 |
+
0
|
| 156 |
+
]
|
| 157 |
+
}
|
| 158 |
+
},
|
| 159 |
+
"24": {
|
| 160 |
+
"class_type": "RandomNoise",
|
| 161 |
+
"inputs": {
|
| 162 |
+
"noise_seed": 42
|
| 163 |
+
}
|
| 164 |
+
},
|
| 165 |
+
"25": {
|
| 166 |
+
"class_type": "SamplerCustomAdvanced",
|
| 167 |
+
"inputs": {
|
| 168 |
+
"guider": [
|
| 169 |
+
"21",
|
| 170 |
+
0
|
| 171 |
+
],
|
| 172 |
+
"latent_image": [
|
| 173 |
+
"7",
|
| 174 |
+
0
|
| 175 |
+
],
|
| 176 |
+
"noise": [
|
| 177 |
+
"24",
|
| 178 |
+
0
|
| 179 |
+
],
|
| 180 |
+
"sampler": [
|
| 181 |
+
"22",
|
| 182 |
+
0
|
| 183 |
+
],
|
| 184 |
+
"sigmas": [
|
| 185 |
+
"23",
|
| 186 |
+
0
|
| 187 |
+
]
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
"26": {
|
| 191 |
+
"class_type": "VAEDecode",
|
| 192 |
+
"inputs": {
|
| 193 |
+
"samples": [
|
| 194 |
+
"25",
|
| 195 |
+
0
|
| 196 |
+
],
|
| 197 |
+
"vae": [
|
| 198 |
+
"14",
|
| 199 |
+
0
|
| 200 |
+
]
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
"27": {
|
| 204 |
+
"class_type": "SaveImage",
|
| 205 |
+
"inputs": {
|
| 206 |
+
"filename_prefix": "new-faceswap-lissie-v4",
|
| 207 |
+
"images": [
|
| 208 |
+
"26",
|
| 209 |
+
0
|
| 210 |
+
]
|
| 211 |
+
}
|
| 212 |
+
},
|
| 213 |
+
"3": {
|
| 214 |
+
"class_type": "RMBG",
|
| 215 |
+
"inputs": {
|
| 216 |
+
"background": "Alpha",
|
| 217 |
+
"image": [
|
| 218 |
+
"2",
|
| 219 |
+
0
|
| 220 |
+
],
|
| 221 |
+
"invert_output": false,
|
| 222 |
+
"mask_blur": 0,
|
| 223 |
+
"mask_offset": 0,
|
| 224 |
+
"model": "RMBG-2.0",
|
| 225 |
+
"process_res": 1024,
|
| 226 |
+
"refine_foreground": false,
|
| 227 |
+
"sensitivity": 1
|
| 228 |
+
}
|
| 229 |
+
},
|
| 230 |
+
"30": {
|
| 231 |
+
"class_type": "ImageResizeKJv2",
|
| 232 |
+
"inputs": {
|
| 233 |
+
"crop_position": "center",
|
| 234 |
+
"device": "cpu",
|
| 235 |
+
"divisible_by": 2,
|
| 236 |
+
"height": 512,
|
| 237 |
+
"image": [
|
| 238 |
+
"1",
|
| 239 |
+
0
|
| 240 |
+
],
|
| 241 |
+
"keep_proportion": "resize",
|
| 242 |
+
"pad_color": "0, 0, 0",
|
| 243 |
+
"upscale_method": "lanczos",
|
| 244 |
+
"width": 512
|
| 245 |
+
}
|
| 246 |
+
},
|
| 247 |
+
"31": {
|
| 248 |
+
"class_type": "AILab_QwenVL",
|
| 249 |
+
"inputs": {
|
| 250 |
+
"attention_mode": "sdpa",
|
| 251 |
+
"custom_prompt": "Describe the facial expression.",
|
| 252 |
+
"image": [
|
| 253 |
+
"30",
|
| 254 |
+
0
|
| 255 |
+
],
|
| 256 |
+
"keep_model_loaded": false,
|
| 257 |
+
"max_tokens": 512,
|
| 258 |
+
"model_name": "Qwen3-VL-4B-Instruct",
|
| 259 |
+
"preset_prompt": "\ud83d\uddbc\ufe0f Tags",
|
| 260 |
+
"quantization": "None (FP16)",
|
| 261 |
+
"seed": 42
|
| 262 |
+
}
|
| 263 |
+
},
|
| 264 |
+
"32": {
|
| 265 |
+
"class_type": "StringConcatenate",
|
| 266 |
+
"inputs": {
|
| 267 |
+
"delimiter": "\n\n",
|
| 268 |
+
"string_a": "Remove the head (face and hair) from Picture 1 and replace it with the head (face and hair) from Picture 2. Keep the lighting, environment, and background from Picture 1. Relight the new head to match the scene. Keep the eye direction, head rotation, and facial expression from Picture 1.",
|
| 269 |
+
"string_b": [
|
| 270 |
+
"31",
|
| 271 |
+
0
|
| 272 |
+
]
|
| 273 |
+
}
|
| 274 |
+
},
|
| 275 |
+
"4": {
|
| 276 |
+
"class_type": "ResizeImageMaskNode",
|
| 277 |
+
"inputs": {
|
| 278 |
+
"input": [
|
| 279 |
+
"1",
|
| 280 |
+
0
|
| 281 |
+
],
|
| 282 |
+
"resize_type": "scale total pixels",
|
| 283 |
+
"resize_type.megapixels": 1.0,
|
| 284 |
+
"scale_method": "lanczos"
|
| 285 |
+
}
|
| 286 |
+
},
|
| 287 |
+
"5": {
|
| 288 |
+
"class_type": "ResizeImageMaskNode",
|
| 289 |
+
"inputs": {
|
| 290 |
+
"input": [
|
| 291 |
+
"3",
|
| 292 |
+
0
|
| 293 |
+
],
|
| 294 |
+
"resize_type": "scale total pixels",
|
| 295 |
+
"resize_type.megapixels": 1.0,
|
| 296 |
+
"scale_method": "lanczos"
|
| 297 |
+
}
|
| 298 |
+
},
|
| 299 |
+
"6": {
|
| 300 |
+
"class_type": "GetImageSize",
|
| 301 |
+
"inputs": {
|
| 302 |
+
"image": [
|
| 303 |
+
"4",
|
| 304 |
+
0
|
| 305 |
+
]
|
| 306 |
+
}
|
| 307 |
+
},
|
| 308 |
+
"7": {
|
| 309 |
+
"class_type": "EmptyFlux2LatentImage",
|
| 310 |
+
"inputs": {
|
| 311 |
+
"batch_size": 1,
|
| 312 |
+
"height": [
|
| 313 |
+
"6",
|
| 314 |
+
1
|
| 315 |
+
],
|
| 316 |
+
"width": [
|
| 317 |
+
"6",
|
| 318 |
+
0
|
| 319 |
+
]
|
| 320 |
+
}
|
| 321 |
+
},
|
| 322 |
+
"8": {
|
| 323 |
+
"class_type": "CLIPLoader",
|
| 324 |
+
"inputs": {
|
| 325 |
+
"clip_name": "qwen_3_8b.safetensors",
|
| 326 |
+
"device": "default",
|
| 327 |
+
"type": "flux2"
|
| 328 |
+
}
|
| 329 |
+
},
|
| 330 |
+
"9": {
|
| 331 |
+
"class_type": "UNETLoader",
|
| 332 |
+
"inputs": {
|
| 333 |
+
"unet_name": "flux-2-klein-9b.safetensors",
|
| 334 |
+
"weight_dtype": "default"
|
| 335 |
+
}
|
| 336 |
+
}
|
| 337 |
+
}
|
serverless/client/qwen-edit-nsfw.json
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"102": {
|
| 3 |
+
"class_type": "Image Saver Simple",
|
| 4 |
+
"inputs": {
|
| 5 |
+
"counter": 0,
|
| 6 |
+
"embed_workflow": true,
|
| 7 |
+
"extension": "webp",
|
| 8 |
+
"filename": "%time_%basemodelname_%seed",
|
| 9 |
+
"images": [
|
| 10 |
+
"8",
|
| 11 |
+
0
|
| 12 |
+
],
|
| 13 |
+
"lossless_webp": false,
|
| 14 |
+
"metadata": [
|
| 15 |
+
"106",
|
| 16 |
+
0
|
| 17 |
+
],
|
| 18 |
+
"optimize_png": false,
|
| 19 |
+
"path": "qwen_edit/%date",
|
| 20 |
+
"quality_jpeg_or_webp": 100,
|
| 21 |
+
"save_workflow_as_json": false,
|
| 22 |
+
"show_preview": true,
|
| 23 |
+
"time_format": "%Y-%m-%d-%H%M%S"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"103": {
|
| 27 |
+
"class_type": "Power Lora Loader (rgthree)",
|
| 28 |
+
"inputs": {
|
| 29 |
+
"clip": [
|
| 30 |
+
"118",
|
| 31 |
+
1
|
| 32 |
+
],
|
| 33 |
+
"lora_1": {
|
| 34 |
+
"lora": "qwen_edit\\next-scene_lora-v2-3000.safetensors",
|
| 35 |
+
"on": false,
|
| 36 |
+
"strength": 1,
|
| 37 |
+
"strengthTwo": null
|
| 38 |
+
},
|
| 39 |
+
"model": [
|
| 40 |
+
"118",
|
| 41 |
+
0
|
| 42 |
+
]
|
| 43 |
+
}
|
| 44 |
+
},
|
| 45 |
+
"106": {
|
| 46 |
+
"class_type": "Image Saver Metadata",
|
| 47 |
+
"inputs": {
|
| 48 |
+
"additional_hashes": "",
|
| 49 |
+
"cfg": 1,
|
| 50 |
+
"clip_skip": 0,
|
| 51 |
+
"custom": "",
|
| 52 |
+
"denoise": 1,
|
| 53 |
+
"download_civitai_data": true,
|
| 54 |
+
"easy_remix": true,
|
| 55 |
+
"height": 512,
|
| 56 |
+
"modelname": "Qwen-Rapid-AIO-NSFW-v11.4.safetensors",
|
| 57 |
+
"negative": "unknown",
|
| 58 |
+
"positive": "unknown",
|
| 59 |
+
"sampler_name": "euler",
|
| 60 |
+
"scheduler_name": "beta57",
|
| 61 |
+
"seed_value": [
|
| 62 |
+
"117",
|
| 63 |
+
0
|
| 64 |
+
],
|
| 65 |
+
"steps": [
|
| 66 |
+
"115",
|
| 67 |
+
0
|
| 68 |
+
],
|
| 69 |
+
"width": 512
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
"115": {
|
| 73 |
+
"_meta": {
|
| 74 |
+
"title": "Steps"
|
| 75 |
+
},
|
| 76 |
+
"class_type": "INTConstant",
|
| 77 |
+
"inputs": {
|
| 78 |
+
"value": 8
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"117": {
|
| 82 |
+
"_meta": {
|
| 83 |
+
"title": "Seed"
|
| 84 |
+
},
|
| 85 |
+
"class_type": "PrimitiveInt",
|
| 86 |
+
"inputs": {
|
| 87 |
+
"value": 0
|
| 88 |
+
}
|
| 89 |
+
},
|
| 90 |
+
"118": {
|
| 91 |
+
"class_type": "CheckpointLoaderSimple",
|
| 92 |
+
"inputs": {
|
| 93 |
+
"ckpt_name": "Qwen-Rapid-AIO-NSFW-v11.4.safetensors"
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
"119": {
|
| 97 |
+
"class_type": "TextEncodeQwenImageEditPlus",
|
| 98 |
+
"inputs": {
|
| 99 |
+
"clip": [
|
| 100 |
+
"103",
|
| 101 |
+
1
|
| 102 |
+
],
|
| 103 |
+
"image1": [
|
| 104 |
+
"78",
|
| 105 |
+
0
|
| 106 |
+
],
|
| 107 |
+
"prompt": "replace the outfit of the subject with a sexy white crop top tank top with a v neck and jean shorts, don't change anything else",
|
| 108 |
+
"vae": [
|
| 109 |
+
"118",
|
| 110 |
+
2
|
| 111 |
+
]
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
"121": {
|
| 115 |
+
"class_type": "ClownsharKSampler_Beta",
|
| 116 |
+
"inputs": {
|
| 117 |
+
"bongmath": true,
|
| 118 |
+
"cfg": 1,
|
| 119 |
+
"denoise": 1,
|
| 120 |
+
"eta": 0.5,
|
| 121 |
+
"latent_image": [
|
| 122 |
+
"88",
|
| 123 |
+
0
|
| 124 |
+
],
|
| 125 |
+
"model": [
|
| 126 |
+
"75",
|
| 127 |
+
0
|
| 128 |
+
],
|
| 129 |
+
"negative": [
|
| 130 |
+
"77",
|
| 131 |
+
0
|
| 132 |
+
],
|
| 133 |
+
"positive": [
|
| 134 |
+
"119",
|
| 135 |
+
0
|
| 136 |
+
],
|
| 137 |
+
"sampler_mode": "standard",
|
| 138 |
+
"sampler_name": "linear/euler",
|
| 139 |
+
"scheduler": "simple",
|
| 140 |
+
"seed": [
|
| 141 |
+
"117",
|
| 142 |
+
0
|
| 143 |
+
],
|
| 144 |
+
"steps": [
|
| 145 |
+
"115",
|
| 146 |
+
0
|
| 147 |
+
],
|
| 148 |
+
"steps_to_run": -1
|
| 149 |
+
}
|
| 150 |
+
},
|
| 151 |
+
"66": {
|
| 152 |
+
"class_type": "ModelSamplingAuraFlow",
|
| 153 |
+
"inputs": {
|
| 154 |
+
"model": [
|
| 155 |
+
"103",
|
| 156 |
+
0
|
| 157 |
+
],
|
| 158 |
+
"shift": 3
|
| 159 |
+
}
|
| 160 |
+
},
|
| 161 |
+
"75": {
|
| 162 |
+
"class_type": "CFGNorm",
|
| 163 |
+
"inputs": {
|
| 164 |
+
"model": [
|
| 165 |
+
"66",
|
| 166 |
+
0
|
| 167 |
+
],
|
| 168 |
+
"pre_cfg": false,
|
| 169 |
+
"strength": 1
|
| 170 |
+
}
|
| 171 |
+
},
|
| 172 |
+
"77": {
|
| 173 |
+
"class_type": "TextEncodeQwenImageEdit",
|
| 174 |
+
"inputs": {
|
| 175 |
+
"clip": [
|
| 176 |
+
"103",
|
| 177 |
+
1
|
| 178 |
+
],
|
| 179 |
+
"image": [
|
| 180 |
+
"78",
|
| 181 |
+
0
|
| 182 |
+
],
|
| 183 |
+
"prompt": "ugly, blurry, distorted, artifacts, bad, wrong, low quality, anime, digital art, semirealistic, cartoon, manga, drawing, fake, unreal, ",
|
| 184 |
+
"vae": [
|
| 185 |
+
"118",
|
| 186 |
+
2
|
| 187 |
+
]
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
"78": {
|
| 191 |
+
"class_type": "LoadImage",
|
| 192 |
+
"inputs": {
|
| 193 |
+
"image": "b.jpg"
|
| 194 |
+
}
|
| 195 |
+
},
|
| 196 |
+
"8": {
|
| 197 |
+
"class_type": "VAEDecode",
|
| 198 |
+
"inputs": {
|
| 199 |
+
"samples": [
|
| 200 |
+
"121",
|
| 201 |
+
1
|
| 202 |
+
],
|
| 203 |
+
"vae": [
|
| 204 |
+
"118",
|
| 205 |
+
2
|
| 206 |
+
]
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"88": {
|
| 210 |
+
"class_type": "VAEEncode",
|
| 211 |
+
"inputs": {
|
| 212 |
+
"pixels": [
|
| 213 |
+
"93",
|
| 214 |
+
0
|
| 215 |
+
],
|
| 216 |
+
"vae": [
|
| 217 |
+
"118",
|
| 218 |
+
2
|
| 219 |
+
]
|
| 220 |
+
}
|
| 221 |
+
},
|
| 222 |
+
"93": {
|
| 223 |
+
"class_type": "ImageScaleToTotalPixels",
|
| 224 |
+
"inputs": {
|
| 225 |
+
"image": [
|
| 226 |
+
"78",
|
| 227 |
+
0
|
| 228 |
+
],
|
| 229 |
+
"megapixels": 1,
|
| 230 |
+
"resolution_steps": 1,
|
| 231 |
+
"upscale_method": "lanczos"
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
}
|
serverless/client/qwen_edit_nsfw_async.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Call the qwen-edit-nsfw RunPod serverless endpoint ASYNCHRONOUSLY (/run).
|
| 3 |
+
|
| 4 |
+
Standalone: python 3.8+, stdlib only. Same arguments as qwen_edit_nsfw_sync.py
|
| 5 |
+
plus:
|
| 6 |
+
--no-wait submit only; print the job id and exit
|
| 7 |
+
--job-id ID skip submission; poll/collect an existing job
|
| 8 |
+
--poll SECONDS poll interval (default 3)
|
| 9 |
+
--cancel JOB_ID cancel a queued/running job and exit
|
| 10 |
+
|
| 11 |
+
# fire-and-forget, collect later
|
| 12 |
+
python qwen_edit_nsfw_async.py --image person.jpg --prompt "..." --no-wait
|
| 13 |
+
python qwen_edit_nsfw_async.py --job-id JOB_ID --out ./results
|
| 14 |
+
"""
|
| 15 |
+
import argparse
|
| 16 |
+
import base64
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import pathlib
|
| 20 |
+
import sys
|
| 21 |
+
import time
|
| 22 |
+
import urllib.error
|
| 23 |
+
import urllib.request
|
| 24 |
+
|
| 25 |
+
DEFAULT_ENDPOINT = "tplcz9jtzihoxa"
|
| 26 |
+
TERMINAL = ("COMPLETED", "FAILED", "CANCELLED", "TIMED_OUT")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def api(args, method, path, payload=None, timeout=90):
|
| 30 |
+
req = urllib.request.Request(
|
| 31 |
+
f"https://api.runpod.ai/v2/{args.endpoint_id}/{path}", method=method,
|
| 32 |
+
data=json.dumps(payload).encode() if payload is not None else None,
|
| 33 |
+
headers={"Content-Type": "application/json",
|
| 34 |
+
"Authorization": f"Bearer {args.api_key}"})
|
| 35 |
+
try:
|
| 36 |
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
| 37 |
+
return json.load(r)
|
| 38 |
+
except urllib.error.HTTPError as e:
|
| 39 |
+
sys.exit(f"ERROR: HTTP {e.code} on /{path}: {e.read().decode(errors='replace')[:2000]}")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def main():
|
| 43 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 44 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 45 |
+
ap.add_argument("--api-key", default=os.environ.get("RUNPOD_API_KEY"))
|
| 46 |
+
ap.add_argument("--endpoint-id", default=DEFAULT_ENDPOINT)
|
| 47 |
+
ap.add_argument("--image")
|
| 48 |
+
ap.add_argument("--prompt")
|
| 49 |
+
ap.add_argument("--negative-prompt")
|
| 50 |
+
ap.add_argument("--steps", type=int)
|
| 51 |
+
ap.add_argument("--seed", type=int)
|
| 52 |
+
ap.add_argument("--megapixels", type=float)
|
| 53 |
+
ap.add_argument("--cfg", type=float)
|
| 54 |
+
ap.add_argument("--eta", type=float)
|
| 55 |
+
ap.add_argument("--sampler")
|
| 56 |
+
ap.add_argument("--scheduler")
|
| 57 |
+
ap.add_argument("--denoise", type=float)
|
| 58 |
+
ap.add_argument("--lora-next-scene", action="store_true")
|
| 59 |
+
ap.add_argument("--lora-next-scene-strength", type=float, default=1.0)
|
| 60 |
+
ap.add_argument("--workflow")
|
| 61 |
+
ap.add_argument("--set", action="append", metavar="NODE.INPUT=VALUE")
|
| 62 |
+
ap.add_argument("--workflow-json")
|
| 63 |
+
ap.add_argument("--out", default=".")
|
| 64 |
+
ap.add_argument("--timeout", type=float, default=1800)
|
| 65 |
+
ap.add_argument("--poll", type=float, default=3.0)
|
| 66 |
+
ap.add_argument("--no-wait", action="store_true")
|
| 67 |
+
ap.add_argument("--job-id")
|
| 68 |
+
ap.add_argument("--cancel", metavar="JOB_ID")
|
| 69 |
+
args = ap.parse_args()
|
| 70 |
+
if not args.api_key:
|
| 71 |
+
sys.exit("ERROR: pass --api-key or set RUNPOD_API_KEY")
|
| 72 |
+
|
| 73 |
+
if args.cancel:
|
| 74 |
+
print(json.dumps(api(args, "POST", f"cancel/{args.cancel}"), indent=2))
|
| 75 |
+
return
|
| 76 |
+
|
| 77 |
+
if args.job_id:
|
| 78 |
+
job_id = args.job_id
|
| 79 |
+
else:
|
| 80 |
+
if not args.image or not args.prompt:
|
| 81 |
+
sys.exit("ERROR: --image and --prompt are required to submit a job")
|
| 82 |
+
params = {"prompt": args.prompt}
|
| 83 |
+
for cli, p in [("negative_prompt", "negative_prompt"), ("steps", "steps"),
|
| 84 |
+
("seed", "seed"), ("megapixels", "megapixels"), ("cfg", "cfg"),
|
| 85 |
+
("eta", "eta"), ("sampler", "sampler"), ("scheduler", "scheduler"),
|
| 86 |
+
("denoise", "denoise")]:
|
| 87 |
+
v = getattr(args, cli)
|
| 88 |
+
if v is not None:
|
| 89 |
+
params[p] = v
|
| 90 |
+
if args.lora_next_scene:
|
| 91 |
+
params["lora_next_scene"] = True
|
| 92 |
+
params["lora_next_scene_strength"] = args.lora_next_scene_strength
|
| 93 |
+
payload = {
|
| 94 |
+
"images": [{"name": os.path.basename(args.image),
|
| 95 |
+
"image": base64.b64encode(open(args.image, "rb").read()).decode()}],
|
| 96 |
+
"params": params,
|
| 97 |
+
}
|
| 98 |
+
if args.workflow:
|
| 99 |
+
payload["workflow"] = args.workflow
|
| 100 |
+
if args.workflow_json:
|
| 101 |
+
payload["workflow_json"] = json.load(open(args.workflow_json))
|
| 102 |
+
overrides = {}
|
| 103 |
+
for s in args.set or []:
|
| 104 |
+
k, v = s.split("=", 1)
|
| 105 |
+
try:
|
| 106 |
+
overrides[k] = json.loads(v)
|
| 107 |
+
except json.JSONDecodeError:
|
| 108 |
+
overrides[k] = v
|
| 109 |
+
if overrides:
|
| 110 |
+
payload["set"] = overrides
|
| 111 |
+
job = api(args, "POST", "run", {"input": payload})
|
| 112 |
+
job_id = job["id"]
|
| 113 |
+
print(f"# submitted job {job_id}", file=sys.stderr)
|
| 114 |
+
if args.no_wait:
|
| 115 |
+
print(job_id)
|
| 116 |
+
return
|
| 117 |
+
|
| 118 |
+
t0 = time.monotonic()
|
| 119 |
+
while True:
|
| 120 |
+
result = api(args, "GET", f"status/{job_id}")
|
| 121 |
+
status = result.get("status")
|
| 122 |
+
if status in TERMINAL:
|
| 123 |
+
break
|
| 124 |
+
if time.monotonic() - t0 > args.timeout:
|
| 125 |
+
sys.exit(f"ERROR: timed out after {args.timeout}s (job {job_id} still collectable with --job-id)")
|
| 126 |
+
print(f"# {status} ... {time.monotonic()-t0:.0f}s", file=sys.stderr)
|
| 127 |
+
time.sleep(args.poll)
|
| 128 |
+
|
| 129 |
+
if status != "COMPLETED":
|
| 130 |
+
sys.exit(f"ERROR: {json.dumps(result, indent=2)[:3000]}")
|
| 131 |
+
output = result["output"]
|
| 132 |
+
if "error" in output:
|
| 133 |
+
sys.exit(f"ERROR from handler: {output['error']}")
|
| 134 |
+
out_dir = pathlib.Path(args.out)
|
| 135 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 136 |
+
for i, img in enumerate(output["images"]):
|
| 137 |
+
p = out_dir / f"{int(time.time())}-{i}-{img['filename']}"
|
| 138 |
+
p.write_bytes(base64.b64decode(img["data"]))
|
| 139 |
+
print(p)
|
| 140 |
+
print(f"# seed={output.get('seed')} delay={result.get('delayTime')}ms "
|
| 141 |
+
f"exec={result.get('executionTime')}ms", file=sys.stderr)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
if __name__ == "__main__":
|
| 145 |
+
main()
|
serverless/client/qwen_edit_nsfw_sync.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Call the qwen-edit-nsfw RunPod serverless endpoint SYNCHRONOUSLY (/runsync).
|
| 3 |
+
|
| 4 |
+
Standalone: python 3.8+, stdlib only. Single-image edit on the
|
| 5 |
+
Qwen-Rapid-AIO-NSFW v11.4 checkpoint (workflow qwen-edit-nsfw).
|
| 6 |
+
|
| 7 |
+
python qwen_edit_nsfw_sync.py \
|
| 8 |
+
--api-key $RUNPOD_API_KEY \
|
| 9 |
+
--image person.jpg \
|
| 10 |
+
--prompt "replace the outfit with ..." \
|
| 11 |
+
--out ./results
|
| 12 |
+
|
| 13 |
+
All arguments:
|
| 14 |
+
--api-key RunPod API key (or set RUNPOD_API_KEY)
|
| 15 |
+
--endpoint-id RunPod endpoint id (default: ENDPOINT_DEFAULT below)
|
| 16 |
+
--image input image (required)
|
| 17 |
+
--prompt edit instruction (required)
|
| 18 |
+
--negative-prompt negative conditioning text
|
| 19 |
+
--steps sampler steps (default 8)
|
| 20 |
+
--seed integer seed (default: random, printed back)
|
| 21 |
+
--megapixels working resolution in MP (default 1.0)
|
| 22 |
+
--cfg / --eta / --sampler / --scheduler / --denoise sampler tuning
|
| 23 |
+
--lora-next-scene [--lora-next-scene-strength] enable next-scene lora
|
| 24 |
+
--workflow baked workflow name (default qwen-edit-nsfw)
|
| 25 |
+
--set NODE.INPUT=VALUE raw graph override, repeatable
|
| 26 |
+
--workflow-json FILE full API-format graph passthrough
|
| 27 |
+
--out output directory (default .)
|
| 28 |
+
--timeout max seconds (default 600)
|
| 29 |
+
"""
|
| 30 |
+
import argparse
|
| 31 |
+
import base64
|
| 32 |
+
import json
|
| 33 |
+
import os
|
| 34 |
+
import pathlib
|
| 35 |
+
import sys
|
| 36 |
+
import time
|
| 37 |
+
import urllib.error
|
| 38 |
+
import urllib.request
|
| 39 |
+
|
| 40 |
+
DEFAULT_ENDPOINT = "tplcz9jtzihoxa"
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def main():
|
| 44 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 45 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 46 |
+
ap.add_argument("--api-key", default=os.environ.get("RUNPOD_API_KEY"))
|
| 47 |
+
ap.add_argument("--endpoint-id", default=DEFAULT_ENDPOINT)
|
| 48 |
+
ap.add_argument("--image", required=True)
|
| 49 |
+
ap.add_argument("--prompt", required=True)
|
| 50 |
+
ap.add_argument("--negative-prompt")
|
| 51 |
+
ap.add_argument("--steps", type=int)
|
| 52 |
+
ap.add_argument("--seed", type=int)
|
| 53 |
+
ap.add_argument("--megapixels", type=float)
|
| 54 |
+
ap.add_argument("--cfg", type=float)
|
| 55 |
+
ap.add_argument("--eta", type=float)
|
| 56 |
+
ap.add_argument("--sampler")
|
| 57 |
+
ap.add_argument("--scheduler")
|
| 58 |
+
ap.add_argument("--denoise", type=float)
|
| 59 |
+
ap.add_argument("--lora-next-scene", action="store_true")
|
| 60 |
+
ap.add_argument("--lora-next-scene-strength", type=float, default=1.0)
|
| 61 |
+
ap.add_argument("--workflow")
|
| 62 |
+
ap.add_argument("--set", action="append", metavar="NODE.INPUT=VALUE")
|
| 63 |
+
ap.add_argument("--workflow-json")
|
| 64 |
+
ap.add_argument("--out", default=".")
|
| 65 |
+
ap.add_argument("--timeout", type=float, default=600)
|
| 66 |
+
args = ap.parse_args()
|
| 67 |
+
if not args.api_key:
|
| 68 |
+
sys.exit("ERROR: pass --api-key or set RUNPOD_API_KEY")
|
| 69 |
+
|
| 70 |
+
params = {"prompt": args.prompt}
|
| 71 |
+
for cli, p in [("negative_prompt", "negative_prompt"), ("steps", "steps"),
|
| 72 |
+
("seed", "seed"), ("megapixels", "megapixels"), ("cfg", "cfg"),
|
| 73 |
+
("eta", "eta"), ("sampler", "sampler"), ("scheduler", "scheduler"),
|
| 74 |
+
("denoise", "denoise")]:
|
| 75 |
+
v = getattr(args, cli)
|
| 76 |
+
if v is not None:
|
| 77 |
+
params[p] = v
|
| 78 |
+
if args.lora_next_scene:
|
| 79 |
+
params["lora_next_scene"] = True
|
| 80 |
+
params["lora_next_scene_strength"] = args.lora_next_scene_strength
|
| 81 |
+
|
| 82 |
+
payload = {
|
| 83 |
+
"images": [{"name": os.path.basename(args.image),
|
| 84 |
+
"image": base64.b64encode(open(args.image, "rb").read()).decode()}],
|
| 85 |
+
"params": params,
|
| 86 |
+
}
|
| 87 |
+
if args.workflow:
|
| 88 |
+
payload["workflow"] = args.workflow
|
| 89 |
+
if args.workflow_json:
|
| 90 |
+
payload["workflow_json"] = json.load(open(args.workflow_json))
|
| 91 |
+
overrides = {}
|
| 92 |
+
for s in args.set or []:
|
| 93 |
+
k, v = s.split("=", 1)
|
| 94 |
+
try:
|
| 95 |
+
overrides[k] = json.loads(v)
|
| 96 |
+
except json.JSONDecodeError:
|
| 97 |
+
overrides[k] = v
|
| 98 |
+
if overrides:
|
| 99 |
+
payload["set"] = overrides
|
| 100 |
+
|
| 101 |
+
req = urllib.request.Request(
|
| 102 |
+
f"https://api.runpod.ai/v2/{args.endpoint_id}/runsync",
|
| 103 |
+
data=json.dumps({"input": payload}).encode(),
|
| 104 |
+
headers={"Content-Type": "application/json",
|
| 105 |
+
"Authorization": f"Bearer {args.api_key}"})
|
| 106 |
+
t0 = time.monotonic()
|
| 107 |
+
try:
|
| 108 |
+
with urllib.request.urlopen(req, timeout=args.timeout) as r:
|
| 109 |
+
result = json.load(r)
|
| 110 |
+
except urllib.error.HTTPError as e:
|
| 111 |
+
sys.exit(f"ERROR: HTTP {e.code}: {e.read().decode(errors='replace')[:2000]}")
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# /runsync answers early (IN_QUEUE/IN_PROGRESS) when a job outlives the
|
| 115 |
+
# sync window (~90 s), e.g. during a cold start — fall back to polling
|
| 116 |
+
TERMINAL = ("COMPLETED", "FAILED", "CANCELLED", "TIMED_OUT")
|
| 117 |
+
while result.get("status") not in TERMINAL and result.get("id"):
|
| 118 |
+
if time.monotonic() - t0 > args.timeout:
|
| 119 |
+
sys.exit(f"ERROR: timed out after {args.timeout}s "
|
| 120 |
+
f"(job {result['id']} status {result.get('status')})")
|
| 121 |
+
time.sleep(3)
|
| 122 |
+
poll = urllib.request.Request(
|
| 123 |
+
f"https://api.runpod.ai/v2/{args.endpoint_id}/status/{result['id']}",
|
| 124 |
+
headers={"Authorization": f"Bearer {args.api_key}"})
|
| 125 |
+
with urllib.request.urlopen(poll, timeout=90) as r:
|
| 126 |
+
result = json.load(r)
|
| 127 |
+
|
| 128 |
+
if result.get("status") != "COMPLETED":
|
| 129 |
+
sys.exit(f"ERROR: {json.dumps(result, indent=2)[:3000]}")
|
| 130 |
+
output = result["output"]
|
| 131 |
+
if "error" in output:
|
| 132 |
+
sys.exit(f"ERROR from handler: {output['error']}")
|
| 133 |
+
out_dir = pathlib.Path(args.out)
|
| 134 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 135 |
+
for i, img in enumerate(output["images"]):
|
| 136 |
+
p = out_dir / f"{int(time.time())}-{i}-{img['filename']}"
|
| 137 |
+
p.write_bytes(base64.b64decode(img["data"]))
|
| 138 |
+
print(p)
|
| 139 |
+
print(f"# seed={output.get('seed')} wall={time.monotonic()-t0:.1f}s "
|
| 140 |
+
f"delay={result.get('delayTime')}ms exec={result.get('executionTime')}ms",
|
| 141 |
+
file=sys.stderr)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
if __name__ == "__main__":
|
| 145 |
+
main()
|
serverless/client/qwen_edit_sync.py
CHANGED
|
@@ -129,6 +129,21 @@ def main():
|
|
| 129 |
except urllib.error.HTTPError as e:
|
| 130 |
sys.exit(f"ERROR: HTTP {e.code}: {e.read().decode(errors='replace')[:2000]}")
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
if result.get("status") != "COMPLETED":
|
| 133 |
sys.exit(f"ERROR: {json.dumps(result, indent=2)[:3000]}")
|
| 134 |
output = result["output"]
|
|
|
|
| 129 |
except urllib.error.HTTPError as e:
|
| 130 |
sys.exit(f"ERROR: HTTP {e.code}: {e.read().decode(errors='replace')[:2000]}")
|
| 131 |
|
| 132 |
+
|
| 133 |
+
# /runsync answers early (IN_QUEUE/IN_PROGRESS) when a job outlives the
|
| 134 |
+
# sync window (~90 s), e.g. during a cold start — fall back to polling
|
| 135 |
+
TERMINAL = ("COMPLETED", "FAILED", "CANCELLED", "TIMED_OUT")
|
| 136 |
+
while result.get("status") not in TERMINAL and result.get("id"):
|
| 137 |
+
if time.monotonic() - t0 > args.timeout:
|
| 138 |
+
sys.exit(f"ERROR: timed out after {args.timeout}s "
|
| 139 |
+
f"(job {result['id']} status {result.get('status')})")
|
| 140 |
+
time.sleep(3)
|
| 141 |
+
poll = urllib.request.Request(
|
| 142 |
+
f"https://api.runpod.ai/v2/{args.endpoint_id}/status/{result['id']}",
|
| 143 |
+
headers={"Authorization": f"Bearer {args.api_key}"})
|
| 144 |
+
with urllib.request.urlopen(poll, timeout=90) as r:
|
| 145 |
+
result = json.load(r)
|
| 146 |
+
|
| 147 |
if result.get("status") != "COMPLETED":
|
| 148 |
sys.exit(f"ERROR: {json.dumps(result, indent=2)[:3000]}")
|
| 149 |
output = result["output"]
|
serverless/endpoints/faceswap-lissie/endpoint.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "faceswap-lissie",
|
| 3 |
+
"description": "Flux-2-Klein head/face swap with QwenVL expression capture (new-faceswap-lissie-v4)",
|
| 4 |
+
"default_workflow": "new-faceswap-lissie-v4",
|
| 5 |
+
"hf_repo": "aleph65/ComfyUI",
|
| 6 |
+
"models": [
|
| 7 |
+
"models/diffusion_models/flux-2-klein-9b.safetensors",
|
| 8 |
+
"models/text_encoders/qwen_3_8b.safetensors",
|
| 9 |
+
"models/vae/flux2-vae.safetensors",
|
| 10 |
+
"models/loras/flux2/klein/bfs_head_v1_flux-klein_9b_step3500_rank128.safetensors",
|
| 11 |
+
"models/loras/lissie_flux2_klein.safetensors",
|
| 12 |
+
"models/LLM/Qwen-VL/Qwen3-VL-4B-Instruct",
|
| 13 |
+
"models/RMBG/RMBG-2.0"
|
| 14 |
+
],
|
| 15 |
+
"custom_nodes": [
|
| 16 |
+
"ComfyUI-KJNodes",
|
| 17 |
+
"ComfyUI-QwenVL",
|
| 18 |
+
"ComfyUI-RMBG"
|
| 19 |
+
],
|
| 20 |
+
"extra_layers": ["pipdeps-delta.tgz"],
|
| 21 |
+
"comfy_args": "--highvram"
|
| 22 |
+
}
|
serverless/endpoints/faceswap-lissie/params/new-faceswap-lissie-v4.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"params": {
|
| 3 |
+
"body_image": {"node": "1", "input": "image", "type": "image"},
|
| 4 |
+
"face_image": {"node": "2", "input": "image", "type": "image"},
|
| 5 |
+
"prompt": {"node": "32", "input": "string_a"},
|
| 6 |
+
"instruction": {"node": "32", "input": "string_a"},
|
| 7 |
+
"expression_prompt": {"node": "31", "input": "custom_prompt"},
|
| 8 |
+
"negative_prompt": {"node": "13", "input": "text"},
|
| 9 |
+
"steps": {"node": "23", "input": "steps"},
|
| 10 |
+
"cfg": {"node": "21", "input": "cfg"},
|
| 11 |
+
"seed": {"node": "24", "input": "noise_seed"},
|
| 12 |
+
"keep_vl_loaded": {"node": "31", "input": "keep_model_loaded"},
|
| 13 |
+
"rmbg_sensitivity": {"node": "3", "input": "sensitivity"},
|
| 14 |
+
"rmbg_process_res": {"node": "3", "input": "process_res"}
|
| 15 |
+
},
|
| 16 |
+
"defaults": {"keep_vl_loaded": true},
|
| 17 |
+
"image_slots": ["body_image", "face_image"],
|
| 18 |
+
"when_image_absent": {}
|
| 19 |
+
}
|
serverless/endpoints/faceswap-lissie/workflows/new-faceswap-lissie-v4.json
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"1": {
|
| 3 |
+
"class_type": "LoadImage",
|
| 4 |
+
"inputs": {
|
| 5 |
+
"image": "body.jpg"
|
| 6 |
+
}
|
| 7 |
+
},
|
| 8 |
+
"12": {
|
| 9 |
+
"class_type": "CLIPTextEncode",
|
| 10 |
+
"inputs": {
|
| 11 |
+
"clip": [
|
| 12 |
+
"8",
|
| 13 |
+
0
|
| 14 |
+
],
|
| 15 |
+
"text": [
|
| 16 |
+
"32",
|
| 17 |
+
0
|
| 18 |
+
]
|
| 19 |
+
}
|
| 20 |
+
},
|
| 21 |
+
"13": {
|
| 22 |
+
"class_type": "CLIPTextEncode",
|
| 23 |
+
"inputs": {
|
| 24 |
+
"clip": [
|
| 25 |
+
"8",
|
| 26 |
+
0
|
| 27 |
+
],
|
| 28 |
+
"text": "bad quality, noise, blurry, worst quality, low resolution, blur, distortion, unnatural blending, cartoon, illustration, painting"
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
"14": {
|
| 32 |
+
"class_type": "VAELoader",
|
| 33 |
+
"inputs": {
|
| 34 |
+
"vae_name": "flux2-vae.safetensors"
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"15": {
|
| 38 |
+
"class_type": "VAEEncode",
|
| 39 |
+
"inputs": {
|
| 40 |
+
"pixels": [
|
| 41 |
+
"4",
|
| 42 |
+
0
|
| 43 |
+
],
|
| 44 |
+
"vae": [
|
| 45 |
+
"14",
|
| 46 |
+
0
|
| 47 |
+
]
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
+
"16": {
|
| 51 |
+
"class_type": "VAEEncode",
|
| 52 |
+
"inputs": {
|
| 53 |
+
"pixels": [
|
| 54 |
+
"5",
|
| 55 |
+
0
|
| 56 |
+
],
|
| 57 |
+
"vae": [
|
| 58 |
+
"14",
|
| 59 |
+
0
|
| 60 |
+
]
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"17": {
|
| 64 |
+
"class_type": "ReferenceLatent",
|
| 65 |
+
"inputs": {
|
| 66 |
+
"conditioning": [
|
| 67 |
+
"12",
|
| 68 |
+
0
|
| 69 |
+
],
|
| 70 |
+
"latent": [
|
| 71 |
+
"15",
|
| 72 |
+
0
|
| 73 |
+
]
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"18": {
|
| 77 |
+
"class_type": "ReferenceLatent",
|
| 78 |
+
"inputs": {
|
| 79 |
+
"conditioning": [
|
| 80 |
+
"17",
|
| 81 |
+
0
|
| 82 |
+
],
|
| 83 |
+
"latent": [
|
| 84 |
+
"16",
|
| 85 |
+
0
|
| 86 |
+
]
|
| 87 |
+
}
|
| 88 |
+
},
|
| 89 |
+
"19": {
|
| 90 |
+
"class_type": "ReferenceLatent",
|
| 91 |
+
"inputs": {
|
| 92 |
+
"conditioning": [
|
| 93 |
+
"13",
|
| 94 |
+
0
|
| 95 |
+
],
|
| 96 |
+
"latent": [
|
| 97 |
+
"15",
|
| 98 |
+
0
|
| 99 |
+
]
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"2": {
|
| 103 |
+
"class_type": "LoadImage",
|
| 104 |
+
"inputs": {
|
| 105 |
+
"image": "face.jpg"
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"20": {
|
| 109 |
+
"class_type": "ReferenceLatent",
|
| 110 |
+
"inputs": {
|
| 111 |
+
"conditioning": [
|
| 112 |
+
"19",
|
| 113 |
+
0
|
| 114 |
+
],
|
| 115 |
+
"latent": [
|
| 116 |
+
"16",
|
| 117 |
+
0
|
| 118 |
+
]
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
"21": {
|
| 122 |
+
"class_type": "CFGGuider",
|
| 123 |
+
"inputs": {
|
| 124 |
+
"cfg": 1.0,
|
| 125 |
+
"model": [
|
| 126 |
+
"9",
|
| 127 |
+
0
|
| 128 |
+
],
|
| 129 |
+
"negative": [
|
| 130 |
+
"20",
|
| 131 |
+
0
|
| 132 |
+
],
|
| 133 |
+
"positive": [
|
| 134 |
+
"18",
|
| 135 |
+
0
|
| 136 |
+
]
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
"22": {
|
| 140 |
+
"class_type": "KSamplerSelect",
|
| 141 |
+
"inputs": {
|
| 142 |
+
"sampler_name": "euler"
|
| 143 |
+
}
|
| 144 |
+
},
|
| 145 |
+
"23": {
|
| 146 |
+
"class_type": "Flux2Scheduler",
|
| 147 |
+
"inputs": {
|
| 148 |
+
"height": [
|
| 149 |
+
"6",
|
| 150 |
+
1
|
| 151 |
+
],
|
| 152 |
+
"steps": 5,
|
| 153 |
+
"width": [
|
| 154 |
+
"6",
|
| 155 |
+
0
|
| 156 |
+
]
|
| 157 |
+
}
|
| 158 |
+
},
|
| 159 |
+
"24": {
|
| 160 |
+
"class_type": "RandomNoise",
|
| 161 |
+
"inputs": {
|
| 162 |
+
"noise_seed": 42
|
| 163 |
+
}
|
| 164 |
+
},
|
| 165 |
+
"25": {
|
| 166 |
+
"class_type": "SamplerCustomAdvanced",
|
| 167 |
+
"inputs": {
|
| 168 |
+
"guider": [
|
| 169 |
+
"21",
|
| 170 |
+
0
|
| 171 |
+
],
|
| 172 |
+
"latent_image": [
|
| 173 |
+
"7",
|
| 174 |
+
0
|
| 175 |
+
],
|
| 176 |
+
"noise": [
|
| 177 |
+
"24",
|
| 178 |
+
0
|
| 179 |
+
],
|
| 180 |
+
"sampler": [
|
| 181 |
+
"22",
|
| 182 |
+
0
|
| 183 |
+
],
|
| 184 |
+
"sigmas": [
|
| 185 |
+
"23",
|
| 186 |
+
0
|
| 187 |
+
]
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
"26": {
|
| 191 |
+
"class_type": "VAEDecode",
|
| 192 |
+
"inputs": {
|
| 193 |
+
"samples": [
|
| 194 |
+
"25",
|
| 195 |
+
0
|
| 196 |
+
],
|
| 197 |
+
"vae": [
|
| 198 |
+
"14",
|
| 199 |
+
0
|
| 200 |
+
]
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
"27": {
|
| 204 |
+
"class_type": "SaveImage",
|
| 205 |
+
"inputs": {
|
| 206 |
+
"filename_prefix": "new-faceswap-lissie-v4",
|
| 207 |
+
"images": [
|
| 208 |
+
"26",
|
| 209 |
+
0
|
| 210 |
+
]
|
| 211 |
+
}
|
| 212 |
+
},
|
| 213 |
+
"3": {
|
| 214 |
+
"class_type": "RMBG",
|
| 215 |
+
"inputs": {
|
| 216 |
+
"background": "Alpha",
|
| 217 |
+
"image": [
|
| 218 |
+
"2",
|
| 219 |
+
0
|
| 220 |
+
],
|
| 221 |
+
"invert_output": false,
|
| 222 |
+
"mask_blur": 0,
|
| 223 |
+
"mask_offset": 0,
|
| 224 |
+
"model": "RMBG-2.0",
|
| 225 |
+
"process_res": 1024,
|
| 226 |
+
"refine_foreground": false,
|
| 227 |
+
"sensitivity": 1
|
| 228 |
+
}
|
| 229 |
+
},
|
| 230 |
+
"30": {
|
| 231 |
+
"class_type": "ImageResizeKJv2",
|
| 232 |
+
"inputs": {
|
| 233 |
+
"crop_position": "center",
|
| 234 |
+
"device": "cpu",
|
| 235 |
+
"divisible_by": 2,
|
| 236 |
+
"height": 512,
|
| 237 |
+
"image": [
|
| 238 |
+
"1",
|
| 239 |
+
0
|
| 240 |
+
],
|
| 241 |
+
"keep_proportion": "resize",
|
| 242 |
+
"pad_color": "0, 0, 0",
|
| 243 |
+
"upscale_method": "lanczos",
|
| 244 |
+
"width": 512
|
| 245 |
+
}
|
| 246 |
+
},
|
| 247 |
+
"31": {
|
| 248 |
+
"class_type": "AILab_QwenVL",
|
| 249 |
+
"inputs": {
|
| 250 |
+
"attention_mode": "sdpa",
|
| 251 |
+
"custom_prompt": "Describe the facial expression.",
|
| 252 |
+
"image": [
|
| 253 |
+
"30",
|
| 254 |
+
0
|
| 255 |
+
],
|
| 256 |
+
"keep_model_loaded": false,
|
| 257 |
+
"max_tokens": 512,
|
| 258 |
+
"model_name": "Qwen3-VL-4B-Instruct",
|
| 259 |
+
"preset_prompt": "\ud83d\uddbc\ufe0f Tags",
|
| 260 |
+
"quantization": "None (FP16)",
|
| 261 |
+
"seed": 42
|
| 262 |
+
}
|
| 263 |
+
},
|
| 264 |
+
"32": {
|
| 265 |
+
"class_type": "StringConcatenate",
|
| 266 |
+
"inputs": {
|
| 267 |
+
"delimiter": "\n\n",
|
| 268 |
+
"string_a": "Remove the head (face and hair) from Picture 1 and replace it with the head (face and hair) from Picture 2. Keep the lighting, environment, and background from Picture 1. Relight the new head to match the scene. Keep the eye direction, head rotation, and facial expression from Picture 1.",
|
| 269 |
+
"string_b": [
|
| 270 |
+
"31",
|
| 271 |
+
0
|
| 272 |
+
]
|
| 273 |
+
}
|
| 274 |
+
},
|
| 275 |
+
"4": {
|
| 276 |
+
"class_type": "ResizeImageMaskNode",
|
| 277 |
+
"inputs": {
|
| 278 |
+
"input": [
|
| 279 |
+
"1",
|
| 280 |
+
0
|
| 281 |
+
],
|
| 282 |
+
"resize_type": "scale total pixels",
|
| 283 |
+
"resize_type.megapixels": 1.0,
|
| 284 |
+
"scale_method": "lanczos"
|
| 285 |
+
}
|
| 286 |
+
},
|
| 287 |
+
"5": {
|
| 288 |
+
"class_type": "ResizeImageMaskNode",
|
| 289 |
+
"inputs": {
|
| 290 |
+
"input": [
|
| 291 |
+
"3",
|
| 292 |
+
0
|
| 293 |
+
],
|
| 294 |
+
"resize_type": "scale total pixels",
|
| 295 |
+
"resize_type.megapixels": 1.0,
|
| 296 |
+
"scale_method": "lanczos"
|
| 297 |
+
}
|
| 298 |
+
},
|
| 299 |
+
"6": {
|
| 300 |
+
"class_type": "GetImageSize",
|
| 301 |
+
"inputs": {
|
| 302 |
+
"image": [
|
| 303 |
+
"4",
|
| 304 |
+
0
|
| 305 |
+
]
|
| 306 |
+
}
|
| 307 |
+
},
|
| 308 |
+
"7": {
|
| 309 |
+
"class_type": "EmptyFlux2LatentImage",
|
| 310 |
+
"inputs": {
|
| 311 |
+
"batch_size": 1,
|
| 312 |
+
"height": [
|
| 313 |
+
"6",
|
| 314 |
+
1
|
| 315 |
+
],
|
| 316 |
+
"width": [
|
| 317 |
+
"6",
|
| 318 |
+
0
|
| 319 |
+
]
|
| 320 |
+
}
|
| 321 |
+
},
|
| 322 |
+
"8": {
|
| 323 |
+
"class_type": "CLIPLoader",
|
| 324 |
+
"inputs": {
|
| 325 |
+
"clip_name": "qwen_3_8b.safetensors",
|
| 326 |
+
"device": "default",
|
| 327 |
+
"type": "flux2"
|
| 328 |
+
}
|
| 329 |
+
},
|
| 330 |
+
"9": {
|
| 331 |
+
"class_type": "UNETLoader",
|
| 332 |
+
"inputs": {
|
| 333 |
+
"unet_name": "flux-2-klein-9b.safetensors",
|
| 334 |
+
"weight_dtype": "default"
|
| 335 |
+
}
|
| 336 |
+
}
|
| 337 |
+
}
|
serverless/endpoints/qwen-edit-nsfw/endpoint.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "qwen-edit-nsfw",
|
| 3 |
+
"description": "Qwen-Rapid-AIO-NSFW v11.4 single-image edit (qwen-edit-nsfw workflow)",
|
| 4 |
+
"default_workflow": "qwen-edit-nsfw",
|
| 5 |
+
"hf_repo": "aleph65/ComfyUI",
|
| 6 |
+
"models": [
|
| 7 |
+
"models/checkpoints/Qwen-Rapid-AIO-NSFW-v11.4.safetensors",
|
| 8 |
+
"models/loras/qwen_edit/next-scene_lora-v2-3000.safetensors"
|
| 9 |
+
],
|
| 10 |
+
"custom_nodes": [
|
| 11 |
+
"rgthree-comfy",
|
| 12 |
+
"ComfyUI-KJNodes",
|
| 13 |
+
"ComfyUI-Image-Saver",
|
| 14 |
+
"RES4LYF"
|
| 15 |
+
],
|
| 16 |
+
"extra_layers": ["pipdeps-delta.tgz"],
|
| 17 |
+
"comfy_args": "--highvram"
|
| 18 |
+
}
|
serverless/endpoints/qwen-edit-nsfw/params/qwen-edit-nsfw.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"params": {
|
| 3 |
+
"prompt": {"node": "119", "input": "prompt"},
|
| 4 |
+
"negative_prompt": {"node": "77", "input": "prompt"},
|
| 5 |
+
"image": {"node": "78", "input": "image", "type": "image"},
|
| 6 |
+
"steps": {"node": "115", "input": "value"},
|
| 7 |
+
"seed": {"node": "117", "input": "value"},
|
| 8 |
+
"megapixels": {"node": "93", "input": "megapixels"},
|
| 9 |
+
"cfg": {"node": "121", "input": "cfg"},
|
| 10 |
+
"eta": {"node": "121", "input": "eta"},
|
| 11 |
+
"sampler": {"node": "121", "input": "sampler_name"},
|
| 12 |
+
"scheduler": {"node": "121", "input": "scheduler"},
|
| 13 |
+
"denoise": {"node": "121", "input": "denoise"},
|
| 14 |
+
"lora_next_scene": {"node": "103", "input": "lora_1", "subkey": "on"},
|
| 15 |
+
"lora_next_scene_strength": {"node": "103", "input": "lora_1", "subkey": "strength"}
|
| 16 |
+
},
|
| 17 |
+
"defaults": {},
|
| 18 |
+
"image_slots": ["image"],
|
| 19 |
+
"when_image_absent": {}
|
| 20 |
+
}
|
serverless/endpoints/qwen-edit-nsfw/workflows/qwen-edit-nsfw.json
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"102": {
|
| 3 |
+
"class_type": "Image Saver Simple",
|
| 4 |
+
"inputs": {
|
| 5 |
+
"counter": 0,
|
| 6 |
+
"embed_workflow": true,
|
| 7 |
+
"extension": "webp",
|
| 8 |
+
"filename": "%time_%basemodelname_%seed",
|
| 9 |
+
"images": [
|
| 10 |
+
"8",
|
| 11 |
+
0
|
| 12 |
+
],
|
| 13 |
+
"lossless_webp": false,
|
| 14 |
+
"metadata": [
|
| 15 |
+
"106",
|
| 16 |
+
0
|
| 17 |
+
],
|
| 18 |
+
"optimize_png": false,
|
| 19 |
+
"path": "qwen_edit/%date",
|
| 20 |
+
"quality_jpeg_or_webp": 100,
|
| 21 |
+
"save_workflow_as_json": false,
|
| 22 |
+
"show_preview": true,
|
| 23 |
+
"time_format": "%Y-%m-%d-%H%M%S"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"103": {
|
| 27 |
+
"class_type": "Power Lora Loader (rgthree)",
|
| 28 |
+
"inputs": {
|
| 29 |
+
"clip": [
|
| 30 |
+
"118",
|
| 31 |
+
1
|
| 32 |
+
],
|
| 33 |
+
"lora_1": {
|
| 34 |
+
"lora": "qwen_edit\\next-scene_lora-v2-3000.safetensors",
|
| 35 |
+
"on": false,
|
| 36 |
+
"strength": 1,
|
| 37 |
+
"strengthTwo": null
|
| 38 |
+
},
|
| 39 |
+
"model": [
|
| 40 |
+
"118",
|
| 41 |
+
0
|
| 42 |
+
]
|
| 43 |
+
}
|
| 44 |
+
},
|
| 45 |
+
"106": {
|
| 46 |
+
"class_type": "Image Saver Metadata",
|
| 47 |
+
"inputs": {
|
| 48 |
+
"additional_hashes": "",
|
| 49 |
+
"cfg": 1,
|
| 50 |
+
"clip_skip": 0,
|
| 51 |
+
"custom": "",
|
| 52 |
+
"denoise": 1,
|
| 53 |
+
"download_civitai_data": true,
|
| 54 |
+
"easy_remix": true,
|
| 55 |
+
"height": 512,
|
| 56 |
+
"modelname": "Qwen-Rapid-AIO-NSFW-v11.4.safetensors",
|
| 57 |
+
"negative": "unknown",
|
| 58 |
+
"positive": "unknown",
|
| 59 |
+
"sampler_name": "euler",
|
| 60 |
+
"scheduler_name": "beta57",
|
| 61 |
+
"seed_value": [
|
| 62 |
+
"117",
|
| 63 |
+
0
|
| 64 |
+
],
|
| 65 |
+
"steps": [
|
| 66 |
+
"115",
|
| 67 |
+
0
|
| 68 |
+
],
|
| 69 |
+
"width": 512
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
"115": {
|
| 73 |
+
"_meta": {
|
| 74 |
+
"title": "Steps"
|
| 75 |
+
},
|
| 76 |
+
"class_type": "INTConstant",
|
| 77 |
+
"inputs": {
|
| 78 |
+
"value": 8
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"117": {
|
| 82 |
+
"_meta": {
|
| 83 |
+
"title": "Seed"
|
| 84 |
+
},
|
| 85 |
+
"class_type": "PrimitiveInt",
|
| 86 |
+
"inputs": {
|
| 87 |
+
"value": 0
|
| 88 |
+
}
|
| 89 |
+
},
|
| 90 |
+
"118": {
|
| 91 |
+
"class_type": "CheckpointLoaderSimple",
|
| 92 |
+
"inputs": {
|
| 93 |
+
"ckpt_name": "Qwen-Rapid-AIO-NSFW-v11.4.safetensors"
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
"119": {
|
| 97 |
+
"class_type": "TextEncodeQwenImageEditPlus",
|
| 98 |
+
"inputs": {
|
| 99 |
+
"clip": [
|
| 100 |
+
"103",
|
| 101 |
+
1
|
| 102 |
+
],
|
| 103 |
+
"image1": [
|
| 104 |
+
"78",
|
| 105 |
+
0
|
| 106 |
+
],
|
| 107 |
+
"prompt": "replace the outfit of the subject with a sexy white crop top tank top with a v neck and jean shorts, don't change anything else",
|
| 108 |
+
"vae": [
|
| 109 |
+
"118",
|
| 110 |
+
2
|
| 111 |
+
]
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
"121": {
|
| 115 |
+
"class_type": "ClownsharKSampler_Beta",
|
| 116 |
+
"inputs": {
|
| 117 |
+
"bongmath": true,
|
| 118 |
+
"cfg": 1,
|
| 119 |
+
"denoise": 1,
|
| 120 |
+
"eta": 0.5,
|
| 121 |
+
"latent_image": [
|
| 122 |
+
"88",
|
| 123 |
+
0
|
| 124 |
+
],
|
| 125 |
+
"model": [
|
| 126 |
+
"75",
|
| 127 |
+
0
|
| 128 |
+
],
|
| 129 |
+
"negative": [
|
| 130 |
+
"77",
|
| 131 |
+
0
|
| 132 |
+
],
|
| 133 |
+
"positive": [
|
| 134 |
+
"119",
|
| 135 |
+
0
|
| 136 |
+
],
|
| 137 |
+
"sampler_mode": "standard",
|
| 138 |
+
"sampler_name": "linear/euler",
|
| 139 |
+
"scheduler": "simple",
|
| 140 |
+
"seed": [
|
| 141 |
+
"117",
|
| 142 |
+
0
|
| 143 |
+
],
|
| 144 |
+
"steps": [
|
| 145 |
+
"115",
|
| 146 |
+
0
|
| 147 |
+
],
|
| 148 |
+
"steps_to_run": -1
|
| 149 |
+
}
|
| 150 |
+
},
|
| 151 |
+
"66": {
|
| 152 |
+
"class_type": "ModelSamplingAuraFlow",
|
| 153 |
+
"inputs": {
|
| 154 |
+
"model": [
|
| 155 |
+
"103",
|
| 156 |
+
0
|
| 157 |
+
],
|
| 158 |
+
"shift": 3
|
| 159 |
+
}
|
| 160 |
+
},
|
| 161 |
+
"75": {
|
| 162 |
+
"class_type": "CFGNorm",
|
| 163 |
+
"inputs": {
|
| 164 |
+
"model": [
|
| 165 |
+
"66",
|
| 166 |
+
0
|
| 167 |
+
],
|
| 168 |
+
"pre_cfg": false,
|
| 169 |
+
"strength": 1
|
| 170 |
+
}
|
| 171 |
+
},
|
| 172 |
+
"77": {
|
| 173 |
+
"class_type": "TextEncodeQwenImageEdit",
|
| 174 |
+
"inputs": {
|
| 175 |
+
"clip": [
|
| 176 |
+
"103",
|
| 177 |
+
1
|
| 178 |
+
],
|
| 179 |
+
"image": [
|
| 180 |
+
"78",
|
| 181 |
+
0
|
| 182 |
+
],
|
| 183 |
+
"prompt": "ugly, blurry, distorted, artifacts, bad, wrong, low quality, anime, digital art, semirealistic, cartoon, manga, drawing, fake, unreal, ",
|
| 184 |
+
"vae": [
|
| 185 |
+
"118",
|
| 186 |
+
2
|
| 187 |
+
]
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
"78": {
|
| 191 |
+
"class_type": "LoadImage",
|
| 192 |
+
"inputs": {
|
| 193 |
+
"image": "b.jpg"
|
| 194 |
+
}
|
| 195 |
+
},
|
| 196 |
+
"8": {
|
| 197 |
+
"class_type": "VAEDecode",
|
| 198 |
+
"inputs": {
|
| 199 |
+
"samples": [
|
| 200 |
+
"121",
|
| 201 |
+
1
|
| 202 |
+
],
|
| 203 |
+
"vae": [
|
| 204 |
+
"118",
|
| 205 |
+
2
|
| 206 |
+
]
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"88": {
|
| 210 |
+
"class_type": "VAEEncode",
|
| 211 |
+
"inputs": {
|
| 212 |
+
"pixels": [
|
| 213 |
+
"93",
|
| 214 |
+
0
|
| 215 |
+
],
|
| 216 |
+
"vae": [
|
| 217 |
+
"118",
|
| 218 |
+
2
|
| 219 |
+
]
|
| 220 |
+
}
|
| 221 |
+
},
|
| 222 |
+
"93": {
|
| 223 |
+
"class_type": "ImageScaleToTotalPixels",
|
| 224 |
+
"inputs": {
|
| 225 |
+
"image": [
|
| 226 |
+
"78",
|
| 227 |
+
0
|
| 228 |
+
],
|
| 229 |
+
"megapixels": 1,
|
| 230 |
+
"resolution_steps": 1,
|
| 231 |
+
"upscale_method": "lanczos"
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
}
|
serverless/handler.py
CHANGED
|
@@ -164,6 +164,14 @@ def build_graph(job_input, job_id, image_names):
|
|
| 164 |
with open(PARAM_MAPS[wf_name]) as fh:
|
| 165 |
pmap = json.load(fh)
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
params = dict(pmap.get("defaults", {}))
|
| 168 |
params.update(job_input.get("params") or {})
|
| 169 |
|
|
@@ -205,14 +213,21 @@ def build_graph(job_input, job_id, image_names):
|
|
| 205 |
raise ValueError(f"bad set target {target!r} (want NODE.INPUT or NODE.INPUT.KEY)")
|
| 206 |
_apply_param(graph, spec, value)
|
| 207 |
|
| 208 |
-
# seed: explicit param wins; otherwise randomize every seed-ish input
|
|
|
|
|
|
|
|
|
|
| 209 |
seed = params.get("seed")
|
| 210 |
if seed is None:
|
| 211 |
-
|
|
|
|
|
|
|
| 212 |
for node in graph.values():
|
| 213 |
for name in SEED_INPUTS:
|
| 214 |
if isinstance(node.get("inputs", {}).get(name), int):
|
| 215 |
node["inputs"][name] = seed
|
|
|
|
|
|
|
| 216 |
|
| 217 |
# route outputs into a per-job folder for clean collection
|
| 218 |
for node in graph.values():
|
|
@@ -266,6 +281,13 @@ def collect_outputs(entry):
|
|
| 266 |
with open(path, "rb") as fh:
|
| 267 |
out.append({"filename": item["filename"], "type": "base64",
|
| 268 |
"data": base64.b64encode(fh.read()).decode()})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
return out
|
| 270 |
|
| 271 |
|
|
|
|
| 164 |
with open(PARAM_MAPS[wf_name]) as fh:
|
| 165 |
pmap = json.load(fh)
|
| 166 |
|
| 167 |
+
# any LoadImage referencing an uploaded image by bare name gets pointed at
|
| 168 |
+
# the per-job copy — this is what makes raw workflow_json passthrough work
|
| 169 |
+
# without the caller knowing about job folders
|
| 170 |
+
for node in graph.values():
|
| 171 |
+
img = node.get("inputs", {}).get("image")
|
| 172 |
+
if node.get("class_type") == "LoadImage" and img in image_names:
|
| 173 |
+
node["inputs"]["image"] = f"{job_id}/{img}"
|
| 174 |
+
|
| 175 |
params = dict(pmap.get("defaults", {}))
|
| 176 |
params.update(job_input.get("params") or {})
|
| 177 |
|
|
|
|
| 213 |
raise ValueError(f"bad set target {target!r} (want NODE.INPUT or NODE.INPUT.KEY)")
|
| 214 |
_apply_param(graph, spec, value)
|
| 215 |
|
| 216 |
+
# seed: explicit param wins; otherwise randomize every seed-ish input, and
|
| 217 |
+
# also push the value through the param map's "seed" target — some
|
| 218 |
+
# workflows feed their sampler from a plain PrimitiveInt whose input is
|
| 219 |
+
# named "value", which the name-based sweep can't see
|
| 220 |
seed = params.get("seed")
|
| 221 |
if seed is None:
|
| 222 |
+
# keep within [1, 2^31): some nodes (e.g. AILab_QwenVL) cap seed at
|
| 223 |
+
# uint32 and reject bigger values at validation
|
| 224 |
+
seed = random.randrange(1, 2**31)
|
| 225 |
for node in graph.values():
|
| 226 |
for name in SEED_INPUTS:
|
| 227 |
if isinstance(node.get("inputs", {}).get(name), int):
|
| 228 |
node["inputs"][name] = seed
|
| 229 |
+
if "seed" in spec_map:
|
| 230 |
+
_apply_param(graph, spec_map["seed"], seed)
|
| 231 |
|
| 232 |
# route outputs into a per-job folder for clean collection
|
| 233 |
for node in graph.values():
|
|
|
|
| 281 |
with open(path, "rb") as fh:
|
| 282 |
out.append({"filename": item["filename"], "type": "base64",
|
| 283 |
"data": base64.b64encode(fh.read()).decode()})
|
| 284 |
+
# savers that ignore filename_prefix (e.g. Image Saver)
|
| 285 |
+
# write outside the per-job folder; delete as we collect
|
| 286 |
+
# so a long-lived worker never fills its container disk
|
| 287 |
+
try:
|
| 288 |
+
os.remove(path)
|
| 289 |
+
except OSError:
|
| 290 |
+
pass
|
| 291 |
return out
|
| 292 |
|
| 293 |
|
serverless/tests/endpoint_test.py
CHANGED
|
@@ -33,7 +33,7 @@ def main():
|
|
| 33 |
ap.add_argument("--endpoint-id", required=True)
|
| 34 |
ap.add_argument("--prompt", default="replace the outfit of the subject in the first "
|
| 35 |
"image with the outfit in the second image, don't change anything else.")
|
| 36 |
-
ap.add_argument("--mode", default="
|
| 37 |
ap.add_argument("--seed", type=int)
|
| 38 |
ap.add_argument("--sync", action="store_true", help="use /runsync instead of /run+poll")
|
| 39 |
ap.add_argument("--timeout", type=float, default=900)
|
|
@@ -42,7 +42,9 @@ def main():
|
|
| 42 |
imgs = [{"name": os.path.basename(p),
|
| 43 |
"image": base64.b64encode(open(p, "rb").read()).decode()}
|
| 44 |
for p in args.images]
|
| 45 |
-
params = {"prompt": args.prompt
|
|
|
|
|
|
|
| 46 |
if args.seed is not None:
|
| 47 |
params["seed"] = args.seed
|
| 48 |
payload = {"input": {"images": imgs, "params": params}}
|
|
|
|
| 33 |
ap.add_argument("--endpoint-id", required=True)
|
| 34 |
ap.add_argument("--prompt", default="replace the outfit of the subject in the first "
|
| 35 |
"image with the outfit in the second image, don't change anything else.")
|
| 36 |
+
ap.add_argument("--mode", default=None, help="only for workflows that support it")
|
| 37 |
ap.add_argument("--seed", type=int)
|
| 38 |
ap.add_argument("--sync", action="store_true", help="use /runsync instead of /run+poll")
|
| 39 |
ap.add_argument("--timeout", type=float, default=900)
|
|
|
|
| 42 |
imgs = [{"name": os.path.basename(p),
|
| 43 |
"image": base64.b64encode(open(p, "rb").read()).decode()}
|
| 44 |
for p in args.images]
|
| 45 |
+
params = {"prompt": args.prompt}
|
| 46 |
+
if args.mode:
|
| 47 |
+
params["mode"] = args.mode
|
| 48 |
if args.seed is not None:
|
| 49 |
params["seed"] = args.seed
|
| 50 |
payload = {"input": {"images": imgs, "params": params}}
|
serverless/tests/local_test.py
CHANGED
|
@@ -22,7 +22,7 @@ def main():
|
|
| 22 |
ap.add_argument("images", nargs="+", help="input image file(s): main [ref]")
|
| 23 |
ap.add_argument("--prompt", default="replace the outfit of the subject in the first "
|
| 24 |
"image with the outfit in the second image, don't change anything else.")
|
| 25 |
-
ap.add_argument("--mode", default="
|
| 26 |
ap.add_argument("--seed", type=int)
|
| 27 |
ap.add_argument("--set", dest="sets", action="append", default=[],
|
| 28 |
metavar="NODE.INPUT=JSONVALUE")
|
|
@@ -32,7 +32,9 @@ def main():
|
|
| 32 |
for p in args.images:
|
| 33 |
imgs.append({"name": os.path.basename(p),
|
| 34 |
"image": base64.b64encode(open(p, "rb").read()).decode()})
|
| 35 |
-
params = {"prompt": args.prompt
|
|
|
|
|
|
|
| 36 |
if args.seed is not None:
|
| 37 |
params["seed"] = args.seed
|
| 38 |
overrides = {}
|
|
|
|
| 22 |
ap.add_argument("images", nargs="+", help="input image file(s): main [ref]")
|
| 23 |
ap.add_argument("--prompt", default="replace the outfit of the subject in the first "
|
| 24 |
"image with the outfit in the second image, don't change anything else.")
|
| 25 |
+
ap.add_argument("--mode", default=None, help="only for workflows that support it")
|
| 26 |
ap.add_argument("--seed", type=int)
|
| 27 |
ap.add_argument("--set", dest="sets", action="append", default=[],
|
| 28 |
metavar="NODE.INPUT=JSONVALUE")
|
|
|
|
| 32 |
for p in args.images:
|
| 33 |
imgs.append({"name": os.path.basename(p),
|
| 34 |
"image": base64.b64encode(open(p, "rb").read()).decode()})
|
| 35 |
+
params = {"prompt": args.prompt}
|
| 36 |
+
if args.mode:
|
| 37 |
+
params["mode"] = args.mode
|
| 38 |
if args.seed is not None:
|
| 39 |
params["seed"] = args.seed
|
| 40 |
overrides = {}
|