runtime
Not-For-All-Audiences
Commit ·
90c71dd
0
Parent(s):
Duplicate from jmpplbp/VideoKarma1-rebuild-20260927
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +35 -0
- APP_FILES.json +62 -0
- ATTRIBUTION.md +15 -0
- MODEL_SOURCES.json +112 -0
- README.md +12 -0
- RUNTIME_RELEASE.json +5 -0
- RUNTIME_SOURCES.json +64 -0
- SUPPORT_SOURCES.json +31 -0
- app-runtime.json +45 -0
- app.js +4 -0
- bernini_continuation.py +143 -0
- catalog_boot.py +76 -0
- catalog_runtime.py +575 -0
- chat_runner.py +32 -0
- generate.py +4 -0
- index.html +12 -0
- irodori_runner.py +104 -0
- models/diffusion_models/denoiser-01.safetensors +3 -0
- models/diffusion_models/denoiser-02.safetensors +3 -0
- models/diffusion_models/denoiser-03.safetensors +3 -0
- models/diffusion_models/denoiser-04.safetensors +3 -0
- models/loras/a-high.safetensors +3 -0
- models/loras/a-low.safetensors +3 -0
- models/loras/adapter-01.safetensors +3 -0
- models/loras/adapter-02.safetensors +3 -0
- models/loras/adapter-03.safetensors +3 -0
- models/loras/adapter-04.safetensors +3 -0
- models/loras/b-high.safetensors +3 -0
- models/loras/b-low.safetensors +3 -0
- models/loras/c-high.safetensors +3 -0
- models/loras/c-low.safetensors +3 -0
- models/loras/d-high.safetensors +3 -0
- models/loras/d-low.safetensors +3 -0
- models/loras/e-high.safetensors +3 -0
- models/loras/e-low.safetensors +3 -0
- models/loras/f-high.safetensors +3 -0
- models/loras/f-low.safetensors +3 -0
- models/loras/g-high.safetensors +3 -0
- models/loras/g-low.safetensors +3 -0
- models/loras/h-high.safetensors +3 -0
- models/loras/h-low.safetensors +3 -0
- models/loras/i-high.safetensors +3 -0
- models/loras/i-low.safetensors +3 -0
- models/loras/j-high.safetensors +3 -0
- models/loras/j-low.safetensors +3 -0
- models/text_encoders/encoder-01.safetensors +3 -0
- models/vae/decoder-01.safetensors +3 -0
- style.css +67 -0
- upstream/Comfy-Org--Wan_2.1_ComfyUI_repackaged/617a7633e636506f850e043bc4605f290a466a8e/README.md +92 -0
- upstream/Comfy-Org--Wan_2.2_ComfyUI_Repackaged/c4f60d30c55a624e35427060fdd217579a6c1d77/README.md +98 -0
.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
APP_FILES.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"path": "generate.py",
|
| 4 |
+
"size": 152,
|
| 5 |
+
"sha256": "77db351fc4095f799d6c6c27fdfa771da6ede7aac13bd86fe14ebd883e918047"
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"path": "catalog_runtime.py",
|
| 9 |
+
"size": 38260,
|
| 10 |
+
"sha256": "da328d2640faf33d1ddd1c87b39a64558ced1009c15a04018912bcac799d9a54"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"path": "catalog_boot.py",
|
| 14 |
+
"size": 4079,
|
| 15 |
+
"sha256": "3b67d5c491382bf7b133f8d1a2ddb11aaf13fe8805706c96bd634645343479e6"
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"path": "bernini_continuation.py",
|
| 19 |
+
"size": 7275,
|
| 20 |
+
"sha256": "3b6b4d8df3a27b7f5a89b0b817fee296eb0a64b3b027c04b474c463b32d8c2ef"
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"path": "irodori_runner.py",
|
| 24 |
+
"size": 5262,
|
| 25 |
+
"sha256": "5d2df4f995dce50c407cff7430a238e6b0d37e7cdef4a3ab59594087972bb92d"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"path": "chat_runner.py",
|
| 29 |
+
"size": 2131,
|
| 30 |
+
"sha256": "b79b9d8e9733a29a8fcdd5039020438f0977cd25ea58b5edcaa8fddc70585bd1"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"path": "RUNTIME_SOURCES.json",
|
| 34 |
+
"size": 3164,
|
| 35 |
+
"sha256": "fc8bdaa86846af47f6c409d5fcdd5374d48b5cce666de276a84d50f323da884c"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"path": "index.html",
|
| 39 |
+
"size": 6969,
|
| 40 |
+
"sha256": "6ed066ae5f376db00f2ba92b7d7470ce8747f076e234f4dad5f1b126b4246961"
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"path": "app.js",
|
| 44 |
+
"size": 72563,
|
| 45 |
+
"sha256": "5bd2dec0f5924f9f6aa9dd9cd58edb6878dccbf4162319775f7792a89ffc979e"
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"path": "style.css",
|
| 49 |
+
"size": 14263,
|
| 50 |
+
"sha256": "c702b68b147a1ddeafbc594927013033f385a03c629609e64ab9594b9e48a6ea"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"path": "app-runtime.json",
|
| 54 |
+
"size": 1509,
|
| 55 |
+
"sha256": "b21755159aae220a478ae5f973afe77abb069911e8e9bb8578b79a816390a6d3"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"path": "README.md",
|
| 59 |
+
"size": 2014,
|
| 60 |
+
"sha256": "a3b26856b061ec10fff08145a8f24d168f185bc823e23aa3e2d16585ecc65d0c"
|
| 61 |
+
}
|
| 62 |
+
]
|
ATTRIBUTION.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Licenses and attribution
|
| 2 |
+
|
| 3 |
+
- [SquishedSquirrel/Wan2.2EV](https://huggingface.co/SquishedSquirrel/Wan2.2EV/tree/46c390c08b58861a20effc0425d0284de2d665f9): apache-2.0
|
| 4 |
+
- [SquishedSquirrel/Wan2.2EV](https://huggingface.co/SquishedSquirrel/Wan2.2EV/tree/46c390c08b58861a20effc0425d0284de2d665f9): apache-2.0
|
| 5 |
+
- [Comfy-Org/Wan_2.2_ComfyUI_Repackaged](https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/c4f60d30c55a624e35427060fdd217579a6c1d77): apache-2.0
|
| 6 |
+
- [Comfy-Org/Wan_2.2_ComfyUI_Repackaged](https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/c4f60d30c55a624e35427060fdd217579a6c1d77): apache-2.0
|
| 7 |
+
- [Comfy-Org/Wan_2.2_ComfyUI_Repackaged](https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/c4f60d30c55a624e35427060fdd217579a6c1d77): apache-2.0
|
| 8 |
+
- [Comfy-Org/Wan_2.2_ComfyUI_Repackaged](https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/c4f60d30c55a624e35427060fdd217579a6c1d77): apache-2.0
|
| 9 |
+
- [Comfy-Org/Wan_2.2_ComfyUI_Repackaged](https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/c4f60d30c55a624e35427060fdd217579a6c1d77): apache-2.0
|
| 10 |
+
- [Comfy-Org/Wan_2.2_ComfyUI_Repackaged](https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/c4f60d30c55a624e35427060fdd217579a6c1d77): apache-2.0
|
| 11 |
+
- [Comfy-Org/Wan_2.1_ComfyUI_repackaged](https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/tree/617a7633e636506f850e043bc4605f290a466a8e): apache-2.0
|
| 12 |
+
- [Comfy-Org/Wan_2.2_ComfyUI_Repackaged](https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/tree/c4f60d30c55a624e35427060fdd217579a6c1d77): apache-2.0
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
Original source revisions and checksums are recorded in MODEL_SOURCES.json where present. Runtime component notices are in RUNTIME_SOURCES.json and bundled LICENSE files.
|
MODEL_SOURCES.json
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"slot": "wan.high",
|
| 4 |
+
"source_repo": "SquishedSquirrel/Wan2.2EV",
|
| 5 |
+
"source_revision": "46c390c08b58861a20effc0425d0284de2d665f9",
|
| 6 |
+
"source_path": "Wan2.2_ev_high.safetensors",
|
| 7 |
+
"target_path": "models/diffusion_models/Wan2.2_ev_high.safetensors",
|
| 8 |
+
"size": 14293828968,
|
| 9 |
+
"sha256": "f6a29c51486a2ec073d155ce85e90cc3bf9ee6c726aa87bf3835c0aa63b05de4",
|
| 10 |
+
"license": "apache-2.0",
|
| 11 |
+
"required_when": "Wan i2v; high/low pair and corresponding default 4-step acceleration LoRA."
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"slot": "wan.low",
|
| 15 |
+
"source_repo": "SquishedSquirrel/Wan2.2EV",
|
| 16 |
+
"source_revision": "46c390c08b58861a20effc0425d0284de2d665f9",
|
| 17 |
+
"source_path": "Wan2.2_ev_low.safetensors",
|
| 18 |
+
"target_path": "models/diffusion_models/Wan2.2_ev_low.safetensors",
|
| 19 |
+
"size": 14293828968,
|
| 20 |
+
"sha256": "82a642cde3b39eb0c45115d6d89c39d2cbae9ec3309bc9fbd8aab8e5e72b9c8c",
|
| 21 |
+
"license": "apache-2.0",
|
| 22 |
+
"required_when": "Wan i2v; high/low pair and corresponding default 4-step acceleration LoRA."
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"slot": "wan.high-lora",
|
| 26 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 27 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 28 |
+
"source_path": "split_files/loras/wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors",
|
| 29 |
+
"target_path": "models/loras/wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors",
|
| 30 |
+
"size": 1226977424,
|
| 31 |
+
"sha256": "d176c808d6fc461999b68e321efcb7501b20b8c3797523ed0df14f7d1deff11e",
|
| 32 |
+
"license": "apache-2.0",
|
| 33 |
+
"required_when": "Wan i2v; high/low pair and corresponding default 4-step acceleration LoRA."
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"slot": "wan.low-lora",
|
| 37 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 38 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 39 |
+
"source_path": "split_files/loras/wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors",
|
| 40 |
+
"target_path": "models/loras/wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors",
|
| 41 |
+
"size": 1226977424,
|
| 42 |
+
"sha256": "024f21de095bc8fad9809ded3e9e49a2e170dcf27075da8145ba7d60d8aab7f9",
|
| 43 |
+
"license": "apache-2.0",
|
| 44 |
+
"required_when": "Wan i2v; high/low pair and corresponding default 4-step acceleration LoRA."
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"slot": "wan.t2v-high",
|
| 48 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 49 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 50 |
+
"source_path": "split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
| 51 |
+
"target_path": "models/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
| 52 |
+
"size": 14293923632,
|
| 53 |
+
"sha256": "cad711ae211c8b23455ec68cd6a190a33a3d874234a77eb57266d73f8f0e6c9f",
|
| 54 |
+
"license": "apache-2.0",
|
| 55 |
+
"required_when": "Wan t2v only; not needed for i2v."
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"slot": "wan.t2v-low",
|
| 59 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 60 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 61 |
+
"source_path": "split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
| 62 |
+
"target_path": "models/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
| 63 |
+
"size": 14293923632,
|
| 64 |
+
"sha256": "e71b96d7c82e638694c5e7fb98fac4bfb0e4ddc5fbbb4b1df40da8f0f1278a97",
|
| 65 |
+
"license": "apache-2.0",
|
| 66 |
+
"required_when": "Wan t2v only; not needed for i2v."
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"slot": "wan.t2v-high-lora",
|
| 70 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 71 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 72 |
+
"source_path": "split_files/loras/wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise.safetensors",
|
| 73 |
+
"target_path": "models/loras/wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise.safetensors",
|
| 74 |
+
"size": 1226977424,
|
| 75 |
+
"sha256": "698321cb86bd30c4af06c9b84e656a1048c8cb54e06d50694536fb5de37fde41",
|
| 76 |
+
"license": "apache-2.0",
|
| 77 |
+
"required_when": "Wan t2v only; not needed for i2v."
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"slot": "wan.t2v-low-lora",
|
| 81 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 82 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 83 |
+
"source_path": "split_files/loras/wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise.safetensors",
|
| 84 |
+
"target_path": "models/loras/wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise.safetensors",
|
| 85 |
+
"size": 1226977424,
|
| 86 |
+
"sha256": "ec95216e614b3c132c11bfb387b11feedf62163150ccc9068bca8a189771e75a",
|
| 87 |
+
"license": "apache-2.0",
|
| 88 |
+
"required_when": "Wan t2v only; not needed for i2v."
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"slot": "wan.text-encoder",
|
| 92 |
+
"source_repo": "Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
| 93 |
+
"source_revision": "617a7633e636506f850e043bc4605f290a466a8e",
|
| 94 |
+
"source_path": "split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
| 95 |
+
"target_path": "models/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
| 96 |
+
"size": 6735906897,
|
| 97 |
+
"sha256": "c3355d30191f1f066b26d93fba017ae9809dce6c627dda5f6a66eaa651204f68",
|
| 98 |
+
"license": "apache-2.0",
|
| 99 |
+
"required_when": "All i2v, t2v modes using this default slot."
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"slot": "wan.vae",
|
| 103 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 104 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 105 |
+
"source_path": "split_files/vae/wan_2.1_vae.safetensors",
|
| 106 |
+
"target_path": "models/vae/wan_2.1_vae.safetensors",
|
| 107 |
+
"size": 253815318,
|
| 108 |
+
"sha256": "2fc39d31359a4b0a64f55876d8ff7fa8d780956ae2cb13463b0223e15148976b",
|
| 109 |
+
"license": "apache-2.0",
|
| 110 |
+
"required_when": "All i2v, t2v modes using this default slot."
|
| 111 |
+
}
|
| 112 |
+
]
|
README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
tags:
|
| 3 |
+
- runtime
|
| 4 |
+
---
|
| 5 |
+
# Video Karma
|
| 6 |
+
|
| 7 |
+
画像・テキストから動画。
|
| 8 |
+
|
| 9 |
+
Runtime assets use neutral filenames. This is an application package; underlying components retain their original licenses and authorship.
|
| 10 |
+
|
| 11 |
+
[Licenses and attribution](ATTRIBUTION.md)
|
| 12 |
+
|
RUNTIME_RELEASE.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"created_at": "2026-09-25T08:07:46.715516+00:00",
|
| 3 |
+
"base_revision": "895a58aeafc117510eca41e4484ea75450e74a40",
|
| 4 |
+
"purpose": "Fresh deployment of Seisui release using verified WAN endpoint configuration"
|
| 5 |
+
}
|
RUNTIME_SOURCES.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base_image_digest": "sha256:38e06c231c5dd608058db20fb3d8d6755ca4e23a6cced6fc5211a22dbfc575c2",
|
| 3 |
+
"custom_nodes": {
|
| 4 |
+
"hf_bernini": {
|
| 5 |
+
"source": "custom_nodes/hf_bernini/__init__.py",
|
| 6 |
+
"purpose": "Bundled color and first-frame blend nodes"
|
| 7 |
+
},
|
| 8 |
+
"hf_ltx_extensions": {
|
| 9 |
+
"source": "https://github.com/TenStrip/10S-Comfy-nodes",
|
| 10 |
+
"local_source": "msr_v4_inspect/custom_nodes/10S-Comfy-nodes",
|
| 11 |
+
"version": "1.9.2",
|
| 12 |
+
"license": "MIT as declared by upstream pyproject.toml",
|
| 13 |
+
"selection": [
|
| 14 |
+
"stg_guider.py",
|
| 15 |
+
"lora_stack_av.py",
|
| 16 |
+
"ltx_reference_enable.py",
|
| 17 |
+
"ltx_reference_conditioning.py"
|
| 18 |
+
]
|
| 19 |
+
},
|
| 20 |
+
"hf_mx": {
|
| 21 |
+
"source": "https://github.com/Smirnov75/ComfyUI-mxToolkit",
|
| 22 |
+
"version": "0.9.92",
|
| 23 |
+
"license": "GPL-3.0; original LICENSE included",
|
| 24 |
+
"selection": [
|
| 25 |
+
"mxtoolkit.py"
|
| 26 |
+
],
|
| 27 |
+
"export": "mxSlider"
|
| 28 |
+
},
|
| 29 |
+
"hf_failsafe_translate": {
|
| 30 |
+
"source": "https://github.com/ah-kun/ComfyUI-FailSafe-Translate-Node",
|
| 31 |
+
"revision": "b0b901a926f561176776c29ac8bfe0b4d3acaf03"
|
| 32 |
+
},
|
| 33 |
+
"hf_nvidia_rtx": {
|
| 34 |
+
"source": "https://github.com/Comfy-Org/Nvidia_RTX_Nodes_ComfyUI",
|
| 35 |
+
"revision": "892515e3eb9a4920a131a502a047e47adca9eb0d"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"runtime_wheels": [
|
| 39 |
+
{
|
| 40 |
+
"filename": "deep_translator-1.11.4-py3-none-any.whl",
|
| 41 |
+
"size": 42285,
|
| 42 |
+
"sha256": "d635df037e23fa35d12fd42dab72a0b55c9dd19e6292009ee7207e3f30b9e60a"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"filename": "nvidia_vfx-0.1.0.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",
|
| 46 |
+
"size": 597321055,
|
| 47 |
+
"sha256": "e51d9e6faa68466e45b83be7928321af4b0c561c7c5536a8cb2b7e6aba25f905"
|
| 48 |
+
}
|
| 49 |
+
],
|
| 50 |
+
"code_sha256": {
|
| 51 |
+
"custom_nodes/hf_bernini/__init__.py": "645d6b38a275782e9cf770d1f1978ae261bc46317ed562171686440a858f9c82",
|
| 52 |
+
"custom_nodes/hf_failsafe_translate/failsafe_translate_node.py": "b2aa0e3dde7d8e1ecdf13caedadbdffafa8381bc4d543b4937548198ffc75a20",
|
| 53 |
+
"custom_nodes/hf_failsafe_translate/test_node.py": "a2ac4002ce078a6495aa247527703f0c76f616da6ca5275d5be6b86fdfebd6d6",
|
| 54 |
+
"custom_nodes/hf_failsafe_translate/__init__.py": "9d216c7f27fdcd2855eef2161afcb24731321fa020f4bed60c9536c3bbdfecf7",
|
| 55 |
+
"custom_nodes/hf_ltx_extensions/lora_stack_av.py": "401e93c32665b6057e2318a7920a4cea0e4bad381a254f8b4483867fcede965b",
|
| 56 |
+
"custom_nodes/hf_ltx_extensions/ltx_reference_conditioning.py": "81e5c61f6854da1857ff28ff884fd1d1714a587e138a6abac95fae583e0e8143",
|
| 57 |
+
"custom_nodes/hf_ltx_extensions/ltx_reference_enable.py": "ce40f0abe51fae12ed21800a777141b1550f81f3de4c094d4d7d6205204e27f1",
|
| 58 |
+
"custom_nodes/hf_ltx_extensions/stg_guider.py": "475ff841c3d6e139e6dc5483333b549a8f3b1578b53f157fb4c8e9bb340342ff",
|
| 59 |
+
"custom_nodes/hf_ltx_extensions/__init__.py": "1fa32bdde6d5a9704688a9e0c7838cdcbe2ea7d259d6a72a55c6e98468d7b620",
|
| 60 |
+
"custom_nodes/hf_mx/mxtoolkit.py": "4167ce23fed719f651618965e0d2af094b2b7c0b7efef77bf1168a5d55c2deb2",
|
| 61 |
+
"custom_nodes/hf_mx/__init__.py": "976b2b972fe79ee1464504a3509108f0e9f289d119d10d70b1327bd704e41f1d",
|
| 62 |
+
"custom_nodes/hf_nvidia_rtx/__init__.py": "185aa7648151904a1741237f54a186bd19647172d78cdfe4e598aef726314252"
|
| 63 |
+
}
|
| 64 |
+
}
|
SUPPORT_SOURCES.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"repo": "jmpplbp/VideoKarma1",
|
| 3 |
+
"files": [],
|
| 4 |
+
"upstream_documents": [
|
| 5 |
+
{
|
| 6 |
+
"source_repo": "Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
| 7 |
+
"source_revision": "617a7633e636506f850e043bc4605f290a466a8e",
|
| 8 |
+
"source_path": "README.md",
|
| 9 |
+
"target_path": "upstream/Comfy-Org--Wan_2.1_ComfyUI_repackaged/617a7633e636506f850e043bc4605f290a466a8e/README.md",
|
| 10 |
+
"sha256": "56072c73cfd706f1160a400aac8820ab95ff3976e4ded03ffc3a5481fcb214bc",
|
| 11 |
+
"size": 3724
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"source_repo": "Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
| 15 |
+
"source_revision": "c4f60d30c55a624e35427060fdd217579a6c1d77",
|
| 16 |
+
"source_path": "README.md",
|
| 17 |
+
"target_path": "upstream/Comfy-Org--Wan_2.2_ComfyUI_Repackaged/c4f60d30c55a624e35427060fdd217579a6c1d77/README.md",
|
| 18 |
+
"sha256": "65f2895855c8906a2dc6e73489282a174aacf4fbc1780b7bf01f6d5df95d5c6f",
|
| 19 |
+
"size": 4546
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"source_repo": "SquishedSquirrel/Wan2.2EV",
|
| 23 |
+
"source_revision": "46c390c08b58861a20effc0425d0284de2d665f9",
|
| 24 |
+
"source_path": "README.md",
|
| 25 |
+
"target_path": "upstream/SquishedSquirrel--Wan2.2EV/46c390c08b58861a20effc0425d0284de2d665f9/README.md",
|
| 26 |
+
"sha256": "4e36442f45d0442052b8b92d8790265fe55e941b38d047f623b256d09a107a0d",
|
| 27 |
+
"size": 64
|
| 28 |
+
}
|
| 29 |
+
],
|
| 30 |
+
"existing_models_preserved": true
|
| 31 |
+
}
|
app-runtime.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"runtime": "hf-html-v1",
|
| 3 |
+
"backend": "python",
|
| 4 |
+
"engine": "comfyui",
|
| 5 |
+
"models": {
|
| 6 |
+
"diffusion_models": [
|
| 7 |
+
"models/diffusion_models/denoiser-01.safetensors",
|
| 8 |
+
"models/diffusion_models/denoiser-02.safetensors",
|
| 9 |
+
"models/diffusion_models/denoiser-03.safetensors",
|
| 10 |
+
"models/diffusion_models/denoiser-04.safetensors"
|
| 11 |
+
],
|
| 12 |
+
"loras": [
|
| 13 |
+
"models/loras/adapter-01.safetensors",
|
| 14 |
+
"models/loras/adapter-02.safetensors",
|
| 15 |
+
"models/loras/adapter-03.safetensors",
|
| 16 |
+
"models/loras/adapter-04.safetensors",
|
| 17 |
+
"models/loras/a-high.safetensors",
|
| 18 |
+
"models/loras/a-low.safetensors",
|
| 19 |
+
"models/loras/b-high.safetensors",
|
| 20 |
+
"models/loras/b-low.safetensors",
|
| 21 |
+
"models/loras/c-high.safetensors",
|
| 22 |
+
"models/loras/c-low.safetensors",
|
| 23 |
+
"models/loras/d-high.safetensors",
|
| 24 |
+
"models/loras/d-low.safetensors",
|
| 25 |
+
"models/loras/e-high.safetensors",
|
| 26 |
+
"models/loras/e-low.safetensors",
|
| 27 |
+
"models/loras/f-high.safetensors",
|
| 28 |
+
"models/loras/f-low.safetensors",
|
| 29 |
+
"models/loras/g-high.safetensors",
|
| 30 |
+
"models/loras/g-low.safetensors",
|
| 31 |
+
"models/loras/h-high.safetensors",
|
| 32 |
+
"models/loras/h-low.safetensors",
|
| 33 |
+
"models/loras/i-high.safetensors",
|
| 34 |
+
"models/loras/i-low.safetensors",
|
| 35 |
+
"models/loras/j-high.safetensors",
|
| 36 |
+
"models/loras/j-low.safetensors"
|
| 37 |
+
],
|
| 38 |
+
"text_encoders": [
|
| 39 |
+
"models/text_encoders/encoder-01.safetensors"
|
| 40 |
+
],
|
| 41 |
+
"vae": [
|
| 42 |
+
"models/vae/decoder-01.safetensors"
|
| 43 |
+
]
|
| 44 |
+
}
|
| 45 |
+
}
|
app.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(()=>{var Y={randomSeed:!0,seed:1};function we(t,e){let o=document.createElement("section");o.className="panel canvas-controls";let s=document.createElement("h3");s.textContent="\u753B\u50CF\u306E\u8A2D\u5B9A",o.append(s);let i=Object.values(t).filter(g=>g.class_type==="KSampler"),a=Object.values(t).find(g=>g.class_type==="EmptyLatentImage"),n=document.createElement("div");n.className="fields";function l(g,b,P=n){let G=document.createElement("label");G.className="field";let v=document.createElement("span");v.textContent=g,G.append(v,b),P.append(G)}function d(g,b,P,G,v,D,R,$=n){let q=document.createElement("input");return q.type="number",q.name=b,q.min=String(G),q.max=String(v),q.step=String(D),q.value=String(P),q.required=!0,q.oninput=()=>{q.validity.valid&&q.value!==""&&R(Number(q.value))},l(g,q,$),q}let r=[...new Set(i.map(g=>String(g.inputs.negative?.[0])))].map(g=>t[g]).filter(g=>g?.class_type==="CLIPTextEncode");if(r.length){let g=document.createElement("textarea");g.name="negative_prompt",g.rows=3,g.value=String(r[0].inputs.text),g.oninput=()=>r.forEach(b=>b.inputs.text=g.value),l("\u30CD\u30AC\u30C6\u30A3\u30D6\u30D7\u30ED\u30F3\u30D7\u30C8",g,o)}if(o.append(n),a){let g=document.createElement("select");g.name="canvas_size";for(let[v,D]of[["1024x1024","\u6B63\u65B9\u5F62 1024 \xD7 1024"],["768x1024","\u7E26\u9577 768 \xD7 1024"],["1024x768","\u6A2A\u9577 1024 \xD7 768"],["576x1024","\u7E26\u9577 576 \xD7 1024"],["1024x576","\u6A2A\u9577 1024 \xD7 576"],["custom","\u30AB\u30B9\u30BF\u30E0"]]){let R=document.createElement("option");R.value=v,R.textContent=D,g.append(R)}let b=a.inputs.width+"x"+a.inputs.height;g.value=Array.from(g.options).some(v=>v.value===b)?b:"custom",l("\u30B5\u30A4\u30BA",g);let P=d("\u5E45\uFF08px\uFF09","canvas_width",Number(a.inputs.width),256,1024,64,v=>{a.inputs.width=v,g.value="custom"}),G=d("\u9AD8\u3055\uFF08px\uFF09","canvas_height",Number(a.inputs.height),256,1024,64,v=>{a.inputs.height=v,g.value="custom"});g.onchange=()=>{if(g.value==="custom")return;let[v,D]=g.value.split("x").map(Number);P.value=String(v),G.value=String(D),a.inputs.width=v,a.inputs.height=D},d("\u751F\u6210\u679A\u6570","canvas_batch",Number(a.inputs.batch_size),1,4,1,v=>a.inputs.batch_size=v)}i.length&&(d("\u30B9\u30C6\u30C3\u30D7\u6570","canvas_steps",Number(i[0].inputs.steps),1,60,1,g=>i.forEach(b=>b.inputs.steps=g)),d("\u30D7\u30ED\u30F3\u30D7\u30C8\u306E\u5F37\u3055\uFF08CFG\uFF09","canvas_cfg",Number(i[0].inputs.cfg),1,20,.5,g=>i.forEach(b=>b.inputs.cfg=g)));let f=document.createElement("input");f.type="checkbox",f.name="canvas_random_seed",f.checked=Y.randomSeed,l("\u30B7\u30FC\u30C9\u3092\u6BCE\u56DE\u30E9\u30F3\u30C0\u30E0\u306B\u3059\u308B",f);let y=d("\u30B7\u30FC\u30C9","canvas_seed",Y.seed,0,Number.MAX_SAFE_INTEGER,1,g=>Y.seed=g);y.disabled=f.checked,f.onchange=()=>{Y.randomSeed=f.checked,y.disabled=f.checked};let L=document.createElement("details"),k=document.createElement("summary");k.textContent="\u8A73\u7D30\u8A2D\u5B9A",L.append(k);let N=document.createElement("div");N.className="fields",L.append(N),o.append(L);for(let[g,b]of[["sampler_name","\u30B5\u30F3\u30D7\u30E9\u30FC"],["scheduler","\u30B9\u30B1\u30B8\u30E5\u30FC\u30E9\u30FC"]]){if(!i.length)continue;let P=document.createElement("select");P.name="canvas_"+g;let G=e.KSampler?.input?.required?.[g]?.[0],v=[...new Set([String(i[0].inputs[g]),...Array.isArray(G)?G:[]])];for(let D of v){let R=document.createElement("option");R.value=String(D),R.textContent=String(D),P.append(R)}P.onchange=()=>i.forEach(D=>D.inputs[g]=P.value),l(b,P,N)}let w=Object.values(t).filter(g=>g.class_type==="CLIPSetLastLayer");return w.length&&d("CLIP\u30B9\u30AD\u30C3\u30D7","canvas_clip_skip",Math.abs(Number(w[0].inputs.stop_at_clip_layer)),1,12,1,g=>w.forEach(b=>b.inputs.stop_at_clip_layer=-g),N),o}function Fe(t){return Object.values(t).flatMap(e=>(e.class_type==="Krea2EditModelPatch"?["source_latent_b","source_image_b"]:e.class_type==="Krea2EditGroundedEncode"?["image_b"]:[]).filter(s=>Array.isArray(e.inputs[s])).map(s=>({node:e,key:s})))}function Ge(t){let e=new Set,o=new Set,s=Fe(t).map(({node:i,key:a})=>String(i.inputs[a][0]));for(;s.length;){let i=s.pop();if(o.has(i))continue;o.add(i);let a=t[i];if(a){if(a.class_type==="LoadImage"){e.add(i);continue}for(let n of Object.values(a.inputs))Array.isArray(n)&&n.length===2&&s.push(String(n[0]))}}return e}function qe(t){for(let{node:e,key:o}of Fe(t))delete e.inputs[o]}(()=>{let t=Symbol.for("app.seisuiPrompts.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-j")?.closest(".video-lora-control");if(!i||i.querySelector("[data-seisui-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.seisuiPrompts="",a.setAttribute("aria-label","Seisui \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["a woman, jumpcut, after the transition, she is nude sitting with her legs spread, she is pissing on the ground","a woman, jumpcut, after the transition, she is nude on all fours, a nude man is standing in front of her, he is holding his penis and pissing into her mouth","a woman, jumpcut, after the transition, she is nude lying on her back with her legs back, she is pissing on her own face, piss splashes against her face, soaking her skin and dripping down her body"]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=document.querySelector("#app-config");if(!t||JSON.parse(t.textContent).id!=="image-edit-prime")return;let e=window.appSdk,o=Symbol.for("app.imageEditPrimeBranding.v1");if(!e||e[o])return;e[o]=!0;let s=/flux/i,i=_=>typeof _=="string"&&s.test(_)?"Image Edit Prime\u306E\u51E6\u7406\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u5165\u529B\u3068\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002":_,a=_=>typeof _=="string"?_.replace(/flux(?:\d+(?:\.\d+)*)?/ig,"ImageEditPrime"):_,n=e.onStatus.bind(e);e.onStatus=_=>n(r=>_({...r,message:i(r.message)}));let l=e.onResult.bind(e);e.onResult=_=>l(r=>{let f=r.output&&typeof r.output=="object"?{...r.output}:r.output;if(f)for(let y of["files","images"])Array.isArray(f[y])&&(f[y]=f[y].map(L=>({...L,filename:a(L.filename)})));return _({...r,error:i(r.error),output:f})});let d=e.onGenerate.bind(e);e.onGenerate=_=>d(async()=>{try{return await _()}catch(r){let f=i(r?.message);throw f!==r?.message?new Error(f):r}})})();(()=>{let t=document.querySelector("#app-config");if(!t||JSON.parse(t.textContent).id!=="video-karma")return;let e=window.appSdk,o=Symbol.for("app.videoKarmaBranding.v1");if(!e||e[o])return;e[o]=!0;let s=/(?:^|[^a-z])wan(?=$|[^a-z]|image|video|model|text|vae|i2v|t2v)/i,i=_=>typeof _=="string"&&s.test(_)?"VideoKarma1\u306E\u51E6\u7406\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u5165\u529B\u3068\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002":_,a=_=>typeof _=="string"?_.replace(/wan(?:\d+(?:\.\d+)*)?/ig,"VideoKarma"):_,n=e.onStatus.bind(e);e.onStatus=_=>n(r=>_({...r,message:i(r.message)}));let l=e.onResult.bind(e);e.onResult=_=>l(r=>{let f=r.output&&typeof r.output=="object"?{...r.output}:r.output;if(f)for(let y of["files","images"])Array.isArray(f[y])&&(f[y]=f[y].map(L=>({...L,filename:a(L.filename)})));return _({...r,error:i(r.error),output:f})});let d=e.onGenerate.bind(e);e.onGenerate=_=>d(async()=>{try{return await _()}catch(r){let f=i(r?.message);throw f!==r?.message?new Error(f):r}})})();(()=>{let t=Symbol.for("app.sideGreetings.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-i")?.closest(".video-lora-control");if(!i||i.querySelector("[data-side-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.sidePrompts="",a.setAttribute("aria-label","Side \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["changed to scene cuts and she is having sex with her leg aside "]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=Symbol.for("app.fellaGreetings.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-g")?.closest(".video-lora-control");if(!i||i.querySelector("[data-fella-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.fellaPrompts="",a.setAttribute("aria-label","Fella \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["A woman looking at the camera. The video then jumpcuts to the same woman giving a blowjob to a man standing in the same location, looking up as she performs the blowjob on the man, she is kneeling in front of him, she is holding his penis with both hands. she looks at the camera the entire time. she shoves the penis deep in her mouth."]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=Symbol.for("app.backGreetings.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-h")?.closest(".video-lora-control");if(!i||i.querySelector("[data-back-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.backPrompts="",a.setAttribute("aria-label","Back \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["The video begins with a close-up of a woman. The video then jumpcuts to the same woman now having sex in doggystyle position. She is lying on her stomach, facing forward, with her head turned slightly to the side as she reacts to the sensations of intercourse. Her facial expressions change throughout the sequence, showing moments of pleasure and exertion, including wide eyes, an open mouth, and clenched fists.","The video begins with a close-up of a woman. The video then jumpcuts to the same woman now having sex with a man in doggystyle position in the the same location. She is positioned standing, while the man stands behind her, The man is muscular his hands are wrapped around the womanss stomach holding her upright while embracing her from behind, he holds her close as he thrusts into her. As the scene progresses, she moves rhythmically with him. she is fully nude. the man aggressively rams his hips into her.","The video begins with a close-up of a woman. The video then jumpcuts to the same woman now having sex with a man in doggystyle position in the same location. She is bent over with her back arched and her head tilted down as he stands behind her, He is muscular. The womans expression changes throughout the sequence as she reacts to their movements, sometimes looking down, other times up at the camera with an open mouth. The scene is recorded from below looking up. she looks at the camera the entire time. The man agressively has sex with her. she is fully nude."]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=Symbol.for("app.kijyoGreetings.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-d")?.closest(".video-lora-control");if(!i||i.querySelector("[data-kijyo-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.kijyoPrompts="",a.setAttribute("aria-label","Kijyo \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["The video begins with a close-up of a woman. The video then jumpcuts to the same woman now having sex with a man in squatting cowgirl position her face fills the screen she is leaning over forwards as she bounces up and down aggressively. His erect penis is in her vagina.she looks at the camera throughout the video.The video is shot from above looking down on the scene.","The video begins with a close-up of a woman. The video then jumpcuts to the same woman now having sex with a man in squatting cowgirl position she is leaning backwards as she bounces up and down aggressively. His erect penis is in her vagina.she looks at the camera throughout the video."]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=Symbol.for("app.ganshaGreetings.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-e")?.closest(".video-lora-control");if(!i||i.querySelector("[data-gansha-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.ganshaPrompts="",a.setAttribute("aria-label","Gansha \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["The video begins with a close-up of a woman. The video then jumpcuts to the same woman now receiving a facial from a mans penis. She is kneeling on the floor looking up with a open mouth. The cum shoots all over her face. The mans hand holds his erect penis masturbating his penis and shooting the thick white cum directly onto her face, forehead, eyes, cheek and mouth. The thick white cum slowly drips down her face onto her body. An explosion of thick white cum blasts her face. she looks directly at the camera throughout the video.","The video begins with a close-up of a woman. The video then jumpcuts to the same woman now receiving a facial from a mans penis. She is lying on her back. The cum shoots all over her face. The mans hand holds his erect penis masturbating his penis and shooting the thick white cum directly onto her face, forehead, eyes, cheek and mouth. The thick white cum slowly drips down her onto her body. an explosion of thick white cum blasts her face.she looks directly at the camera throughout the video."]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=Symbol.for("app.tekokiGreetings.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-f")?.closest(".video-lora-control");if(!i||i.querySelector("[data-tekoki-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.tekokiPrompts="",a.setAttribute("aria-label","Tekoki \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["The video begins with a close-up of a woman. The video then jumpcuts to the same woman on her stomach with her head resting low in the frame on a mans thigh next to his penis on the left of the frame with her right hand she grasps the mans erect penis and moves it up and down the shaft in a steady rhythm, performing the handjob. She goes through various facial expressions throughout the video from happy to gasping she looks like she is talking.She looks at the camera throughout the video","The video begins with a close-up of a woman. The video then jumpcuts to the same woman now kneeling between a mans legs with her upper body is bent forward over him, and her face is close to his lap. With one hand, she grasps the mans erect penis and moves it up and down its shaft in a steady rhythm, performing the handjob. She goes through various facial expressions throughout the video from happy to gasping she looks like she is talking.She looks at the camera throughout the video She mans feet are seen in the background","The video begins with close-up of a woman. The video then jumpcuts to the same woman now kneeling on the floor in front of the man who is sitting high above her. She is giving the mans penis a handjob with both hands moving them up and down along the shaft. she goes through various facial expressions throughout the video from happy to gasping she looks like she is talking. The woman is looking up to the man.She looks at the camera throughout the video"]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=Symbol.for("app.seijyoGreetings.v1");if(document[t])return;document[t]=!0;let e=()=>{let o=document.querySelector("#workflow-form");if(!o)return;let s=()=>{let i=o.querySelector("#lora-c")?.closest(".video-lora-control");if(!i||i.querySelector("[data-seijyo-prompts]"))return;let a=document.createElement("select");a.className="lora-prompt-presets",a.dataset.seijyoPrompts="",a.setAttribute("aria-label","Seijyo \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["The video begins with a close-up of a woman. The video then jumpcuts to the same woman now having sex in missionary position. She is lying on her back on a bed with a patterned bed spread and pillow with her legs spread wide. A mans large penis is visible entering her vagina from below. The man is positioned kneeling between her legs infront of her thrusting his penis into her vagina. Throughout the scene, she appears to be experiencing pleasure, often with her mouth open or eyes closed as she lies back. Her hands hold onto her thighs spreading her legs.","The video begins with a close-up of a woman. The video then jumpcuts to the same woman now having sex in missionary position. She is lying on her back on a bed with a patterned bed spread and pillow with her legs spread with her knees to her chest. A mans large penis is visible entering her vagina from below. The man is positioned kneeling between her legs infront of her thrusting his penis into her vagina. Throughout the scene, she appears to be experiencing pleasure, often with her mouth open or eyes closed as she lies back. Her hands hold onto her thighs spreading her legs.","The video begins with a close-up of a woman.The video then jumpcuts to the same woman having sex in missionary position on the edge of a bed with a mans erect penis is in her vagina she is shown experiencing various stages of sexual pleasure, with her facial expressions changing from contentment to intense enjoyment as the act continues.she looks at the camera throughout the video."]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}a.addEventListener("change",()=>{let l=o.querySelector('textarea[name="prompt"]');a.value&&l&&(l.value=a.value,l.dispatchEvent(new Event("input",{bubbles:!0})),a.value="")}),i.append(a)};new MutationObserver(s).observe(o,{childList:!0,subtree:!0}),s()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()})();(()=>{let t=Symbol.for("app.loraTouch.v1");if(document[t])return;document[t]=!0;let e=null;function o(i){let{slider:a,offset:n}=e,l=a.getBoundingClientRect(),d=12,_=Number(a.min||0),r=Number(a.max||100),f=Number(a.step)||1,y=Math.max(0,Math.min(1,(i.clientX-n-l.left-d)/Math.max(1,l.width-d*2))),L=Math.min(r,Math.max(_,_+Math.round(y*(r-_)/f)*f)),k=Number(L.toFixed(8));a.valueAsNumber!==k&&(a.value=String(k),a.dispatchEvent(new Event("input",{bubbles:!0})))}document.addEventListener("pointerdown",i=>{if(e||!i.isPrimary||!["touch","pen"].includes(i.pointerType))return;let a=i.target instanceof Element?i.target.closest('input[type="range"][data-lora-slider]'):null;if(!a||a.disabled)return;let n=a.getBoundingClientRect(),l=Number(a.min||0),d=Number(a.max||100),_=n.left+12+(a.valueAsNumber-l)/(d-l)*(n.width-24);e={slider:a,id:i.pointerId,offset:Math.abs(i.clientX-_)<=12?i.clientX-_:0},i.preventDefault(),a.focus({preventScroll:!0}),a.setPointerCapture(i.pointerId),o(i)},{capture:!0,passive:!1}),document.addEventListener("pointermove",i=>{!e||i.pointerId!==e.id||(i.preventDefault(),o(i))},{capture:!0,passive:!1});function s(i){if(!e||i.pointerId!==e.id)return;let{slider:a,id:n}=e;i.type==="pointerup"&&(i.preventDefault(),o(i)),e=null,a.hasPointerCapture(n)&&a.releasePointerCapture(n),a.dispatchEvent(new Event("change",{bubbles:!0}))}document.addEventListener("pointerup",s,{capture:!0,passive:!1}),document.addEventListener("pointercancel",s,!0),document.addEventListener("lostpointercapture",s,!0)})();var De=["cream","cream2","mllti","fella","moving"],mt={cream:"zamen1",cream2:"zamen2",mllti:"dynamic move",moving:"dynamic move2",fella:"fella"},_t=["cream","cream2","mllti","moving","fella"],Ve=()=>Object.fromEntries(De.map(t=>[t,0]));function Pe(t,e){let o=De.filter(l=>{let d=e[l];if(!Number.isFinite(d)||d<0||d>2)throw new Error("LoRA strength must be between 0 and 2: "+l);return d!==0});if(!o.length)return;let s=Object.entries(t).filter(([,l])=>l.class_type==="CheckpointLoaderSimple");if(s.length!==1)throw new Error("Expected one VIDEO checkpoint loader");let i=s[0][0],a=Object.values(t).flatMap(l=>Object.entries(l.inputs).filter(([,d])=>Array.isArray(d)&&d[0]===i&&d[1]===0).map(([d])=>({node:l,key:d})));if(!a.length)throw new Error("VIDEO checkpoint model has no consumers");let n=[i,0];for(let l of o){let d="video_user_lora_"+l;if(t[d])throw new Error("LoRA node already exists: "+d);t[d]={class_type:"LoraLoaderModelOnly",inputs:{model:n,lora_name:l+".safetensors",strength_model:e[l]},_meta:{title:l}},n=[d,0]}for(let{node:l,key:d}of a)l.inputs[d]=n}function se(t,e=_t,o=mt){let s=document.createElement("fieldset");s.className="video-loras",s.id="video-loras";let i=document.createElement("legend");i.textContent="LoRA",s.append(i);let a=[],n=document.createElement("p");n.className="hint",n.textContent="0 = \u7121\u52B9 / 1 = \u6A19\u6E96\u306E\u5F37\u3055\u3002LoRA\u306F\u5358\u4F53\u3067\u306E\u4F7F\u7528\u3092\u63A8\u5968\u3057\u307E\u3059\u3002",s.append(n);for(let d of e){let _=document.createElement("div");_.className="video-lora-control";let r=document.createElement("label");r.htmlFor="lora-"+d,r.textContent=o[d];let f=document.createElement("input");f.type="range",f.setAttribute("aria-label",o[d]+" LoRA \u5F37\u3055"),f.dataset.loraSlider=d;let y=document.createElement("input");y.type="number",y.id="lora-"+d,y.name="lora_"+d,y.required=!0,y.setAttribute("aria-label",o[d]+" LoRA \u5F37\u3055\uFF08\u6570\u5024\uFF09");for(let k of[f,y])k.min="0",k.max="2",k.step="0.05",k.value=String(t[d]);y.inputMode="decimal";let L=k=>{t[d]=k,y.value=String(k),f.value=String(k),f.style.setProperty("--lora-fill",String(k/2*100)+"%")};f.addEventListener("input",()=>L(f.valueAsNumber)),y.addEventListener("input",()=>{y.validity.valid&&L(y.valueAsNumber)}),a.push(()=>L(0)),L(t[d]),_.append(r,y,f),s.append(_)}let l=document.createElement("button");return l.type="button",l.textContent="\u3059\u3079\u3066\u7121\u52B9\u306B\u3059\u308B",l.addEventListener("click",()=>{for(let d of a)d()}),s.append(l),s}var ge=["a","b","c","d","e","f","g","h","i","j"],Re=()=>Object.fromEntries(ge.map(t=>[t,0]));function Be(t,e){let o=document.createElement("details");o.className="wan-lora-settings";let s=document.createElement("summary");s.textContent="LoRA \u8A2D\u5B9A",o.append(s,se(t,ge,{a:"Multi Motion",b:"PaiPai",c:"Seijyo",d:"Kijyo",e:"Gansha",f:"Tekoki",g:"Fella",h:"Back",i:"Side",j:"Seisui"}));let i=o.querySelector("#lora-b").closest(".video-lora-control"),a=document.createElement("select");a.className="lora-prompt-presets",a.setAttribute("aria-label","PaiPai \u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u30D7\u30ED\u30F3\u30D7\u30C8\u3092\u7F6E\u63DB\uFF09");let n=document.createElement("option");n.value="",n.textContent="\u304A\u3059\u3059\u3081\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u9078\u3076\u3068\u7F6E\u63DB\uFF09",a.append(n);for(let l of["The video begins with a close up of a woman. The video then jumpcuts to the same woman now lying down on a tiled floor of the the same location with her breasts positioned around the mans erect penis as he thrusts his penis up and down in a titjob motion sliding it between her breasts. she makes various facial expressions during the video she looks like she is talking and has her eyes wide open with a crazy expression.","The video begins with a close up of a woman. The video then jumpcuts to the same woman kneeling in the same location with her breasts positioned around the mans erect penis as she moves them up and down in a sliding motion. she makes various facial expressions she looks like she is talking and has her eyes wide open with a crazy expression."]){let d=document.createElement("option");d.value=l,d.textContent=l,a.append(d)}return a.addEventListener("change",()=>{a.value&&(e(a.value),a.value="")}),i.append(a),o}function Ue(t,e){let o=ge.filter(s=>{let i=e[s];if(!Number.isFinite(i)||i<0||i>2)throw new Error("LoRA strength must be between 0 and 2: "+s);return i!==0});if(o.length){for(let[s,i,a]of[["high","101","104"],["low","102","103"]]){if(t[i]?.class_type!=="LoraLoaderModelOnly"||t[a]?.class_type!=="ModelSamplingSD3"||JSON.stringify(t[a].inputs.model)!==JSON.stringify([i,0]))throw new Error("Unexpected VideoKarma1 "+s+" model chain");for(let n of o)if(t["wan_user_lora_"+n+"_"+s])throw new Error("Duplicate VideoKarma1 LoRA node")}if(o.some(s=>s!=="a")){if(t[86]?.class_type!=="KSamplerAdvanced"||t[85]?.class_type!=="KSamplerAdvanced")throw new Error("Unexpected VideoKarma1 sampler pair");Object.assign(t[86].inputs,{steps:4,start_at_step:0,end_at_step:1}),Object.assign(t[85].inputs,{steps:4,start_at_step:1,end_at_step:4}),t[104].inputs.shift=2,t[103].inputs.shift=2}for(let[s,i,a]of[["high","101","104"],["low","102","103"]]){let n=[i,0];for(let l of o){let d="wan_user_lora_"+l+"_"+s;t[d]={class_type:"LoraLoaderModelOnly",inputs:{model:n,lora_name:l+"-"+s+".safetensors",strength_model:e[l]},_meta:{title:l+" "+s}},n=[d,0]}t[a].inputs.model=n}}}function We(t,e){return t==="video-flow"&&/^(i2v|t2v)/.test(e)?64:["video-flow","video-fusion","image-edit-super","image-studio"].includes(t)?32:t==="image-canvas"?64:16}function ne(t,e){return t.startsWith("image-")||t==="media-enhance"&&e==="image"?1024:768}function ze(t,e,o=!1){return o?256:t==="video-flow"&&/^(i2v|t2v)/.test(e)?128:64}function Ke(t,e,o=768,s=64){if(!Number.isFinite(t.width)||!Number.isFinite(t.height)||t.width<=0||t.height<=0)throw Error("\u7D20\u6750\u306E\u30B5\u30A4\u30BA\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3002");let i=Math.min(1,o/Math.max(t.width,t.height)),a=Math.round(t.width*i/e)*e,n=Math.round(t.height*i/e)*e;if(Math.min(a,n)<s)throw Error("\u3053\u306E\u7D20\u6750\u306F\u7E26\u6A2A\u6BD4\u3092\u4FDD\u3063\u3066\u8ABF\u6574\u3059\u308B\u3068\u77ED\u8FBA\u304C"+s+"px\u672A\u6E80\u306B\u306A\u308A\u307E\u3059\u3002\u9577\u8FBA"+o+"px\u4EE5\u5185\u30FB\u77ED\u8FBA"+s+"px\u4EE5\u4E0A\u306B\u306A\u308B\u753B\u50CF\u3084\u52D5\u753B\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002");return{width:a,height:n}}function ye(t){let e={width:0,height:0};for(let o of["width","height"])for(let s of Object.values(t)){let i=s._meta?.title||"";new RegExp("(?:^|\\s)"+o+"(?:$|\\s)","i").test(i)&&typeof s.inputs.value=="number"&&(e[o]=s.inputs.value);for(let a of[o,"resize_type."+o,"custom_"+o]){let n=s.inputs[a];typeof n=="number"&&n>0&&!e[o]&&(e[o]=n)}}return{width:e.width||768,height:e.height||768}}function ee(t,e){for(let o of Object.values(t))for(let s of["width","height"]){for(let i of[s,"custom_"+s,"resize_type."+s])typeof o.inputs[i]=="number"&&(o.inputs[i]=e[s]);if(new RegExp("(?:^|\\s)"+s+"(?:$|\\s)","i").test(o._meta?.title||"")&&(/^Primitive(?:Int|Float)$/.test(o.class_type)&&typeof o.inputs.value=="number"&&(o.inputs.value=e[s]),o.class_type==="mxSlider"))for(let i of["Xi","Xf"])typeof o.inputs[i]=="number"&&(o.inputs[i]=e[s])}}function He(t,e,o){for(let s of new Set(e.map(i=>i[0]))){if(!/loadimage/i.test(t[s]?.class_type||""))continue;let i="input_size_"+s;for(;t[i];)i+="_";for(let a of Object.values(t))for(let[n,l]of Object.entries(a.inputs))Array.isArray(l)&&String(l[0])===s&&l[1]===0&&(a.inputs[n]=[i,0]);t[i]={class_type:"ImageScale",inputs:{image:[s,0],upscale_method:"lanczos",width:o.width,height:o.height,crop:"disabled"}}}}async function Xe(t,e,o){if(e==="audio")return null;let s=URL.createObjectURL(t);try{return await new Promise((i,a)=>{let n=document.createElement(e==="image"?"img":"video"),l=setTimeout(()=>d(new Error("\u7D20\u6750\u306E\u30B5\u30A4\u30BA\u78BA\u8A8D\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\u3002")),15e3);function d(_){clearTimeout(l),n.onerror=null,!_&&o!==void 0&&n instanceof HTMLVideoElement&&(!Number.isFinite(n.duration)||n.duration<=0?_=new Error("\u7D20\u6750\u52D5\u753B\u306E\u9577\u3055\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002"):n.duration>o&&(_=new Error("\u7D20\u6750\u52D5\u753B\u306F10\u79D2\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002")));let r=n instanceof HTMLImageElement?n.naturalWidth:n.videoWidth,f=n instanceof HTMLImageElement?n.naturalHeight:n.videoHeight;n instanceof HTMLVideoElement?(n.onloadedmetadata=null,n.removeAttribute("src"),n.load()):n.onload=null,_?a(_):r&&f?i({width:r,height:f}):a(new Error("\u7D20\u6750\u306E\u30B5\u30A4\u30BA\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002"))}n.onerror=()=>d(new Error("\u3053\u306E\u7D20\u6750\u306E\u30B5\u30A4\u30BA\u3092\u30D6\u30E9\u30A6\u30B6\u3067\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u5225\u306E\u753B\u50CF\u30FB\u52D5\u753B\u5F62\u5F0F\u3067\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002")),n instanceof HTMLImageElement?n.onload=()=>d():(n.preload="metadata",n.onloadedmetadata=()=>d()),n.src=s})}finally{URL.revokeObjectURL(s)}}var Ae=(t,e)=>{let o=Object.entries(t).filter(([,n])=>e[n.class_type]?.output_node===!0).map(([n])=>n);if(!o.length)return 0;let s=new Set,i=[...o];for(;i.length;){let n=i.pop();if(s.has(n))continue;let l=t[n];if(l){s.add(n);for(let d of Object.values(l.inputs))Array.isArray(d)&&d.length===2&&i.push(String(d[0]))}}let a=0;for(let n of Object.keys(t))s.has(n)||(delete t[n],a+=1);return a};var Je=t=>{let e=t.class_type.toLowerCase(),o=Object.keys(t.inputs).map(i=>i.toLowerCase()),s=(t._meta?.title??"").toLowerCase();return e.includes("lora")||/lora|turbo|4\s*steps?/.test(s)?"lora":e.includes("loadimage")||e.includes("loadvideo")?"input":e.includes("loader")||o.some(i=>["ckpt_name","unet_name","model_name","vae_name","clip_name"].includes(i))?"model":e.includes("textencode")||o.some(i=>["text","prompt","positive","negative"].includes(i))||typeof t.inputs.value=="string"&&(e.includes("primitivestring")||e.includes("stringmultiline")||e.includes("multilinestring")||/prompt|text \(multiline\)|プロンプト/.test(s))&&!/replace|regex|concatenate|combine/.test(e)?"prompt":e.includes("sampler")||/steps?|cfg|seed/.test(s)||o.some(i=>["steps","cfg","sampler_name","scheduler","seed","noise_seed"].includes(i))?"sampling":/fps|frame rate|frames?|duration|seconds?|width|height|size|length/.test(s)||o.some(i=>["width","target_width","output_width","height","target_height","output_height","length","duration","duration_seconds","seconds","num_frames","video_frames","frame_count","frames","fps","frame_rate"].includes(i))?"size":e.includes("save")||e.includes("preview")||e.includes("combine")?"output":"advanced"};var je=t=>{let e=t.toLowerCase().replace(/[^a-z0-9]+/g,"_");return/(?:^|_)negative(?:_|$)/.test(e)||e==="neg"?"negative":/(?:^|_)positive(?:_|$)/.test(e)||e==="pos"?"positive":null},Ye=t=>{let e={},o=(i,a)=>{let n=e[i];e[i]=n&&n!==a?"both":a},s=(i,a,n)=>{if(n.has(i))return;n.add(i);let l=t[i];if(!l)return;Je(l)==="prompt"&&o(i,a);let d=Object.entries(l.inputs).filter(([r,f])=>je(r)===a&&Array.isArray(f)&&f.length===2),_=d.length?d.map(([,r])=>r):Object.values(l.inputs);for(let r of _)Array.isArray(r)&&r.length===2&&s(String(r[0]),a,n)};for(let i of Object.values(t))for(let[a,n]of Object.entries(i.inputs)){let l=je(a);!l||!Array.isArray(n)||n.length!==2||s(String(n[0]),l,new Set)}for(let[i,a]of Object.entries(t)){if(e[i]||Je(a)!=="prompt")continue;let n=(a._meta?.title??"").toLowerCase();/negative|ネガティブ|除外/.test(n)?o(i,"negative"):/positive|ポジティブ|肯定/.test(n)&&o(i,"positive")}return e};var te=t=>typeof t=="number"&&Number.isFinite(t),ae=(t,e)=>t[e.id]?.inputs[e.key],K=(t,e,o)=>{for(let s of e)t[s.id].inputs[s.key]=o},Qe=t=>t._meta?.title||"";function Ze(t,e,o){let s=document.createElement("section");s.className="panel basic-controls";let i=document.createElement("div");i.className="basic-prompts";let a=document.createElement("div");a.className="fields",s.append(i,a);let n=Object.entries(t),l=()=>s.dispatchEvent(new CustomEvent("workflowchange",{bubbles:!0}));function d(u,c,m,p={}){let h=document.createElement("label");h.className="field";let A=document.createElement("span");A.textContent=u,h.append(A);let x=p.multiline?document.createElement("textarea"):document.createElement("input");return x instanceof HTMLTextAreaElement?x.rows=3:(x.type="number",p.min!==void 0&&(x.min=String(p.min)),p.max!==void 0&&(x.max=String(p.max)),x.step=String(p.step??1)),x.value=String(c),p.name&&(x.name=p.name),p.ref&&(x.dataset.node=p.ref.id,x.dataset.input=p.ref.key),x.addEventListener("input",()=>{if(x instanceof HTMLInputElement){if(x.value===""||!x.validity.valid)return;let C=Number(x.value);if(!Number.isFinite(C))return;m(C)}else m(x.value);l()}),h.append(x),(p.container||(p.multiline?i:a)).append(h),x}let _=Ye(t),r=new Map;for(let[u,c]of n)if(!(_[u]==="negative"||/negative|ネガティブ|除外/i.test(Qe(c))))for(let[m,p]of Object.entries(c.inputs)){if(typeof p!="string"||!(/^(text|prompt)$/.test(m)||m==="value"&&/PrimitiveString|StringMultiline|MultilineString/.test(c.class_type))||o.angleControls?.node===u&&o.angleControls.key===m||!p.trim()&&_[u]!=="positive")continue;let h=r.get(p)||{refs:[],value:p};h.refs.push({id:u,key:m}),r.set(p,h)}let k=0;for(let u of r.values()){if(e.id==="image-edit-super"&&o.id==="pose")continue;k++;let c=e.id==="sound-sync"?"\u97F3\u306E\u5185\u5BB9":e.id==="dialogue"?"\u52D5\u753B\u306E\u5185\u5BB9":r.size>1?"\u30D7\u30ED\u30F3\u30D7\u30C8 "+k:"\u30D7\u30ED\u30F3\u30D7\u30C8",m=e.id==="dialogue"&&u.value.match(/^; she says: [^\n]*\n*/)?.[0]||"",p=d(c,m?u.value.slice(m.length):u.value,h=>K(t,u.refs,m+h),{multiline:!0,ref:u.refs[0],name:"prompt"+(k>1?"_"+k:"")});e.id==="video-flow"&&(p.placeholder="\u5973\u6027\u304C\u30B3\u30C3\u30D7\u3092\u624B\u306B\u53D6\u3063\u3066\u98F2\u3080"),e.id==="image-edit-prime"&&(p.placeholder="\u5973\u6027\u304C\u7B11\u3046")}if(["video-karma","video-flow","video-extend"].includes(e.id)){let u=new Map;for(let[m,p]of n)if(!(_[m]!=="negative"&&!/negative|ネガティブ|除外/i.test(Qe(p))))for(let[h,A]of Object.entries(p.inputs)){if(typeof A!="string"||!(/^(text|prompt)$/.test(h)||h==="value"&&/PrimitiveString|StringMultiline|MultilineString/.test(p.class_type)))continue;let x=u.get(A)||{refs:[],value:A};x.refs.push({id:m,key:h}),u.set(A,x)}let c=0;for(let m of u.values())c++,d("\u30CD\u30AC\u30C6\u30A3\u30D6\u30D7\u30ED\u30F3\u30D7\u30C8"+(u.size>1?" "+c:""),m.value,h=>K(t,m.refs,h),{multiline:!0,ref:m.refs[0],name:"negative_prompt"+(c>1?"_"+c:"")}).setAttribute("placeholder","\u907F\u3051\u305F\u3044\u5185\u5BB9\u3092\u5165\u529B\uFF08\u4EFB\u610F\uFF09")}let N=(u,c,m)=>{let p=t[u]?.inputs[c];return te(p)?p:m},w=(u,c,m)=>{te(t[u]?.inputs[c])&&(t[u].inputs[c]=m)};function g(){return n.flatMap(([u,c])=>Object.entries(c.inputs).filter(([m,p])=>te(p)&&(m==="length"&&/LatentVideo|ImageToVideo|BerniniConditioning/.test(c.class_type)||m==="frames_number"||m==="frame_load_cap"&&p>0)).map(([m])=>({id:u,key:m})))}let Q;function H(u,c,m,p=.25,h,A={}){Q=d(u,c,m,{name:"seconds",min:A.min??p,max:A.max??30,step:p,ref:h})}let Z=N("24","frame_rate",20),rt=Math.max(1,N("26","multiplier",2)),fe=5;function Ce(){let u=Z/rt,c=Math.max(5,Math.round(fe*u/4)*4+1);w("24","frame_rate",Z),K(t,g(),c);for(let[,m]of n)/^VHS_LoadVideo/.test(m.class_type)&&typeof m.inputs.force_rate=="number"&&(m.inputs.force_rate=u)}if(e.id==="video-extend"){let u=document.createElement("label");u.className="field bernini-fps-control";let c=document.createElement("span");c.textContent="FPS\uFF08\u51FA\u529B\uFF09";let m=document.createElement("output");m.textContent=String(Z);let p=document.createElement("input");p.type="range",p.name="fps",p.id="bernini-fps",p.min="1",p.max="24",p.step="1",p.value=String(Z),p.style.cssText="width:100%;min-height:44px;touch-action:pan-y",p.setAttribute("aria-valuetext",Z+" FPS"),p.addEventListener("input",()=>{let h=Number(p.value);!Number.isInteger(h)||h<1||h>24||(Z=h,m.textContent=String(h),p.setAttribute("aria-valuetext",h+" FPS"),Ce(),l())}),u.append(c,m,p),a.append(u)}if(!o.continuation)if(e.id==="video-karma"){let u=g(),c=N("94","fps",16),m=u.length?Number(ae(t,u[0])):81,p=Number(((m-1)/c).toFixed(2)),h=10,A=n.flatMap(([B,J])=>Object.entries(J.inputs).filter(([j,z])=>/^(fps|frame_rate)$/.test(j)&&te(z)).map(([j])=>({id:B,key:j}))),x=B=>{let J=Math.max(5,Math.round(B*h/4)*4+1);K(t,u,J)};H("\u79D2\u6570",p,x,.25,u[0]);let C=document.createElement("label");C.className="field karma-fps-control";let O=document.createElement("span");O.textContent="FPS";let E=document.createElement("output");E.textContent=String(h);let S=document.createElement("input");S.type="range",S.name="fps",S.id="karma-fps",S.min="1",S.max="24",S.step="1",S.value=String(h),S.dataset.loraSlider="",E.htmlFor=S.id;let X=()=>{h=Number(S.value),E.textContent=String(h),S.setAttribute("aria-valuetext",h+" FPS"),K(t,A,h),x(Number(Q.value)),l()};S.addEventListener("input",X),C.append(O,E,S),a.append(C),X()}else if(e.id==="video-flow"&&/^(i2v|t2v)/.test(o.id)){let u=N("300","value",24);H("\u79D2\u6570",N("301","value",5),c=>{w("301","value",c)},1,{id:"301",key:"value"})}else if(e.id==="video-flow"){let u=o.id==="crossview",c=u?N("616","value",24):N("133","frame_rate",24),m=g(),p=u?N("614","value",121):N("113","length",121);H("\u79D2\u6570",u?N("615","value",5):Number(((p-1)/c).toFixed(2)),h=>{let A=Math.max(9,Math.round(h*c/8)*8+1);K(t,m,A),u?(w("614","value",A),w("615","value",h)):(w("116","video_end_time",h),w("116","audio_end_time",h))},1,u?{id:"615",key:"value"}:{id:"113",key:"length"})}else if(e.id==="dialogue"){let u=N("542","value",24);H("\u52D5\u753B\u306E\u79D2\u6570",N("796","Xi",144)/u,c=>{let m=Math.max(8,Math.round(c*u/8)*8);w("796","Xi",m),w("796","Xf",m),w("535","frames_number",m+1)},1,{id:"796",key:"Xi"})}else if(e.id==="video-fusion")if(t[21]?.class_type==="MiniMaxH3Easy")H("\u79D2\u6570",N("21","seconds",5),u=>w("21","seconds",u),1,{id:"21",key:"seconds"},{min:1,max:20});else{let u=N("132","fps",24),c=N("133","length",124);H("\u79D2\u6570",Math.round(c/u*2)/2,m=>w("133","length",Math.max(5,Math.ceil((m*u-5)/17)*17+5)),.5,{id:"133",key:"length"})}else e.id==="video-extend"?H("\u79D2\u6570",fe,u=>{fe=u,Ce()},.1,{id:"20",key:"length"}):e.id==="sound-sync"&&H("\u97F3\u58F0\u306E\u79D2\u6570",N("92","duration",5),u=>w("92","duration",u),.5,{id:"92",key:"duration"});if(e.id==="motion-smooth"){let u=n.find(([,c])=>c.class_type==="FrameInterpolate");if(u){let[c,m]=u,p=Number(m.inputs.multiplier)||2,h=n.find(([,x])=>x.class_type==="VHS_VideoCombine"),A=h&&te(h[1].inputs.frame_rate)?h[1].inputs.frame_rate/p:void 0;d("\u306A\u3081\u3089\u304B\u3055\uFF08\u500D\uFF09",p,x=>{m.inputs.multiplier=x,h&&A!==void 0&&(h[1].inputs.frame_rate=A*x)},{name:"interpolation",ref:{id:c,key:"multiplier"},min:2,max:8,step:1})}}return s.hidden=!i.children.length&&!a.children.length,s}var $e={"CLIPSave":{"output_node":true},"CheckpointPerturbWeights":{"output_node":true},"CheckpointSave":{"output_node":true},"ConsoleDebug+":{"output_node":true},"CreateBoundingBoxes":{"output_node":true},"DebugTensorShape+":{"output_node":true},"DecodeAndSaveVideo":{"output_node":true},"Display Any (rgthree)":{"output_node":true},"Display Int (rgthree)":{"output_node":true},"DisplayAny":{"output_node":true},"DummyOut":{"output_node":true},"FastPreview":{"output_node":true},"FastPreviewBatch":{"output_node":true},"HDRPreviewKJ":{"output_node":true},"IPAdapterSaveEmbeds":{"output_node":true},"Image Comparer (rgthree)":{"output_node":true},"ImageAndMaskPreview":{"output_node":true},"ImageCompare":{"output_node":true},"ImageCompositor":{"output_node":true},"ImageOnlyCheckpointSave":{"output_node":true},"ImagePreviewFromLatent+":{"output_node":true},"LTXVGemmaEnhancePrompt":{"output_node":true},"LTXVHDRDecodePostprocess":{"output_node":true},"LTXVSaveConditioning":{"output_node":true},"LTXVSparseTrackEditor":{"output_node":true},"LoraExtractKJ":{"output_node":true},"LoraReduceRankKJ":{"output_node":true},"LoraSave":{"output_node":true},"LossGraphNode":{"output_node":true},"MaskPreview":{"output_node":true},"MaskPreview+":{"output_node":true},"ModelSave":{"output_node":true},"ModelSaveKJ":{"output_node":true},"PlaySoundKJ":{"output_node":true},"Preview3D":{"output_node":true},"Preview3DAdvanced":{"output_node":true},"PreviewAnimation":{"output_node":true},"PreviewAny":{"output_node":true},"PreviewAudio":{"output_node":true},"PreviewGaussianSplat":{"output_node":true},"PreviewImage":{"output_node":true},"PreviewImageOrMask":{"output_node":true},"PreviewPointCloud":{"output_node":true},"SAM3_TrackPreview":{"output_node":true},"Save3DAdvanced":{"output_node":true},"SaveAnimatedPNG":{"output_node":true},"SaveAnimatedWEBP":{"output_node":true},"SaveAudio":{"output_node":true},"SaveAudioAdvanced":{"output_node":true},"SaveAudioMP3":{"output_node":true},"SaveAudioOpus":{"output_node":true},"SaveCondition_EditUtils":{"output_node":true},"SaveGLB":{"output_node":true},"SaveGaussianSplat":{"output_node":true},"SaveImage":{"output_node":true},"SaveImageAdvanced":{"output_node":true},"SaveImageDataSetToFolder":{"output_node":true},"SaveImageKJ":{"output_node":true},"SaveImageTextDataSetToFolder":{"output_node":true},"SaveImageWebsocket":{"output_node":true},"SaveImageWithAlpha":{"output_node":true},"SaveLatent":{"output_node":true},"SaveLoRA":{"output_node":true},"SavePointCloud":{"output_node":true},"SavePoseKpsAsJsonFile":{"output_node":true},"SaveSVGNode":{"output_node":true},"SaveStringKJ":{"output_node":true},"SaveText":{"output_node":true},"SaveTrainingDataset":{"output_node":true},"SaveVideo":{"output_node":true},"SaveWEBM":{"output_node":true},"SeparateMasks":{"output_node":true},"VAESave":{"output_node":true},"VHS_PruneOutputs":{"output_node":true},"VHS_VideoCombine":{"output_node":true},"VisualizeCUDAMemoryHistory":{"output_node":true},"KSampler":{"input":{"required":{"sampler_name":[["euler","euler_cfg_pp","euler_ancestral","euler_ancestral_cfg_pp","heun","heunpp2","exp_heun_2_x0","exp_heun_2_x0_sde","dpm_2","dpm_2_ancestral","lms","dpm_fast","dpm_adaptive","dpmpp_2s_ancestral","dpmpp_2s_ancestral_cfg_pp","dpmpp_sde","dpmpp_sde_gpu","dpmpp_2m","dpmpp_2m_cfg_pp","dpmpp_2m_sde","dpmpp_2m_sde_gpu","dpmpp_2m_sde_heun","dpmpp_2m_sde_heun_gpu","dpmpp_3m_sde","dpmpp_3m_sde_gpu","ddpm","lcm","ipndm","ipndm_v","deis","res_multistep","res_multistep_cfg_pp","res_multistep_ancestral","res_multistep_ancestral_cfg_pp","gradient_estimation","gradient_estimation_cfg_pp","er_sde","seeds_2","seeds_3","sa_solver","sa_solver_pece","ddim","uni_pc","uni_pc_bh2"],{"tooltip":"The algorithm used when sampling, this can affect the quality, speed, and style of the generated output."}],"scheduler":[["simple","sgm_uniform","karras","exponential","ddim_uniform","beta","normal","linear_quadratic","kl_optimal"],{"tooltip":"The scheduler controls how noise is gradually removed to form the image."}]}}}};var I=JSON.parse(document.querySelector("#app-config").textContent),xe=$e,T=t=>document.querySelector(t),de=t=>structuredClone(t),M=I.modes[0],U=de(M?.workflow||{}),et=Ve(),tt=Re(),V=new Map,ue=[],Te="",re=1,pe=768;function at(){return ne(I.id,M.id)===768?pe:1024}function be(){if(I.id==="image-canvas")return ye(U);let t=ye(M.workflow),e=ne(I.id,M.id)===768&&pe===1024?1024/Math.max(t.width,t.height):1;return nt({width:t.width*e,height:t.height*e})}var F=(t,e,o)=>{let s=document.createElement(t);return s.textContent=e,o&&(s.className=o),s};function W(t,e=!1){T("#ui-status").textContent=t,T("#ui-status").classList.toggle("error",e)}function ie(t,e,o,s={}){let i=document.createElement("label");i.className="field",i.append(F("span",t));let a;if(s.choices){a=document.createElement("select");for(let n of[...new Set([String(e),...s.choices])]){let l=document.createElement("option");l.value=n,l.textContent=n,a.append(l)}}else s.rows?(a=document.createElement("textarea"),a.rows=s.rows):(a=document.createElement("input"),a.type=typeof e=="boolean"?"checkbox":s.type||(typeof e=="number"?"number":"text"));return a instanceof HTMLInputElement&&a.type==="checkbox"?(a.checked=!!e,i.classList.add("check-field")):a.value=String(e),a instanceof HTMLInputElement&&(s.min!==void 0&&(a.min=String(s.min)),s.max!==void 0&&(a.max=String(s.max)),s.step!==void 0?a.step=String(s.step):typeof e=="number"&&(a.step="any")),a.addEventListener("input",()=>{let n=a instanceof HTMLInputElement&&a.type==="checkbox"?a.checked:typeof e=="number"?Number(a.value):a.value;o(n)}),i.append(a),s.help&&i.append(F("small",s.help)),{wrapper:i,field:a}}var ft=t=>"upload-"+crypto.randomUUID()+"."+(t.name.split(".").pop()?.toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,8)||"bin"),Ie=t=>/loadaudio/i.test(t.class_type)?"audio":/loadvideo/i.test(t.class_type)?"video":/loadimage/i.test(t.class_type)?"image":null,ve=t=>Object.keys(t.inputs).find(e=>/^(image|video|audio|file|path|filename|audiofile|audiopath)$/i.test(e)&&typeof t.inputs[e]=="string");function me(t,e,o,s,i=!0,a=!1){let n=e==="video"&&(I.id==="video-extend"||o==="continuation-source"||I.id==="video-flow"&&M?.id==="crossview"),l=document.createElement("div");l.className="media-card",l.append(F("strong",t));let d=document.createElement("input");d.type="file",d.accept=e+"/*",d.multiple=a,d.dataset.upload=o,d.setAttribute("aria-label",t);let _=document.createElement("div");_.className="media-preview";let r=F("small",i?"\u7D20\u6750\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002":"\u4EFB\u610F\u306E\u53C2\u8003\u7D20\u6750");return n&&l.append(F("small","\u7D20\u6750\u52D5\u753B\u306F10\u79D2\u4EE5\u5185\u3002")),d.onchange=()=>{d.setCustomValidity("");for(let[y,L]of V)(y===o||y.startsWith(o+"::"))&&(URL.revokeObjectURL(L.url),V.delete(y));_.replaceChildren();let f=Array.from(d.files||[]);try{if(f.length>20)throw new Error("\u53C2\u8003\u7D20\u6750\u306F20\u4EF6\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");if(f.reduce((y,L)=>y+L.size,0)>2*1024*1024*1024)throw new Error("\u5165\u529B\u7D20\u6750\u306F\u5408\u8A082 GiB\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");f.forEach((y,L)=>{if(y.size>512*1024*1024)throw new Error("\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u306F512 MiB\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");let k=URL.createObjectURL(y),N=ft(y),w=Xe(y,e,n?10:void 0);w.catch(b=>{n&&V.get(o)?.file===y&&d.isConnected&&(d.setCustomValidity(b.message),r.textContent=b.message,W(b.message,!0))}),V.set(a?o+"::"+L:o,{file:y,name:N,refs:s,url:k,kind:e,dimensions:w});let g=document.createElement(e==="image"?"img":e==="audio"?"audio":"video");g.src=k,g instanceof HTMLMediaElement?(g.controls=!0,g.preload="metadata",g instanceof HTMLVideoElement&&(g.playsInline=!0)):g.alt=y.name,_.append(g)}),r.textContent=f.length?f.map(y=>y.name+" \xB7 "+(y.size/1048576).toFixed(1)+" MiB").join(", "):i?"\u7D20\u6750\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002":"\u4EFB\u610F\u306E\u53C2\u8003\u7D20\u6750",W(f.length?"\u7D20\u6750\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F\u3002\u5B9F\u884C\u30DC\u30BF\u30F3\u3067\u958B\u59CB\u3067\u304D\u307E\u3059\u3002":"\u7D20\u6750\u3092\u89E3\u9664\u3057\u307E\u3057\u305F\u3002"),Le().catch(y=>W(y.message,!0))}catch(y){W(y.message,!0),d.value=""}},l.append(d,_,r),l}function ht(){for(let t of V.values())URL.revokeObjectURL(t.url);V.clear()}function gt(t){let e=I.modes.find(o=>o.id===t);e&&(M=e,U=de(e.workflow),ht(),st(),W("\u5165\u529B\u3092\u8A2D\u5B9A\u3057\u3001\u30B5\u30A4\u30C8\u4E0A\u306E\u300C\u751F\u6210\u3059\u308B\u300D\u3092\u62BC\u3057\u3066\u304F\u3060\u3055\u3044\u3002"))}function it(t){return I.task==="dialogue"&&t==="dialogue-generated.wav"||M?.continuation&&t===M.continuation.last_frame_name}function ot(t){return I.id==="image-studio"&&M.id==="reference"?Ge(t):I.id!=="image-edit-super"||M.id!=="reference"?new Set:new Set(Object.values(t).filter(e=>e.class_type==="TextEncodeQwenImageEditPlus").flatMap(e=>{let o=e.inputs.image2;return Array.isArray(o)&&t[String(o[0])]?.class_type==="LoadImage"?[String(o[0])]:[]}))}function st(){T("#workflow-form").replaceChildren(),T("#upload-list").replaceChildren();let t=new Map,e={};for(let[i,a]of Object.entries(U)){let n=Ie(a),l=ve(a);if(n&&l&&!it(a.inputs[l])){let d=n+":"+String(a.inputs[l]),_=t.get(d);if(!_){e[n]=(e[n]||0)+1;let r={image:"\u753B\u50CF",video:"\u52D5\u753B",audio:"\u97F3\u58F0"},f=a._meta?.title||"",y=I.id==="video-extend"&&n==="image"?"\u30B5\u30D6\u753B\u50CF":/reference|参考|参照/i.test(f)?"\u53C2\u8003"+r[n]:/pose|姿勢/i.test(f)?"\u59FF\u52E2\u306E\u53C2\u8003\u753B\u50CF":r[n];_={kind:n,label:I.id==="image-edit-super"&&M.id==="pose"&&n==="image"&&e[n]===2?"\u30DD\u30FC\u30BA\u53C2\u7167\u753B\u50CF":y+(e[n]>1?" "+e[n]:""),refs:[]},t.set(d,_)}_.refs.push([i,l])}}let o=ot(U);for(let[i,a]of t){let n=!a.refs.every(([l])=>o.has(l));T("#upload-list").append(me(a.label+(n?"":"\uFF08\u4EFB\u610F\uFF09"),a.kind,i,a.refs,n))}M?.continuation&&T("#upload-list").prepend(me("\u5EF6\u9577\u3059\u308B\u5143\u52D5\u753B","video","continuation-source",[])),At(),ee(U,be()),T("#workflow-form").append(Ze(U,I,M)),I.id==="image-canvas"&&T("#workflow-form").append(we(U,xe)),I.id==="video-flow"&&T("#workflow-form").append(se(et)),I.id==="video-karma"&&T("#workflow-form").append(Be(tt,i=>{let a=T('#workflow-form textarea[name="prompt"]');a.value=i,a.dispatchEvent(new Event("input",{bubbles:!0}))}));let s=F("p","\u30B5\u30A4\u30BA\u306F\u81EA\u52D5\u8ABF\u6574\uFF08\u9577\u8FBA\u6700\u5927"+at()+"px\uFF09\u3002\u7D20\u6750\u306E\u7E26\u6A2A\u6BD4\u3092\u57FA\u6E96\u306B\u3057\u307E\u3059\u3002","hint");s.id="output-size-hint",I.id==="image-canvas"&&(s.textContent="\u5E45\u30FB\u9AD8\u3055\u306F256\u301C1024px\u300164px\u5358\u4F4D\u3067\u8A2D\u5B9A\u3067\u304D\u307E\u3059\u3002"),T("#workflow-form").append(s)}function Oe(){let t=V.get("continuation-source");if(t)return t;let e=Array.from(document.querySelectorAll("[data-upload]")).flatMap(o=>{let s=V.get(o.dataset.upload);return s&&s.kind!=="audio"?[s]:[]});return e.find(o=>o.kind==="video")||e[0]}function nt(t){return Ke(t,We(I.id,M.id),at(),ze(I.id,M.id,!!M.continuation))}async function Le(){let t=Oe();if(!t)return null;let e=await t.dimensions;if(t!==Oe()||!e)return null;let o=nt(e);ee(U,o);let s=document.querySelector("#output-size-hint");return s&&(s.textContent="\u51FA\u529B\u30B5\u30A4\u30BA\uFF1A"+o.width+" \xD7 "+o.height+" px\uFF08\u7D20\u6750\u306E\u7E26\u6A2A\u6BD4\u306B\u5408\u308F\u305B\u3001\u5BFE\u5FDC\u3059\u308B\u30B5\u30A4\u30BA\u5358\u4F4D\u306B\u8ABF\u6574\uFF09"),o}function Ee(){let t=crypto.getRandomValues(new Uint32Array(2)),e=(t[0]&2097151)*4294967296+t[1];return e===re&&(e=(e+1)%Number.MAX_SAFE_INTEGER),re=e,e}async function lt(){let t=Array.from(V.values());if(t.length>20)throw new Error("\u5165\u529B\u7D20\u6750\u306F\u6700\u592720\u30D5\u30A1\u30A4\u30EB\u3067\u3059\u3002");if(t.some(e=>e.file.size>512*1024*1024))throw new Error("\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u306F512 MiB\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");if(t.reduce((e,o)=>e+o.file.size,0)>2*1024*1024*1024)throw new Error("\u5165\u529B\u7D20\u6750\u306E\u5408\u8A08\u306F2 GiB\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");return t.map(e=>({name:e.name,mime_type:e.file.type||yt(e.file.name),blob:e.file}))}function yt(t){let e=t.split(".").pop()?.toLowerCase();return{png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",webp:"image/webp",mp4:"video/mp4",webm:"video/webm",mov:"video/quicktime",wav:"audio/wav",mp3:"audio/mpeg",flac:"audio/flac",ogg:"audio/ogg"}[e||""]||"application/octet-stream"}async function dt(){let t=await Le();if(!T("#app-form").reportValidity())throw new Error("\u5165\u529B\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002");let e=de(U);for(let d of ot(e)){let _=e[d],r=ve(_);if(r&&!V.has("image:"+_.inputs[r])){I.id==="image-studio"&&qe(e);for(let f of Object.values(e))f.class_type==="TextEncodeQwenImageEditPlus"&&Array.isArray(f.inputs.image2)&&String(f.inputs.image2[0])===d&&delete f.inputs.image2}}["image-edit-super","image-studio"].includes(I.id)&&M.id==="reference"&&Ae(e,xe);let o=new Set;for(let d of Object.values(e)){let _=ve(d);Ie(d)&&_&&!it(d.inputs[_])&&o.add(Ie(d)+":"+d.inputs[_])}for(let d of o)if(!V.has(d))throw new Error("\u5165\u529B\u7D20\u6750\u304C\u672A\u9078\u629E\u3067\u3059: "+d.split(":").slice(1).join(":"));for(let d of V.values())for(let[_,r]of d.refs)e[_]&&(e[_].inputs[r]=d.name);let s=I.id==="image-canvas"&&!Y.randomSeed?Y.seed:Ee();re=s;for(let[d,_]of Object.entries(e))for(let r of Object.keys(_.inputs))if(/^(seed|noise_seed)$/i.test(r)&&typeof _.inputs[r]=="number"){_.inputs[r]=s,U[d].inputs[r]=s;let f=document.querySelector(`[data-node="${CSS.escape(d)}"][data-input="${r}"]`);f&&(f.value=String(s))}let i=t||be();ee(e,i);let a=Oe();a?.kind==="image"&&He(e,a.refs,i),I.id==="video-flow"&&Pe(e,et),I.id==="video-karma"&&Ue(e,tt),Ae(e,xe);let n=await lt();W("\u751F\u6210\u7528\u306E\u5165\u529B\u3092\u30B5\u30A4\u30C8\u3078\u6E21\u3057\u307E\u3057\u305F\u3002");let l;if(M?.continuation){let d=V.get("continuation-source");if(!d)throw new Error("\u5EF6\u9577\u3059\u308B\u5143\u52D5\u753B\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002");l={...de(M.continuation),source_video_name:d.name};let _=e[20]?.inputs,r=e[24]?.inputs;_&&(l.width=_.width,l.height=_.height),r&&(l.fps=r.frame_rate)}return{workflow:e,files:n,...l?{continuation:l}:{}}}function At(){let t=T("#quick-controls");t.replaceChildren();let e=M?.angleControls;if(ne(I.id,M.id)===768){let o=ie("\u52D5\u753B\u306E\u89E3\u50CF\u5EA6",String(pe),()=>{},{choices:["768","1024"],help:"\u6700\u59271024\u306F\u751F\u6210\u6642\u9593\u304C\u5897\u3048\u307E\u3059\u3002"}),s=o.field;s.name="video_max_edge";for(let i of Array.from(s.options))i.textContent="\u6700\u5927"+i.value+" px";s.onchange=()=>{pe=s.value==="1024"?1024:768,Le().then(i=>{if(!i){let a=be();ee(U,a);let n=document.querySelector("#output-size-hint");n&&(n.textContent="\u51FA\u529B\u30B5\u30A4\u30BA\uFF1A"+a.width+" \xD7 "+a.height+" px")}}).catch(i=>W(i.message,!0))},t.append(o.wrapper)}if(e){let o=document.createElement("div");o.className="panel";let s={};for(let[i,a]of[["azimuth","\u5DE6\u53F3\u306E\u30A2\u30F3\u30B0\u30EB"],["elevation","\u30AB\u30E1\u30E9\u306E\u9AD8\u3055"],["distance","\u30AB\u30E1\u30E9\u306E\u8DDD\u96E2"]]){let n=e[i]||[];s[i]=n.find(_=>String(U[e.node]?.inputs[e.key]||"").includes(_.prompt))?.prompt||n[0]?.prompt||"";let l=document.createElement("label");l.className="field",l.append(F("span",a));let d=document.createElement("select");d.name=i;for(let _ of n){let r=document.createElement("option");r.value=_.prompt,r.textContent=_.label,d.append(r)}d.value=s[i],d.onchange=()=>{s[i]=d.value;let _=e.prefix+" "+Object.values(s).join(e.prefix.startsWith("crossview")?", ":" ");U[e.node].inputs[e.key]=_;let r=document.querySelector('[data-node="'+e.node+'"][data-input="'+e.key+'"]');r&&(r.value=_)},l.append(d),o.append(l)}t.append(o)}if(M?.continuation){let o=ie("\u7D50\u5408\u5F8C\u306E\u76EE\u6A19\u79D2\u6570",Number(M.continuation.target_duration_seconds)||10,i=>{M.continuation.target_duration_seconds=i},{min:1,max:120,step:.1}),s=document.createElement("div");s.className="panel",s.append(o.wrapper),t.append(s)}}function xt(){let t=document.createElement("section");t.className="panel",t.id="dialogue-controls",t.append(F("h3","\u30BB\u30EA\u30D5\u3068\u58F0"));let e={text:"\u3053\u3093\u306B\u3061\u306F\u3002\u4ECA\u65E5\u306F\u3044\u3044\u5929\u6C17\u3067\u3059\u306D\u3002",caption:"A calm, natural Japanese voice.",num_steps:60,cfg_scale_text:3,cfg_scale_caption:3,cfg_scale_speaker:10,duration_scale:1,useCaptionWithReference:!1};for(let[o,s,i]of[["text","\u30BB\u30EA\u30D5",{rows:4}],["caption","\u58F0\u306E\u8AAC\u660E",{rows:3}],["duration_scale","\u767A\u8A71\u306E\u9577\u3055\u500D\u7387",{min:.5,max:2,step:.05}]]){let a=ie(s,e[o],n=>e[o]=n,i);a.field.setAttribute("name","dialogue_"+o),t.append(a.wrapper)}return T("#quick-controls").after(t),T("#upload-list").append(me("\u58F0\u306E\u53C2\u8003\u97F3\u58F0\uFF08\u4EFB\u610F\uFF09","audio","dialogue-voice",[],!1,!0)),async()=>{if(!e.text.trim()||e.text.length>1e3)throw new Error("\u30BB\u30EA\u30D5\u306F1\u301C1000\u6587\u5B57\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");let o=await dt(),s=Array.from(V.entries()).filter(([i])=>i.startsWith("dialogue-voice")).map(([,i])=>i);if(s.reduce((i,a)=>i+a.file.size,0)>20*1024*1024)throw new Error("\u53C2\u8003\u97F3\u58F0\u306F\u5408\u8A0820 MiB\u4EE5\u5185\u3067\u3059\u3002");return o.workflow[536]&&(o.workflow[536].inputs.text=String(o.workflow[536].inputs.text).replace(/^; she says: [^\n]*/,"; she says: "+String(e.text).trim().replace(/[\"']/g,"").replace(/\s+/g," ")+".")),{...o,task:"dialogue",irodori:{...e,caption:s.length&&!e.useCaptionWithReference?"":e.caption,cfg_scale_speaker:s.length?e.cfg_scale_speaker:0,seed:re,reference_audios:s.map(i=>i.name)}}}}function Tt(){let t=T("#workflow-form");t.replaceChildren();let e={text:"\u3053\u3093\u306B\u3061\u306F\u3002\u4ECA\u65E5\u3082\u826F\u3044\u4E00\u65E5\u3092\u3002",caption:"\u843D\u3061\u7740\u3044\u305F\u81EA\u7136\u306A\u58F0",num_steps:60,cfg_scale_text:3,cfg_scale_caption:3,cfg_scale_speaker:10,speed:1,seconds:0,useCaptionWithReference:!1};for(let[o,s,i]of[["text","\u8AAD\u307F\u4E0A\u3052\u308B\u6587\u7AE0",{rows:4}],["caption","\u58F0\u306E\u8AAC\u660E",{rows:3}],["speed","\u8A71\u3059\u901F\u5EA6",{min:.5,max:2,step:.05}],["seconds","\u9577\u3055\uFF080\u306F\u81EA\u52D5\uFF09",{min:0,max:45,step:.5}]]){let a=ie(s,e[o],n=>e[o]=n,i);a.field.setAttribute("name",o),t.append(a.wrapper)}return T("#upload-list").append(me("\u53C2\u8003\u97F3\u58F0\uFF08\u4EFB\u610F\u30FB\u8907\u6570\u5BFE\u5FDC\uFF09","audio","voice",[],!1,!0)),async()=>{if(!String(e.text).trim()||String(e.text).length>1e3)throw new Error("\u8AAD\u307F\u4E0A\u3052\u308B\u6587\u7AE0\u306F1\u301C1000\u6587\u5B57\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");let o=Array.from(V.values());if(o.length>20||o.reduce((i,a)=>i+a.file.size,0)>20*1024*1024)throw new Error("\u53C2\u8003\u97F3\u58F0\u306F\u6700\u592720\u4EF6\u3001\u5408\u8A0820 MiB\u4EE5\u5185\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");let s=Ee();return{task:"irodori",text:e.text,caption:o.length&&!e.useCaptionWithReference?"":e.caption,num_steps:e.num_steps,cfg_scale_text:e.cfg_scale_text,cfg_scale_caption:e.cfg_scale_caption,cfg_scale_speaker:o.length?e.cfg_scale_speaker:0,duration_scale:1/e.speed,...e.seconds>0?{seconds:e.seconds}:{},seed:s,reference_audios:o.map(i=>i.name),files:await lt()}}}function It(){let t={system:"\u65E5\u672C\u8A9E\u3067\u308F\u304B\u308A\u3084\u3059\u304F\u5FDC\u7B54\u3057\u3066\u304F\u3060\u3055\u3044\u3002",message:"",max_tokens:1024,temperature:.8,top_p:.95,repetition_penalty:1.1},e=T("#workflow-form");e.replaceChildren();for(let[s,i,a]of[["message","\u30E1\u30C3\u30BB\u30FC\u30B8",{rows:4}]]){let n=ie(i,t[s],l=>t[s]=l,a);n.field.setAttribute("name",s),e.append(n.wrapper)}let o=F("button","\u4F1A\u8A71\u5C65\u6B74\u3092\u6D88\u53BB");return o.type="button",o.onclick=()=>{ue=[],T("#chat-history").replaceChildren()},e.append(o,F("div","","chat-history")),e.lastElementChild.id="chat-history",async()=>{if(!t.message.trim())throw new Error("\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");Te=t.message.trim();let s=Ee();return{task:"chat",messages:[{role:"system",content:t.system},...ue.slice(-8),{role:"user",content:Te}],max_tokens:t.max_tokens,temperature:t.temperature,top_p:t.top_p,repetition_penalty:t.repetition_penalty,seed:s,files:[]}}}function vt(t){if(t.url){let e=new URL(t.url);if(e.protocol!=="https:")throw new Error("\u51FA\u529BURL\u304C\u4E0D\u6B63\u3067\u3059\u3002");return e.href}if(t.data&&/^(image|video|audio)\//.test(t.mime_type))return"data:"+t.mime_type+";base64,"+t.data;throw new Error("\u7D50\u679C\u306B\u30E1\u30C7\u30A3\u30A2URL\u304C\u3042\u308A\u307E\u305B\u3093\u3002")}function le(t){T("#preview-loading").hidden=!t,T("#results").hidden=t,T(".output").setAttribute("aria-busy",String(t))}function Ot({output:t,error:e}){le(!1);let o=T("#results");if(o.replaceChildren(),e){o.append(F("p",e,"error")),W(e,!0);return}if(t?.text&&(o.append(F("pre",String(t.text),"text-result")),I.task==="chat")){ue.push({role:"user",content:Te},{role:"assistant",content:String(t.text)});let i=T("#chat-history");i.replaceChildren();for(let a of ue.slice(-8))i.append(F("p",(a.role==="user"?"\u3042\u306A\u305F: ":"AI: ")+a.content))}let s=Array.isArray(t?.files)?t.files:Array.isArray(t?.images)?t.images:[];for(let i of s)try{let a=vt(i),n=document.createElement("article");n.className="result-card",n.append(F("h3",i.filename||"\u751F\u6210\u7D50\u679C"));let l;i.mime_type?.startsWith("image/")?(l=document.createElement("img"),l.alt=i.filename):i.mime_type?.startsWith("video/")?(l=document.createElement("video"),l.controls=!0,l.playsInline=!0):i.mime_type?.startsWith("audio/")&&(l=document.createElement("audio"),l.controls=!0),l&&(l.src=a,n.append(l));let d=document.createElement("a");d.href=a,d.download=i.filename||"output",d.textContent="\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9",d.rel="noreferrer",n.append(d),i.size&&n.append(F("small",(i.size/1048576).toFixed(2)+" MiB")),o.append(n)}catch(a){o.append(F("p",a.message,"error"))}!s.length&&!t?.text&&o.append(F("p","\u751F\u6210\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002")),W("\u751F\u6210\u7D50\u679C\u3092\u53D7\u3051\u53D6\u308A\u307E\u3057\u305F\u3002")}T("#title").textContent=I.title;T("#description").textContent=I.description;document.title=I.title+" \xB7 GPU App";var Ne=T("#mode");for(let t of I.modes){let e=document.createElement("option");e.value=t.id,e.textContent=t.label,Ne.append(e)}Ne.onchange=()=>gt(Ne.value);T(".top-panel").hidden=I.modes.length<2;T("#app-form").onsubmit=t=>t.preventDefault();I.modes.length&&st();var Nt=I.task==="irodori"?Tt():I.task==="chat"?It():I.task==="dialogue"?xt():dt;if(window.appSdk){let t=window.appSdk,e=document.createElement("dialog");e.id="app-run-confirm",e.setAttribute("aria-labelledby","app-run-confirm-title"),e.setAttribute("aria-describedby","app-run-confirm-message"),e.append(F("h2","GPU\u3067\u5B9F\u884C\u3057\u307E\u3059\u304B\uFF1F")),e.firstElementChild.id="app-run-confirm-title";let o=F("p","\u5B9F\u884C\u4E2D\u306F\u753B\u9762\u9589\u3058\u305A\u306B\u304A\u5F85\u3061\u304F\u3060\u3055\u3044");o.id="app-run-confirm-message",e.append(o);let s=document.createElement("div");s.className="app-confirm-actions";let i=document.createElement("button");i.type="button",i.textContent="\u30AD\u30E3\u30F3\u30BB\u30EB",i.autofocus=!0,i.onclick=()=>e.close();let a=document.createElement("button");a.type="button",a.id="app-run-approve",a.textContent="\u5B9F\u884C\u3059\u308B",s.append(i,a),e.append(s),document.body.append(e);let n=document.createElement("style");n.textContent="#app-run-confirm{box-sizing:border-box;width:min(440px,calc(100% - 32px));padding:24px;border:1px solid #35455d;border-radius:16px;background:#141c29;color:#eef3ff}#app-run-confirm::backdrop{background:rgba(0,0,0,.65)}#app-run-confirm h2{font-size:20px;margin:0 0 16px}#app-run-confirm p{line-height:1.7}.app-confirm-actions{display:flex;justify-content:flex-end;gap:12px;margin-top:24px}.app-confirm-actions button{padding:12px 18px;border:1px solid #43526a;border-radius:10px;background:#263449;color:#fff;font-size:16px;cursor:pointer}#app-run-approve{background:#2865df}",document.head.append(n);let l,d=async()=>{try{if(!t.getTokenBalance)throw Error("SDK update required");let r=await t.getTokenBalance();T("#token-balance").textContent="保有: "+(r.balance+(Number(r.reservedTokens)||0))+" \u30C8\u30FC\u30AF\u30F3"+(r.reservedTokens?"\uFF08\u5B9F\u884C\u7528\u306B "+r.reservedTokens+" \u78BA\u4FDD\u4E2D\uFF09":"")}catch{T("#token-balance").textContent="\u30C8\u30FC\u30AF\u30F3\u6B8B\u9AD8\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093"}},_=(r,f=0)=>{clearTimeout(l),r.status==="settled"?(T("#token-usage").textContent="\u4ECA\u56DE\u306E\u6D88\u8CBB: "+r.consumedTokens+" \u30C8\u30FC\u30AF\u30F3",d()):(T("#token-usage").textContent="\u6D88\u8CBB\u30C8\u30FC\u30AF\u30F3\u3092\u7CBE\u7B97\u4E2D\u2026",t.getUsage&&f<20&&(l=setTimeout(()=>{t.getUsage(r.jobId).then(y=>_(y,f+1)).catch(()=>{T("#token-usage").textContent="\u7CBE\u7B97\u7D50\u679C\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u518D\u8868\u793A\u3057\u3066\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"})},3e3)))};t.onUsage?.(r=>_(r)),d(),t.onGenerate(Nt),t.onResult(Ot),t.onStatus(r=>{le(["collecting","preparing_input","uploading","starting","queued","running"].includes(r.phase)),T("#app-execute").disabled=!r.canRun,a.disabled=!r.canRun,!r.canRun&&e.open&&e.close(),W(r.message,r.phase==="failed");let f=T("#transfer-progress");f.hidden=!r.progress,r.progress&&(f.max=r.progress.totalBytes||1,f.value=r.progress.bytes||0)}),T("#app-execute").onclick=()=>{e.open||e.showModal()},a.onclick=()=>{a.disabled||!e.open||(e.close(),le(!0),T("#token-usage").textContent="",t.run().catch(r=>W(r.message,!0)).finally(()=>{le(!1),d()}))}}else W("\u3053\u306Eindex.html\u306FGPU App\u304B\u3089\u958B\u304F\u3068\u5B9F\u884C\u3067\u304D\u307E\u3059\u3002");})();
|
| 2 |
+
|
| 3 |
+
;/* media selection removal */
|
| 4 |
+
(()=>{var c=new WeakMap;function u(e){if(c.has(e))return;let n=document.createElement("div");n.className="media-clear-actions",e.insertAdjacentElement("afterend",n);let t=()=>{n.replaceChildren();let r=Array.from(e.files||[]);n.hidden=r.length===0,r.forEach((s,b)=>{let d=document.createElement("div");d.className="media-clear-row";let l=document.createElement("span");l.textContent=s.name;let a=document.createElement("button");a.type="button",a.className="media-clear-button",a.textContent="\xD7",a.title="\u7D20\u6750\u3092\u89E3\u9664",a.setAttribute("aria-label",s.name+"\u3092\u89E3\u9664"),a.disabled=e.disabled,a.addEventListener("click",()=>{if(e.disabled)return;let m=Array.from(e.files||[]).filter((i,o)=>o!==b);if(m.length){let i=new DataTransfer;m.forEach(o=>i.items.add(o)),e.files=i.files}else e.value="";e.setCustomValidity(""),e.dispatchEvent(new Event("change",{bubbles:!0})),t(),(n.querySelector("button")||e).focus()}),d.append(l,a),n.append(d)})};c.set(e,t),e.addEventListener("change",()=>queueMicrotask(t)),t()}function f(){if(document.getElementById("media-clear-style"))return;let e=document.createElement("style");e.id="media-clear-style",e.textContent=".media-clear-actions{display:grid;gap:6px;min-width:0;margin-top:6px}.media-clear-actions[hidden]{display:none!important}.media-clear-row{display:flex;align-items:center;gap:8px;min-width:0}.media-clear-row>span{flex:1;min-width:0;overflow-wrap:anywhere;font-size:12px;color:var(--muted,#63776a)}button.media-clear-button{flex:none;width:36px;min-width:36px;height:36px;min-height:36px;margin:0;padding:0;display:inline-flex;align-items:center;justify-content:center;border:1px solid #bed0c4;border-radius:8px;background:#fff;color:#245b43;font:24px/1 system-ui;cursor:pointer}button.media-clear-button:hover{background:#edf4ef}button.media-clear-button:disabled{opacity:.45;cursor:default}",document.head.append(e);let n=t=>{(t instanceof Element||t instanceof Document)&&(t instanceof HTMLInputElement&&t.type==="file"&&u(t),t.querySelectorAll('input[type="file"]').forEach(u))};n(document),new MutationObserver(t=>{for(let r of t)r.type==="attributes"?c.get(r.target)?.():r.addedNodes.forEach(n)}).observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["disabled"]})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",f,{once:!0}):f();})();
|
bernini_continuation.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import base64
|
| 4 |
+
import re
|
| 5 |
+
import subprocess
|
| 6 |
+
import tempfile
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
_DEADLINE = None
|
| 12 |
+
|
| 13 |
+
VIDEO_SUFFIXES = {'.mp4', '.mov', '.mkv', '.webm', '.avi', '.m4v'}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _safe_name(value: object, fallback: str) -> str:
|
| 17 |
+
name = str(value or fallback).replace('\\', '/').split('/')[-1]
|
| 18 |
+
name = re.sub(r'[^A-Za-z0-9._-]+', '_', name).strip('._')
|
| 19 |
+
return (name or fallback)[:160]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _decode(value: object, label: str) -> bytes:
|
| 23 |
+
encoded = str(value or '').strip()
|
| 24 |
+
if encoded.startswith('data:') and ',' in encoded:
|
| 25 |
+
encoded = encoded.split(',', 1)[1]
|
| 26 |
+
encoded = re.sub(r'\s+', '', encoded).replace('-', '+').replace('_', '/')
|
| 27 |
+
encoded += '=' * ((4 - len(encoded) % 4) % 4)
|
| 28 |
+
try:
|
| 29 |
+
data = base64.b64decode(encoded, validate=True)
|
| 30 |
+
except Exception as exc:
|
| 31 |
+
raise ValueError(f'{label} is not valid base64') from exc
|
| 32 |
+
if not data:
|
| 33 |
+
raise ValueError(f'{label} is empty')
|
| 34 |
+
return data
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _run(command: list[str], label: str, timeout: int) -> None:
|
| 38 |
+
if _DEADLINE is not None: timeout = _DEADLINE.remaining(timeout)
|
| 39 |
+
completed = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=timeout, check=False)
|
| 40 |
+
if completed.returncode:
|
| 41 |
+
detail = (completed.stderr or completed.stdout or '').strip()[-2000:]
|
| 42 |
+
raise RuntimeError(f'{label} failed: {detail}')
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def validate_continuation(value: object) -> dict[str, Any] | None:
|
| 46 |
+
if value is None or value is False:
|
| 47 |
+
return None
|
| 48 |
+
if not isinstance(value, dict):
|
| 49 |
+
raise ValueError('continuation must be an object')
|
| 50 |
+
if value.get('enabled') is False:
|
| 51 |
+
return None
|
| 52 |
+
source_name = _safe_name(value.get('source_video_name'), 'bernini_source_video.mp4')
|
| 53 |
+
frame_name = _safe_name(value.get('last_frame_name'), 'bernini_continuation_last_frame.png')
|
| 54 |
+
if Path(source_name).suffix.lower() not in VIDEO_SUFFIXES:
|
| 55 |
+
raise ValueError('continuation source must be a video file')
|
| 56 |
+
if Path(frame_name).suffix.lower() != '.png':
|
| 57 |
+
raise ValueError('continuation last frame must be a PNG file')
|
| 58 |
+
try:
|
| 59 |
+
width = int(value.get('width'))
|
| 60 |
+
height = int(value.get('height'))
|
| 61 |
+
fps = int(value.get('fps', 20))
|
| 62 |
+
drop = int(value.get('drop_generated_frames', 2))
|
| 63 |
+
target_value = value.get('target_duration_seconds')
|
| 64 |
+
target_duration = float(target_value) if target_value is not None else None
|
| 65 |
+
except (TypeError, ValueError) as exc:
|
| 66 |
+
raise ValueError('invalid continuation settings') from exc
|
| 67 |
+
if not 256 <= width <= 2048 or not 256 <= height <= 2048 or width % 2 or height % 2:
|
| 68 |
+
raise ValueError('invalid continuation dimensions')
|
| 69 |
+
if not 1 <= fps <= 60 or not 1 <= drop <= 10:
|
| 70 |
+
raise ValueError('invalid continuation frame settings')
|
| 71 |
+
if target_duration is not None and not 1 <= target_duration <= 3600:
|
| 72 |
+
raise ValueError('invalid continuation target duration')
|
| 73 |
+
return {'source_video_name': source_name, 'last_frame_name': frame_name, 'width': width, 'height': height, 'fps': fps, 'drop_generated_frames': drop, 'target_duration_seconds': target_duration}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def prepare_continuation_assets(payload: dict[str, Any]) -> dict[str, Any] | None:
|
| 77 |
+
config = validate_continuation(payload.get('continuation'))
|
| 78 |
+
if config is None:
|
| 79 |
+
return None
|
| 80 |
+
assets = payload.get('assets')
|
| 81 |
+
if not isinstance(assets, list):
|
| 82 |
+
raise ValueError('continuation assets are missing')
|
| 83 |
+
source = next((item for item in assets if isinstance(item, dict) and item.get('name') == config['source_video_name']), None)
|
| 84 |
+
if source is None:
|
| 85 |
+
raise ValueError('continuation source video was not supplied')
|
| 86 |
+
source_bytes = _decode(source.get('data'), config['source_video_name'])
|
| 87 |
+
suffix = Path(config['source_video_name']).suffix.lower()
|
| 88 |
+
with tempfile.TemporaryDirectory(prefix='video-continuation-frame-') as temp_dir:
|
| 89 |
+
root = Path(temp_dir)
|
| 90 |
+
source_path = root / ('source' + suffix)
|
| 91 |
+
output_path = root / config['last_frame_name']
|
| 92 |
+
source_path.write_bytes(source_bytes)
|
| 93 |
+
resize = f"scale={config['width']}:{config['height']}:force_original_aspect_ratio=decrease,pad={config['width']}:{config['height']}:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1"
|
| 94 |
+
errors = []
|
| 95 |
+
for offset in ('-0.1', '-0.5', '-1.0'):
|
| 96 |
+
output_path.unlink(missing_ok=True)
|
| 97 |
+
try:
|
| 98 |
+
_run(
|
| 99 |
+
['ffmpeg', '-hide_banner', '-loglevel', 'error', '-y', '-sseof', offset,
|
| 100 |
+
'-i', str(source_path), '-map', '0:v:0', '-an', '-frames:v', '1',
|
| 101 |
+
'-vf', resize, str(output_path)],
|
| 102 |
+
'continuation last-frame extraction', 300,
|
| 103 |
+
)
|
| 104 |
+
except (RuntimeError, subprocess.TimeoutExpired) as exc:
|
| 105 |
+
errors.append(str(exc))
|
| 106 |
+
if output_path.is_file() and output_path.stat().st_size > 0:
|
| 107 |
+
break
|
| 108 |
+
else:
|
| 109 |
+
output_path.unlink(missing_ok=True)
|
| 110 |
+
try:
|
| 111 |
+
_run(
|
| 112 |
+
['ffmpeg', '-hide_banner', '-loglevel', 'error', '-y', '-i', str(source_path),
|
| 113 |
+
'-map', '0:v:0', '-an', '-vf', resize, '-fps_mode', 'passthrough',
|
| 114 |
+
'-update', '1', str(output_path)],
|
| 115 |
+
'continuation full-decode fallback', 3600,
|
| 116 |
+
)
|
| 117 |
+
except (RuntimeError, subprocess.TimeoutExpired) as exc:
|
| 118 |
+
errors.append(str(exc))
|
| 119 |
+
if not output_path.is_file() or output_path.stat().st_size <= 0:
|
| 120 |
+
raise RuntimeError('continuation last-frame extraction produced no image: ' + '; '.join(errors)[-2000:])
|
| 121 |
+
assets.append({'name': config['last_frame_name'], 'data': base64.b64encode(output_path.read_bytes()).decode('ascii')})
|
| 122 |
+
return config
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def merge_continuation_output(source_path: Path, generated_path: Path, output_path: Path, config: dict[str, Any]) -> None:
|
| 126 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 127 |
+
resize = f"scale={config['width']}:{config['height']}:force_original_aspect_ratio=decrease,pad={config['width']}:{config['height']}:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1,format=yuv420p"
|
| 128 |
+
filters = (
|
| 129 |
+
f"[0:v]fps={config['fps']},{resize},setpts=PTS-STARTPTS[source];"
|
| 130 |
+
f"[1:v]fps={config['fps']},{resize},trim=start_frame={config['drop_generated_frames']},setpts=PTS-STARTPTS[generated];"
|
| 131 |
+
'[source][generated]concat=n=2:v=1:a=0[video]'
|
| 132 |
+
)
|
| 133 |
+
partial = output_path.with_suffix(output_path.suffix + '.part.mp4')
|
| 134 |
+
_run([
|
| 135 |
+
'ffmpeg', '-hide_banner', '-loglevel', 'error', '-y', '-i', str(source_path), '-i', str(generated_path),
|
| 136 |
+
'-filter_complex', filters, '-map', '[video]', '-map', '0:a?', '-c:v', 'libx264', '-preset', 'fast',
|
| 137 |
+
'-crf', '16', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-b:a', '192k',
|
| 138 |
+
*(['-t', str(config['target_duration_seconds'])] if config['target_duration_seconds'] is not None else []),
|
| 139 |
+
'-movflags', '+faststart', str(partial),
|
| 140 |
+
], 'continuation video merge', 3600)
|
| 141 |
+
if not partial.is_file() or partial.stat().st_size <= 0:
|
| 142 |
+
raise RuntimeError('continuation merge produced no video')
|
| 143 |
+
partial.replace(output_path)
|
catalog_boot.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Start Comfy with HF-provided custom nodes while leaving the image unchanged."""
|
| 2 |
+
import atexit,json,os,subprocess,sys,time,urllib.request
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
PROCESS=None
|
| 5 |
+
|
| 6 |
+
def stop():
|
| 7 |
+
global PROCESS
|
| 8 |
+
if PROCESS and PROCESS.poll() is None:
|
| 9 |
+
PROCESS.terminate()
|
| 10 |
+
try:PROCESS.wait(timeout=3)
|
| 11 |
+
except subprocess.TimeoutExpired:PROCESS.kill()
|
| 12 |
+
PROCESS=None
|
| 13 |
+
|
| 14 |
+
def extra_paths(root):
|
| 15 |
+
paths={'base_path':str(root),'is_default':True}
|
| 16 |
+
if (root/'custom_nodes').is_dir():paths['custom_nodes']='custom_nodes'
|
| 17 |
+
if (root/'models').is_dir():
|
| 18 |
+
for folder in (root/'models').iterdir():
|
| 19 |
+
if folder.is_dir() and folder.name.replace('_','').isalnum():paths[folder.name]='models/'+folder.name
|
| 20 |
+
return paths
|
| 21 |
+
|
| 22 |
+
def initialize():
|
| 23 |
+
global PROCESS
|
| 24 |
+
value=os.environ.get('HF_APP_ROOT')
|
| 25 |
+
if not value:return
|
| 26 |
+
startup_deadline=time.monotonic()+220
|
| 27 |
+
root=Path(value);config=json.loads((root/'app-runtime.json').read_text(encoding='utf-8-sig'))
|
| 28 |
+
if config.get('engine')!='comfyui':return
|
| 29 |
+
if config.get('backend')!='python':raise ValueError('HF custom-node startup requires backend python / engine comfyui')
|
| 30 |
+
work=Path('/workspace/hf-app');user=work/'comfy-user';user.mkdir(parents=True,exist_ok=True)
|
| 31 |
+
extra=work/'hf-extra-paths.yaml'
|
| 32 |
+
wheels=sorted((root/'runtime_wheels').glob('*.whl'))
|
| 33 |
+
packages=work/'python-deps'
|
| 34 |
+
if wheels:
|
| 35 |
+
stamp=json.dumps([(wheel.name,wheel.stat().st_size) for wheel in wheels])
|
| 36 |
+
marker=packages/'.installed-wheels.json'
|
| 37 |
+
if not marker.is_file() or marker.read_text()!=stamp:
|
| 38 |
+
subprocess.run(['/opt/venv/bin/python','-m','pip','install','--disable-pip-version-check','--no-deps','--no-index','--no-compile','--target',str(packages),*[str(wheel) for wheel in wheels]],check=True,timeout=120)
|
| 39 |
+
marker.write_text(stamp)
|
| 40 |
+
sys.path.insert(0,str(packages))
|
| 41 |
+
os.environ['PYTHONPATH']=str(packages)+os.pathsep+os.environ.get('PYTHONPATH','')
|
| 42 |
+
# JSON is a valid YAML document and avoids path quoting ambiguities.
|
| 43 |
+
paths=extra_paths(root)
|
| 44 |
+
mappings={'hf_app':paths}
|
| 45 |
+
if (root/'auxiliary'/'pose').is_dir():
|
| 46 |
+
os.environ['AUX_ANNOTATOR_CKPTS_PATH']=str(root/'auxiliary'/'pose')
|
| 47 |
+
os.environ['AUX_USE_SYMLINKS']='False'
|
| 48 |
+
os.environ['AUX_TEMP_DIR']=str(work/'aux-temp')
|
| 49 |
+
|
| 50 |
+
extra.write_text(json.dumps(mappings))
|
| 51 |
+
entrypoints=[p for p in Path('/opt').glob('*/comfy_main_with_shared_models.py') if p.is_file()]
|
| 52 |
+
if len(entrypoints)!=1:raise RuntimeError('Expected one installed runtime entrypoint')
|
| 53 |
+
arguments=['/opt/venv/bin/python','-u',str(entrypoints[0]),
|
| 54 |
+
'--listen','127.0.0.1','--port','8188','--disable-auto-launch','--disable-metadata',
|
| 55 |
+
'--disable-api-nodes','--log-stdout','--enable-dynamic-vram','--reserve-vram','1',
|
| 56 |
+
'--use-sage-attention','--async-offload','2','--preview-method','none',
|
| 57 |
+
'--enable-triton-backend','--fast','fp16_accumulation',
|
| 58 |
+
'--input-directory','/comfyui/input','--output-directory','/comfyui/output',
|
| 59 |
+
'--temp-directory','/comfyui/temp','--user-directory',str(user),
|
| 60 |
+
'--database-url','sqlite:////workspace/hf-app/comfy-user/comfyui.db','--extra-model-paths-config',str(extra)]
|
| 61 |
+
PROCESS=subprocess.Popen(arguments,cwd='/comfyui')
|
| 62 |
+
# Keep the enclosing v53 child ready/cleanup checks pointed at the real Comfy process.
|
| 63 |
+
main=sys.modules.get('__main__')
|
| 64 |
+
if main and hasattr(main,'COMFY'):main.COMFY=PROCESS
|
| 65 |
+
deadline=min(startup_deadline,time.monotonic()+180)
|
| 66 |
+
while time.monotonic()<deadline:
|
| 67 |
+
if PROCESS.poll() is not None:raise RuntimeError('ComfyUI exited while loading HF runtime extensions')
|
| 68 |
+
try:
|
| 69 |
+
with urllib.request.urlopen('http://127.0.0.1:8188/system_stats',timeout=2) as response:
|
| 70 |
+
if response.status==200:
|
| 71 |
+
print('HF_CATALOG_COMFY_READY',flush=True);return
|
| 72 |
+
except Exception:pass
|
| 73 |
+
time.sleep(.5)
|
| 74 |
+
stop();raise TimeoutError('HF Comfy runtime did not become ready within180seconds')
|
| 75 |
+
|
| 76 |
+
atexit.register(stop)
|
catalog_runtime.py
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generic ComfyUI/Irodori app backend supplied by the HF repository.
|
| 2 |
+
|
| 3 |
+
The fixed v53 image executes this code as uid10001. HTML is hosted separately.
|
| 4 |
+
Only a job-scoped Cloudflare capability is received; no provider keys are needed.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
import base64,hashlib,json,math,mimetypes,os,re,shutil,subprocess,sys,time,urllib.error,urllib.parse,urllib.request,uuid
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
TRUSTED_ORIGIN=os.environ['APP_SERVICE_ORIGIN']
|
| 11 |
+
USER_AGENT='app-HF/1.0'
|
| 12 |
+
INPUT_ROOT=Path('/comfyui/input')
|
| 13 |
+
OUTPUT_ROOT=Path('/comfyui/output')
|
| 14 |
+
TEMP_ROOT=Path('/comfyui/temp')
|
| 15 |
+
CHUNK_BYTES=8*1024*1024
|
| 16 |
+
MAX_ARTIFACT=512*1024*1024
|
| 17 |
+
MAX_TOTAL=2*1024*1024*1024
|
| 18 |
+
MAX_FILES=16
|
| 19 |
+
MEDIA_MARKERS=('loadimage','loadvideo','loadaudio')
|
| 20 |
+
MEDIA_KEYS={'image','images','imagefile','imagepath','video','videos','videofile','videopath','audio','audiofile','audiopath','file','filename','path','filepath','directory','folder','url','uri'}
|
| 21 |
+
MODEL_KEYS={'ckpt_name','unet_name','lora_name','vae_name','clip_name','clip_name1','clip_name2','clip_name3','clip_name4','control_net_name','model_name','text_encoder','text_encoder_name','tiny_vae','mmaudio_model','clip_model','vae_model','synchformer_model'}
|
| 22 |
+
DENIED_MARKERS={'python','shell','terminal','subprocess','executecommand','systemcommand','commandline','comfyinstall','comfymanager'}
|
| 23 |
+
MIMES={'.mp4':'video/mp4','.webm':'video/webm','.mov':'video/quicktime','.mkv':'video/x-matroska','.avi':'video/x-msvideo','.m4v':'video/mp4','.wav':'audio/wav','.flac':'audio/flac','.mp3':'audio/mpeg','.ogg':'audio/ogg','.opus':'audio/ogg','.m4a':'audio/mp4','.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg','.webp':'image/webp','.gif':'image/gif','.avif':'image/avif'}
|
| 24 |
+
|
| 25 |
+
class Deadline:
|
| 26 |
+
def __init__(self,seconds=280):self.end=time.monotonic()+seconds
|
| 27 |
+
def remaining(self,limit=None):
|
| 28 |
+
remaining=self.end-time.monotonic()
|
| 29 |
+
if remaining<=0:raise TimeoutError('Generation and artifact transfer exceeded the 280-second app limit')
|
| 30 |
+
return min(remaining,limit) if limit is not None else remaining
|
| 31 |
+
|
| 32 |
+
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
| 33 |
+
def redirect_request(self,*args,**kwargs):raise ValueError('Media service redirects are not accepted')
|
| 34 |
+
|
| 35 |
+
def trusted_url(value):
|
| 36 |
+
if not isinstance(value,str) or len(value)>4096:raise ValueError('Invalid media service URL')
|
| 37 |
+
parsed=urllib.parse.urlsplit(value)
|
| 38 |
+
if parsed.scheme!='https' or parsed.netloc!=urllib.parse.urlsplit(TRUSTED_ORIGIN).netloc or parsed.username or parsed.password or parsed.fragment:
|
| 39 |
+
raise ValueError('Media URL must use the trusted Cloudflare service origin')
|
| 40 |
+
return value
|
| 41 |
+
|
| 42 |
+
def set_read_timeout(response,deadline):
|
| 43 |
+
remaining=deadline.remaining(25)
|
| 44 |
+
# urllib's read timeout is otherwise reset per read, allowing a slow peer to
|
| 45 |
+
# retain the original timeout after the overall job budget has elapsed.
|
| 46 |
+
sock=getattr(getattr(getattr(response,'fp',None),'raw',None),'_sock',None)
|
| 47 |
+
if sock is not None:sock.settimeout(remaining)
|
| 48 |
+
|
| 49 |
+
def service_error(label,error,deadline):
|
| 50 |
+
details=['HTTP '+str(error.code)]
|
| 51 |
+
# Never reflect capability URLs, HTML, echoed request data, or credentials.
|
| 52 |
+
try:
|
| 53 |
+
set_read_timeout(error,deadline)
|
| 54 |
+
snippet=error.read(8192).decode('utf-8',errors='replace')
|
| 55 |
+
match=re.search(r'(?:error[ _-]*code|cloudflare[ _-]*error)\s*[:=]\s*["\']?(1[0-9]{3})\b',snippet,re.I)
|
| 56 |
+
if match:details.append('Cloudflare '+match.group(1))
|
| 57 |
+
if error.headers.get('cf-mitigated')=='challenge':details.append('Cloudflare challenge')
|
| 58 |
+
ray=error.headers.get('cf-ray','')
|
| 59 |
+
if re.fullmatch(r'[0-9a-fA-F]{8,32}-[A-Z]{3}',ray):details.append('Ray '+ray)
|
| 60 |
+
except Exception:pass
|
| 61 |
+
return RuntimeError(label+' ('+', '.join(details)+')')
|
| 62 |
+
|
| 63 |
+
def trusted_network_test(url,deadline):
|
| 64 |
+
trusted_url(url)
|
| 65 |
+
request=urllib.request.Request(url,headers={'User-Agent':USER_AGENT,'Range':'bytes=0-0','Accept':'application/octet-stream'})
|
| 66 |
+
try:
|
| 67 |
+
with urllib.request.build_opener(NoRedirect()).open(request,timeout=deadline.remaining(25)) as response:
|
| 68 |
+
set_read_timeout(response,deadline);received=len(response.read(1))
|
| 69 |
+
return {'status':response.status,'sample_bytes':received,'content_type':response.headers.get('Content-Type'),'content_length':response.headers.get('Content-Length'),'cache_status':response.headers.get('CF-Cache-Status')}
|
| 70 |
+
except urllib.error.HTTPError as error:raise service_error('Network diagnostic failed',error,deadline) from None
|
| 71 |
+
except urllib.error.URLError:raise RuntimeError('Network diagnostic connection failed') from None
|
| 72 |
+
|
| 73 |
+
def request_json(url,method,body,deadline,headers=None):
|
| 74 |
+
trusted_url(url)
|
| 75 |
+
raw=body if isinstance(body,bytes) else json.dumps(body,separators=(',',':'),allow_nan=False).encode()
|
| 76 |
+
actual={'User-Agent':USER_AGENT,'Content-Type':'application/octet-stream' if isinstance(body,bytes) else 'application/json',**(headers or {})}
|
| 77 |
+
req=urllib.request.Request(url,data=raw,method=method,headers=actual)
|
| 78 |
+
try:
|
| 79 |
+
with urllib.request.build_opener(NoRedirect()).open(req,timeout=deadline.remaining(25)) as response:
|
| 80 |
+
set_read_timeout(response,deadline)
|
| 81 |
+
encoded=response.read(65537)
|
| 82 |
+
if len(encoded)>65536:raise ValueError('Media service JSON response is too large')
|
| 83 |
+
result=json.loads(encoded)
|
| 84 |
+
if not isinstance(result,dict):raise ValueError('Invalid media service response')
|
| 85 |
+
return result
|
| 86 |
+
except urllib.error.HTTPError as error:
|
| 87 |
+
# Capability URLs must never appear in error messages returned to the user.
|
| 88 |
+
raise service_error('Media service request failed',error,deadline) from None
|
| 89 |
+
except urllib.error.URLError as error:
|
| 90 |
+
raise RuntimeError('Media service connection failed: '+str(type(error.reason).__name__)) from None
|
| 91 |
+
|
| 92 |
+
def relative_name(value):
|
| 93 |
+
return isinstance(value,str) and bool(value) and len(value)<=512 and not value.startswith('/') and '\\' not in value and ':' not in value and not any(part in ('','..','.') for part in value.split('/')) and '\x00' not in value
|
| 94 |
+
|
| 95 |
+
def reference_name(value):
|
| 96 |
+
if not isinstance(value,str):return None
|
| 97 |
+
if value.startswith('input/'):value=value[6:]
|
| 98 |
+
if value.endswith(' [input]'):value=value[:-8]
|
| 99 |
+
return value if relative_name(value) else None
|
| 100 |
+
|
| 101 |
+
def safe_cache_path(root,value,directory=False):
|
| 102 |
+
if not relative_name(value):raise ValueError('Invalid cached model path')
|
| 103 |
+
source=Path(root)/value
|
| 104 |
+
# Native HF snapshot files are symlinks into this repository's blob directory.
|
| 105 |
+
resolved=source.resolve();repo=Path(root).parent.parent.resolve()
|
| 106 |
+
if not (resolved.is_relative_to(Path(root).resolve()) or resolved.is_relative_to(repo)):
|
| 107 |
+
raise ValueError('Cached model escaped its repository')
|
| 108 |
+
if not (source.is_dir() if directory else source.is_file()):raise ValueError('Required file is missing from the native HF cache: '+value)
|
| 109 |
+
return source
|
| 110 |
+
|
| 111 |
+
def prepare_files(files,job,deadline):
|
| 112 |
+
if files is None:files=[]
|
| 113 |
+
if not isinstance(files,list) or len(files)>20:raise ValueError('files must contain at most20 items')
|
| 114 |
+
directory=INPUT_ROOT/'hf-jobs'/job
|
| 115 |
+
directory.mkdir(parents=True,exist_ok=False)
|
| 116 |
+
references={};total=0
|
| 117 |
+
try:
|
| 118 |
+
for item in files:
|
| 119 |
+
deadline.remaining()
|
| 120 |
+
if not isinstance(item,dict):raise ValueError('Invalid input file')
|
| 121 |
+
name=item.get('name')
|
| 122 |
+
if not isinstance(name,str) or not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._-]{0,159}',name) or name in references:
|
| 123 |
+
raise ValueError('Input file names must be unique safe basenames')
|
| 124 |
+
if Path(name).suffix.lower() not in MIMES:raise ValueError('Unsupported input media extension: '+name)
|
| 125 |
+
expected=item.get('size');expected_hash=item.get('sha256')
|
| 126 |
+
if expected is not None and (isinstance(expected,bool) or not isinstance(expected,int) or expected<1 or expected>MAX_ARTIFACT):
|
| 127 |
+
raise ValueError('Input file size is invalid or exceeds512MiB')
|
| 128 |
+
if expected_hash is not None and not re.fullmatch(r'[a-f0-9]{64}',str(expected_hash)):
|
| 129 |
+
raise ValueError('Input SHA256 must be lowercase hexadecimal')
|
| 130 |
+
target=directory/(uuid.uuid4().hex+Path(name).suffix.lower())
|
| 131 |
+
digest=hashlib.sha256();size=0
|
| 132 |
+
with target.open('xb') as out:
|
| 133 |
+
if item.get('url'):
|
| 134 |
+
trusted_url(item['url'])
|
| 135 |
+
if expected is None or expected_hash is None:raise ValueError('Staged inputs require size and sha256')
|
| 136 |
+
try:
|
| 137 |
+
request=urllib.request.Request(item['url'],headers={'User-Agent':USER_AGENT,'Accept':'application/octet-stream'})
|
| 138 |
+
response=urllib.request.build_opener(NoRedirect()).open(request,timeout=deadline.remaining(25))
|
| 139 |
+
except urllib.error.HTTPError as error:raise service_error('Input media download failed',error,deadline) from None
|
| 140 |
+
except urllib.error.URLError as error:raise RuntimeError('Input media download connection failed') from None
|
| 141 |
+
with response:
|
| 142 |
+
while True:
|
| 143 |
+
deadline.remaining()
|
| 144 |
+
set_read_timeout(response,deadline)
|
| 145 |
+
chunk=response.read(256*1024)
|
| 146 |
+
if not chunk:break
|
| 147 |
+
size+=len(chunk);total+=len(chunk)
|
| 148 |
+
if size>expected or size>MAX_ARTIFACT or total>MAX_TOTAL:raise ValueError('Input media size exceeds declared or job limits')
|
| 149 |
+
digest.update(chunk);out.write(chunk)
|
| 150 |
+
elif isinstance(item.get('data'),str):
|
| 151 |
+
encoded=item['data'].split(',',1)[-1]
|
| 152 |
+
if len(encoded)>768*1024:raise ValueError('Inline media is too large; stage it through Cloudflare first')
|
| 153 |
+
try:raw=base64.b64decode(encoded,validate=True)
|
| 154 |
+
except ValueError:raise ValueError('Invalid input base64') from None
|
| 155 |
+
size=len(raw);total+=size;digest.update(raw);out.write(raw)
|
| 156 |
+
else:raise ValueError('Input media requires a staged URL')
|
| 157 |
+
if not size or (expected is not None and size!=expected):raise ValueError('Input media size does not match its manifest')
|
| 158 |
+
if expected_hash is not None and digest.hexdigest()!=expected_hash:raise ValueError('Input media SHA256 does not match its manifest')
|
| 159 |
+
references[name]=target.relative_to(INPUT_ROOT).as_posix()
|
| 160 |
+
return references,directory
|
| 161 |
+
except BaseException:
|
| 162 |
+
shutil.rmtree(directory,ignore_errors=True)
|
| 163 |
+
raise
|
| 164 |
+
|
| 165 |
+
def normalize_and_validate(workflow,object_info,references,job):
|
| 166 |
+
if not isinstance(workflow,dict) or not workflow or len(workflow)>512:raise ValueError('workflow must be a non-empty API graph with at most512 nodes')
|
| 167 |
+
workflow=json.loads(json.dumps(workflow,allow_nan=False))
|
| 168 |
+
for node_id,node in workflow.items():
|
| 169 |
+
if not isinstance(node_id,str) or not re.fullmatch(r'[A-Za-z0-9_-]{1,80}',node_id):raise ValueError('Invalid workflow node ID')
|
| 170 |
+
if not isinstance(node,dict) or not isinstance(node.get('inputs'),dict):raise ValueError('Invalid workflow node: '+node_id)
|
| 171 |
+
node_type=node.get('class_type')
|
| 172 |
+
if not isinstance(node_type,str) or node_type not in object_info:raise ValueError('Node type is not installed: '+str(node_type))
|
| 173 |
+
normalized=re.sub('[^a-z0-9]','',node_type.lower())
|
| 174 |
+
if any(marker in normalized for marker in DENIED_MARKERS):raise ValueError('This node cannot run in the shared runtime: '+node_type)
|
| 175 |
+
definitions=object_info[node_type].get('input',{})
|
| 176 |
+
definitions={**definitions.get('required',{}),**definitions.get('optional',{})}
|
| 177 |
+
if object_info[node_type].get('output_node') and 'filename_prefix' in definitions and 'filename_prefix' not in node['inputs']:
|
| 178 |
+
node['inputs']['filename_prefix']='output'
|
| 179 |
+
for key,value in list(node['inputs'].items()):
|
| 180 |
+
lower=str(key).lower()
|
| 181 |
+
if isinstance(value,str) and len(value)>20000:raise ValueError('Workflow text is too long')
|
| 182 |
+
if isinstance(value,list) and len(value)==2 and isinstance(value[0],str) and isinstance(value[1],int):
|
| 183 |
+
if value[0] not in workflow or not 0<=value[1]<=128:raise ValueError('Broken workflow link in node '+node_id)
|
| 184 |
+
if isinstance(value,(int,float)) and not isinstance(value,bool):
|
| 185 |
+
if not math.isfinite(value):raise ValueError('Workflow numbers must be finite')
|
| 186 |
+
limits={'width':8192,'height':8192,'target_width':8192,'target_height':8192,'output_width':8192,'output_height':8192,
|
| 187 |
+
'length':2000,'num_frames':2000,'frames':2000,'video_frames':2000,'frame_count':2000,
|
| 188 |
+
'fps':240,'frame_rate':240,'steps':120,'batch':8,'batch_size':8,'batch_count':8,'duration':300,'duration_seconds':300}
|
| 189 |
+
if lower in limits and not 0<=value<=limits[lower]:raise ValueError('Workflow exceeds '+lower+' limit')
|
| 190 |
+
if (lower in MODEL_KEYS or re.fullmatch(r'lora_\d+',lower)) and isinstance(value,str) and value.lower() not in ('none','disabled'):
|
| 191 |
+
if not relative_name(value):raise ValueError('Unsafe model path in '+node_id)
|
| 192 |
+
definition=definitions.get(key)
|
| 193 |
+
options=definition[0] if isinstance(definition,list) and definition and isinstance(definition[0],list) else None
|
| 194 |
+
if options and value not in options:
|
| 195 |
+
basename=Path(value).name
|
| 196 |
+
matches=[option for option in options if isinstance(option,str) and Path(option).name==basename]
|
| 197 |
+
if len(matches)==1:node['inputs'][key]=matches[0]
|
| 198 |
+
else:raise ValueError('Model is missing from the native HF cache mapping: '+value)
|
| 199 |
+
if lower in ('filename_prefix','subfolder') and isinstance(value,str):
|
| 200 |
+
if value and not relative_name(value):raise ValueError('Unsafe output path')
|
| 201 |
+
node['inputs'][key]='hf-jobs/'+job+'/'+(value or 'output')
|
| 202 |
+
media_key=re.sub('[^a-z0-9]','',lower)
|
| 203 |
+
if any(marker in normalized for marker in MEDIA_MARKERS) and media_key in MEDIA_KEYS and isinstance(value,str):
|
| 204 |
+
source=reference_name(value)
|
| 205 |
+
if source not in references:raise ValueError('Workflow input media was not supplied by this job: '+str(source))
|
| 206 |
+
node['inputs'][key]=references[source]
|
| 207 |
+
return workflow
|
| 208 |
+
|
| 209 |
+
def snapshot_outputs():
|
| 210 |
+
state={}
|
| 211 |
+
for root in (OUTPUT_ROOT,TEMP_ROOT):
|
| 212 |
+
if not root.exists():continue
|
| 213 |
+
for path in root.rglob('*'):
|
| 214 |
+
try:
|
| 215 |
+
resolved=path.resolve()
|
| 216 |
+
if path.is_file() and resolved.is_relative_to(root.resolve()):
|
| 217 |
+
stat=path.stat();state[resolved]=(stat.st_size,stat.st_mtime_ns)
|
| 218 |
+
except OSError:pass
|
| 219 |
+
return state
|
| 220 |
+
|
| 221 |
+
def output_files(history,before):
|
| 222 |
+
paths=[];owned=[];seen=set()
|
| 223 |
+
def visit(value,depth=0):
|
| 224 |
+
if depth>8:return
|
| 225 |
+
if isinstance(value,list):
|
| 226 |
+
for child in value:visit(child,depth+1)
|
| 227 |
+
elif isinstance(value,dict):
|
| 228 |
+
if isinstance(value.get('filename'),str):
|
| 229 |
+
name=value['filename'];subfolder=value.get('subfolder','');kind=value.get('type','output')
|
| 230 |
+
roots={'output':OUTPUT_ROOT,'temp':TEMP_ROOT}
|
| 231 |
+
if kind not in roots or not relative_name(name) or (subfolder and not relative_name(subfolder)):return
|
| 232 |
+
root=roots[kind].resolve();path=(root/subfolder/name).resolve()
|
| 233 |
+
if not path.is_relative_to(root) or not path.is_file() or path in seen:return
|
| 234 |
+
seen.add(path);paths.append(path)
|
| 235 |
+
stat=path.stat()
|
| 236 |
+
if before.get(path)!=(stat.st_size,stat.st_mtime_ns):owned.append(path)
|
| 237 |
+
else:
|
| 238 |
+
for child in value.values():visit(child,depth+1)
|
| 239 |
+
visit(history.get('outputs',{}))
|
| 240 |
+
if not paths:raise RuntimeError('ComfyUI completed without an image, video or audio artifact')
|
| 241 |
+
if len(paths)>MAX_FILES:raise ValueError('Workflow produced more than16 output artifacts')
|
| 242 |
+
return paths,owned
|
| 243 |
+
|
| 244 |
+
def comfy_execution_ms(history):
|
| 245 |
+
"""Use ComfyUI execution lifecycle timestamps; exclude input/output transfer."""
|
| 246 |
+
events={}
|
| 247 |
+
for item in (history.get('status') or {}).get('messages',[]):
|
| 248 |
+
if not isinstance(item,(list,tuple)) or len(item)!=2:continue
|
| 249 |
+
name,data=item
|
| 250 |
+
if name not in ('execution_start','execution_success') or not isinstance(data,dict):continue
|
| 251 |
+
stamp=data.get('timestamp')
|
| 252 |
+
if isinstance(stamp,(int,float)) and not isinstance(stamp,bool) and math.isfinite(stamp):events[name]=(data.get('prompt_id'),stamp)
|
| 253 |
+
start=events.get('execution_start');end=events.get('execution_success')
|
| 254 |
+
if not start or not end or not start[0] or start[0]!=end[0] or end[1]<start[1]:return None
|
| 255 |
+
return math.ceil(end[1]-start[1])
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def run_comfy(workflow,context,deadline):
|
| 259 |
+
identifier=None
|
| 260 |
+
with context.lock:
|
| 261 |
+
execution_started=time.monotonic()
|
| 262 |
+
try:
|
| 263 |
+
result=context.comfy('/prompt',{'prompt':workflow,'client_id':'hf-'+uuid.uuid4().hex})
|
| 264 |
+
identifier=result.get('prompt_id')
|
| 265 |
+
if not isinstance(identifier,str):raise RuntimeError('ComfyUI rejected the workflow: '+json.dumps(result)[:1800])
|
| 266 |
+
while True:
|
| 267 |
+
deadline.remaining(10)
|
| 268 |
+
history=context.comfy('/history/'+identifier).get(identifier)
|
| 269 |
+
if isinstance(history,dict):
|
| 270 |
+
status=history.get('status') or {}
|
| 271 |
+
if status.get('status_str')=='error':raise RuntimeError('ComfyUI execution failed: '+json.dumps(status.get('messages',[]))[:1800])
|
| 272 |
+
if status.get('completed') or status.get('status_str')=='success':
|
| 273 |
+
# Fallback measured inside the lock, before cleanup/transfer.
|
| 274 |
+
return {**history,'_execution_ms':elapsed_ms(execution_started)}
|
| 275 |
+
time.sleep(min(.25,deadline.remaining()))
|
| 276 |
+
except (TimeoutError,KeyboardInterrupt):
|
| 277 |
+
try:context.comfy('/interrupt',{})
|
| 278 |
+
except Exception:pass
|
| 279 |
+
raise
|
| 280 |
+
except urllib.error.HTTPError as error:
|
| 281 |
+
try:message=error.read(16384).decode(errors='replace')
|
| 282 |
+
except Exception:message=''
|
| 283 |
+
raise RuntimeError('ComfyUI rejected the workflow (HTTP '+str(error.code)+'): '+message[:1800]) from None
|
| 284 |
+
finally:
|
| 285 |
+
if identifier:
|
| 286 |
+
try:context.comfy('/history',{'delete':[identifier]})
|
| 287 |
+
except Exception:pass
|
| 288 |
+
|
| 289 |
+
def file_metadata(path,deadline):
|
| 290 |
+
size=path.stat().st_size
|
| 291 |
+
if not 0<size<=MAX_ARTIFACT:raise ValueError('Output artifact must be1byte..512MiB')
|
| 292 |
+
digest=hashlib.sha256()
|
| 293 |
+
with path.open('rb') as source:
|
| 294 |
+
while True:
|
| 295 |
+
deadline.remaining();chunk=source.read(CHUNK_BYTES)
|
| 296 |
+
if not chunk:break
|
| 297 |
+
digest.update(chunk)
|
| 298 |
+
return {'filename':path.name,'mime_type':MIMES.get(path.suffix.lower()) or mimetypes.guess_type(path.name)[0] or 'application/octet-stream',
|
| 299 |
+
'size':size,'sha256':digest.hexdigest()}
|
| 300 |
+
|
| 301 |
+
def upload_outputs(paths,capability,deadline):
|
| 302 |
+
if len(paths)>MAX_FILES or sum(path.stat().st_size for path in paths)>MAX_TOTAL:raise ValueError('Output artifacts exceed the per-job limit')
|
| 303 |
+
if capability:
|
| 304 |
+
trusted_url(capability)
|
| 305 |
+
parsed=urllib.parse.urlsplit(capability)
|
| 306 |
+
if parsed.query or not re.fullmatch(r'/a/[A-Za-z0-9_-]{43}',parsed.path):raise ValueError('Invalid job-scoped artifact capability')
|
| 307 |
+
elif sum(path.stat().st_size for path in paths)>7*1024*1024:
|
| 308 |
+
raise ValueError('A Cloudflare artifact upload capability is required for output larger than7MiB')
|
| 309 |
+
outputs=[]
|
| 310 |
+
for path in paths:
|
| 311 |
+
metadata=file_metadata(path,deadline)
|
| 312 |
+
if not capability:
|
| 313 |
+
outputs.append({**metadata,'data':base64.b64encode(path.read_bytes()).decode()});continue
|
| 314 |
+
response=request_json(capability+'/begin','POST',metadata,deadline)
|
| 315 |
+
identifier=response.get('id')
|
| 316 |
+
if not isinstance(identifier,str) or not re.fullmatch(r'[A-Za-z0-9_-]{8,100}',identifier):raise RuntimeError('Invalid artifact upload ID')
|
| 317 |
+
parts=[]
|
| 318 |
+
with path.open('rb') as source:
|
| 319 |
+
index=0
|
| 320 |
+
while True:
|
| 321 |
+
deadline.remaining();chunk=source.read(CHUNK_BYTES)
|
| 322 |
+
if not chunk:break
|
| 323 |
+
digest=hashlib.sha256(chunk).hexdigest()
|
| 324 |
+
response=request_json(capability+'/'+identifier+'/parts/'+str(index),'PUT',chunk,deadline,{'X-Part-SHA256':digest})
|
| 325 |
+
if response.get('sha256') and response['sha256']!=digest:raise RuntimeError('Artifact part checksum mismatch')
|
| 326 |
+
parts.append({'index':index,'sha256':digest,'size':len(chunk)});index+=1
|
| 327 |
+
completed=request_json(capability+'/'+identifier+'/complete','POST',{'parts':parts,'sha256':metadata['sha256'],'size':metadata['size']},deadline)
|
| 328 |
+
url=completed.get('url');trusted_url(url)
|
| 329 |
+
if completed.get('sha256') and completed['sha256']!=metadata['sha256']:raise RuntimeError('Uploaded artifact checksum mismatch')
|
| 330 |
+
if completed.get('size') is not None and completed['size']!=metadata['size']:raise RuntimeError('Uploaded artifact size mismatch')
|
| 331 |
+
outputs.append({**metadata,'url':url,'expires_at':completed.get('expires_at')})
|
| 332 |
+
return outputs
|
| 333 |
+
|
| 334 |
+
def finite_number(body,key,default,minimum,maximum,integer=False):
|
| 335 |
+
raw=body.get(key,default)
|
| 336 |
+
if isinstance(raw,bool):raise ValueError(key+' must be numeric')
|
| 337 |
+
try:value=float(raw)
|
| 338 |
+
except (TypeError,ValueError):raise ValueError(key+' must be numeric') from None
|
| 339 |
+
if not math.isfinite(value) or not minimum<=value<=maximum or (integer and value!=int(value)):
|
| 340 |
+
raise ValueError(key+' must be between'+str(minimum)+' and'+str(maximum))
|
| 341 |
+
return int(value) if integer else value
|
| 342 |
+
|
| 343 |
+
def run_irodori(payload,context,references,job,deadline):
|
| 344 |
+
config=json.loads((context.root/'app-runtime.json').read_text(encoding='utf-8-sig')).get('irodori')
|
| 345 |
+
if not isinstance(config,dict):raise ValueError('HF app is missing its irodori model configuration')
|
| 346 |
+
text=payload.get('text');caption=payload.get('caption','')
|
| 347 |
+
if not isinstance(text,str) or not text.strip() or len(text)>1000:raise ValueError('Irodori text must contain1..1000 characters')
|
| 348 |
+
if not isinstance(caption,str) or len(caption)>2000:raise ValueError('Irodori caption exceeds2000 characters')
|
| 349 |
+
checkpoint=safe_cache_path(context.root,config.get('checkpoint'))
|
| 350 |
+
codec=safe_cache_path(context.root,config.get('codec'))
|
| 351 |
+
tokenizer=safe_cache_path(context.root,config.get('tokenizer','tokenizer'),directory=True)
|
| 352 |
+
if not (tokenizer/'tokenizer_config.json').is_file():raise ValueError('Irodori tokenizer_config.json is missing')
|
| 353 |
+
directory=OUTPUT_ROOT/'hf-jobs'/job;directory.mkdir(parents=True,exist_ok=True)
|
| 354 |
+
output=directory/'irodori.wav'
|
| 355 |
+
reference=payload.get('reference_audio')
|
| 356 |
+
many=payload.get('reference_audios')
|
| 357 |
+
if reference is not None and many is not None:raise ValueError('Use one reference representation')
|
| 358 |
+
selected=many if many is not None else ([reference] if reference else [])
|
| 359 |
+
if not isinstance(selected,list) or len(selected)>20 or any(name not in references for name in selected):raise ValueError('Irodori reference audio was not supplied by this job')
|
| 360 |
+
if sum((INPUT_ROOT/references[name]).stat().st_size for name in selected)>20*1024*1024:raise ValueError('Irodori reference audio exceeds20MiB combined')
|
| 361 |
+
request={'checkpoint':str(checkpoint),'codec':str(codec),'tokenizer':str(tokenizer),'output':str(output),
|
| 362 |
+
'text':text.strip(),'caption':caption.strip(),'reference_wav':str(INPUT_ROOT/references[reference]) if reference else None,
|
| 363 |
+
'reference_wavs':[str(INPUT_ROOT/references[name]) for name in many] if many is not None else None,
|
| 364 |
+
'num_steps':finite_number(payload,'num_steps',60,4,80,True),'duration_scale':finite_number(payload,'duration_scale',1,.5,2),
|
| 365 |
+
'cfg_scale_text':finite_number(payload,'cfg_scale_text',3,0,20),'cfg_scale_caption':finite_number(payload,'cfg_scale_caption',3,0,20),
|
| 366 |
+
'cfg_scale_speaker':finite_number(payload,'cfg_scale_speaker',10,0,20),
|
| 367 |
+
'seed':None if payload.get('seed') is None else finite_number(payload,'seed',0,0,2**53-1,True),
|
| 368 |
+
'seconds':None if payload.get('seconds') is None else finite_number(payload,'seconds',5,.5,45)}
|
| 369 |
+
runner=context.root/'irodori_runner.py'
|
| 370 |
+
if not runner.is_file():raise ValueError('irodori_runner.py is missing from the HF app')
|
| 371 |
+
log=directory/'irodori.stderr'
|
| 372 |
+
with log.open('wb') as error_log:
|
| 373 |
+
try:
|
| 374 |
+
result=subprocess.run(['/opt/irodori-venv/bin/python',str(runner)],input=json.dumps(request),text=True,
|
| 375 |
+
stdout=subprocess.PIPE,stderr=error_log,timeout=deadline.remaining(),check=False,
|
| 376 |
+
env={**os.environ,'HF_HUB_OFFLINE':'1','TRANSFORMERS_OFFLINE':'1'})
|
| 377 |
+
except subprocess.TimeoutExpired:raise TimeoutError('Irodori exceeded the remaining280-second app budget') from None
|
| 378 |
+
if result.returncode:
|
| 379 |
+
with log.open('rb') as error_log:error_log.seek(max(0,log.stat().st_size-1600));detail=error_log.read().decode(errors='replace')
|
| 380 |
+
raise RuntimeError('Irodori generation failed: '+detail)
|
| 381 |
+
try:summary=json.loads(result.stdout)
|
| 382 |
+
except ValueError:raise RuntimeError('Irodori returned invalid metadata') from None
|
| 383 |
+
if summary.get('error'):raise RuntimeError(str(summary['error'])[:1600])
|
| 384 |
+
if not output.is_file():raise RuntimeError('Irodori returned no WAV file')
|
| 385 |
+
return [output],summary
|
| 386 |
+
|
| 387 |
+
def cleanup_job(directory,roots):
|
| 388 |
+
if directory is None:return
|
| 389 |
+
resolved=directory.resolve()
|
| 390 |
+
if any(resolved.is_relative_to((root/'hf-jobs').resolve()) and resolved!=(root/'hf-jobs').resolve() for root in roots):
|
| 391 |
+
shutil.rmtree(directory,ignore_errors=True)
|
| 392 |
+
|
| 393 |
+
def elapsed_ms(started):
|
| 394 |
+
return max(0,math.ceil((time.monotonic()-started)*1000))
|
| 395 |
+
|
| 396 |
+
def generate(payload,context):
|
| 397 |
+
if not isinstance(payload,dict):raise ValueError('Generation payload must be an object')
|
| 398 |
+
deadline=Deadline(280);started=time.monotonic();job=uuid.uuid4().hex
|
| 399 |
+
metadata=payload.get('_app') or {}
|
| 400 |
+
if not isinstance(metadata,dict):raise ValueError('Invalid artifact metadata')
|
| 401 |
+
capability=metadata.get('artifact_upload_url')
|
| 402 |
+
if capability:trusted_url(capability)
|
| 403 |
+
references={};input_directory=None;owned=[];additional=[]
|
| 404 |
+
try:
|
| 405 |
+
references,input_directory=prepare_files(payload.get('files'),job,deadline)
|
| 406 |
+
if payload.get('task')=='diagnostics':
|
| 407 |
+
execution_started=time.monotonic()
|
| 408 |
+
result=diagnostics(payload,context)
|
| 409 |
+
result['summary']={'backend':'diagnostics','executed_ms':elapsed_ms(execution_started)}
|
| 410 |
+
return result
|
| 411 |
+
if payload.get('task')=='chat':
|
| 412 |
+
execution_started=time.monotonic()
|
| 413 |
+
result=run_chat(payload,context,deadline)
|
| 414 |
+
result['summary']['executed_ms']=elapsed_ms(execution_started)
|
| 415 |
+
result['summary']['elapsed_seconds']=round(time.monotonic()-started,3)
|
| 416 |
+
return result
|
| 417 |
+
if payload.get('task')=='irodori':
|
| 418 |
+
execution_started=time.monotonic()
|
| 419 |
+
paths,summary=run_irodori(payload,context,references,job,deadline);owned=paths
|
| 420 |
+
summary['executed_ms']=elapsed_ms(execution_started)
|
| 421 |
+
else:
|
| 422 |
+
additional_ms=0
|
| 423 |
+
if payload.get('task')=='dialogue':
|
| 424 |
+
settings=payload.get('irodori')
|
| 425 |
+
if not isinstance(settings,dict):raise ValueError('Dialogue requires Irodori synthesis settings')
|
| 426 |
+
execution_started=time.monotonic()
|
| 427 |
+
audio,tts_summary=run_irodori(settings,context,references,job,deadline)
|
| 428 |
+
additional_ms=elapsed_ms(execution_started)
|
| 429 |
+
tts_summary['executed_ms']=additional_ms
|
| 430 |
+
reference=input_directory/'dialogue-generated.wav'
|
| 431 |
+
shutil.copyfile(audio[0],reference)
|
| 432 |
+
references['dialogue-generated.wav']=reference.relative_to(INPUT_ROOT).as_posix()
|
| 433 |
+
additional.extend(audio);owned.extend(audio)
|
| 434 |
+
continuation=prepare_continuation(payload.get('continuation'),references,input_directory,deadline)
|
| 435 |
+
workflow=payload.get('workflow')
|
| 436 |
+
if workflow is None:
|
| 437 |
+
default=context.root/'workflow.json'
|
| 438 |
+
if not default.is_file():raise ValueError('Provide an API workflow or include workflow.json in the HF app')
|
| 439 |
+
workflow=json.loads(default.read_text(encoding='utf-8-sig'))
|
| 440 |
+
if payload.get('task')=='dialogue':workflow=dialogue_timing(workflow,tts_summary)
|
| 441 |
+
info=context.comfy('/object_info')
|
| 442 |
+
workflow=normalize_and_validate(workflow,info,references,job)
|
| 443 |
+
before=snapshot_outputs();history=run_comfy(workflow,context,deadline)
|
| 444 |
+
paths,new_outputs=output_files(history,before);owned.extend(new_outputs)
|
| 445 |
+
paths,merged=finish_continuation(paths,continuation,job,deadline);owned.extend(merged)
|
| 446 |
+
executed_ms=comfy_execution_ms(history)
|
| 447 |
+
if executed_ms is None:executed_ms=history['_execution_ms']
|
| 448 |
+
paths.extend(additional);summary={'backend':'comfyui','nodes':len(workflow),'executed_ms':executed_ms+additional_ms}
|
| 449 |
+
if additional:summary['irodori']=tts_summary
|
| 450 |
+
if continuation:summary['continuation']=True
|
| 451 |
+
outputs=upload_outputs(paths,capability,deadline)
|
| 452 |
+
return {'files':outputs,'summary':{**summary,'elapsed_seconds':round(time.monotonic()-started,3)}}
|
| 453 |
+
finally:
|
| 454 |
+
cleanup_job(input_directory,(INPUT_ROOT,))
|
| 455 |
+
for path in owned:
|
| 456 |
+
try:
|
| 457 |
+
resolved=path.resolve()
|
| 458 |
+
if any(resolved.is_relative_to(root.resolve()) for root in (OUTPUT_ROOT,TEMP_ROOT)):path.unlink(missing_ok=True)
|
| 459 |
+
except OSError:pass
|
| 460 |
+
for root in (OUTPUT_ROOT,TEMP_ROOT):cleanup_job(root/'hf-jobs'/job,(root,))
|
| 461 |
+
|
| 462 |
+
def run_chat(payload,context,deadline):
|
| 463 |
+
config=json.loads((context.root/'app-runtime.json').read_text(encoding='utf-8-sig')).get('chat') or {}
|
| 464 |
+
model_path=context.root if config.get('model_path','.')=='.' else safe_cache_path(context.root,config['model_path'],directory=True)
|
| 465 |
+
if not (model_path/'config.json').is_file():raise ValueError('Chat model config.json is missing from the native HF cache')
|
| 466 |
+
messages=payload.get('messages')
|
| 467 |
+
if messages is None:
|
| 468 |
+
messages=[]
|
| 469 |
+
if payload.get('system_prompt'):messages.append({'role':'system','content':payload['system_prompt']})
|
| 470 |
+
if isinstance(payload.get('history'),list):messages.extend(payload['history'])
|
| 471 |
+
messages.append({'role':'user','content':payload.get('message','')})
|
| 472 |
+
if not isinstance(messages,list) or not 1<=len(messages)<=64:raise ValueError('Chat requires1..64 messages')
|
| 473 |
+
for message in messages:
|
| 474 |
+
if not isinstance(message,dict) or message.get('role') not in ('system','user','assistant') or not isinstance(message.get('content'),str) or not message['content'].strip() or len(message['content'])>20000:
|
| 475 |
+
raise ValueError('Chat messages require role and nonempty text content')
|
| 476 |
+
request={'model_path':str(model_path),'messages':messages,'max_tokens':finite_number(payload,'max_tokens',512,1,2048,True),
|
| 477 |
+
'temperature':finite_number(payload,'temperature',.78,0,2),'top_p':finite_number(payload,'top_p',.9,.01,1),
|
| 478 |
+
'repetition_penalty':finite_number(payload,'repetition_penalty',1.08,1,2),
|
| 479 |
+
'seed':None if payload.get('seed') is None else finite_number(payload,'seed',0,0,2**53-1,True),
|
| 480 |
+
'generation_seconds':deadline.remaining()}
|
| 481 |
+
directory=Path('/workspace/hf-app');directory.mkdir(parents=True,exist_ok=True)
|
| 482 |
+
log=directory/('chat-'+uuid.uuid4().hex+'.stderr')
|
| 483 |
+
try:
|
| 484 |
+
with log.open('wb') as error_log:
|
| 485 |
+
try:result=subprocess.run(['/opt/venv/bin/python',str(context.root/'chat_runner.py')],input=json.dumps(request),text=True,
|
| 486 |
+
stdout=subprocess.PIPE,stderr=error_log,timeout=deadline.remaining(),check=False,
|
| 487 |
+
env={**os.environ,'HF_HUB_OFFLINE':'1','TRANSFORMERS_OFFLINE':'1'})
|
| 488 |
+
except subprocess.TimeoutExpired:raise TimeoutError('Chat exceeded the remaining280-second app budget') from None
|
| 489 |
+
try:output=json.loads(result.stdout)
|
| 490 |
+
except ValueError:raise RuntimeError('Chat returned invalid JSON') from None
|
| 491 |
+
if result.returncode or output.get('error'):raise RuntimeError('Chat failed: '+str(output.get('error','runtime exited'))[:1600])
|
| 492 |
+
return output
|
| 493 |
+
finally:log.unlink(missing_ok=True)
|
| 494 |
+
|
| 495 |
+
def prepare_continuation(value,references,directory,deadline):
|
| 496 |
+
import bernini_continuation as module
|
| 497 |
+
config=module.validate_continuation(value)
|
| 498 |
+
if config is None:return None
|
| 499 |
+
name=config['source_video_name']
|
| 500 |
+
if name not in references:raise ValueError('Continuation source video was not supplied')
|
| 501 |
+
source=INPUT_ROOT/references[name]
|
| 502 |
+
frame=directory/(uuid.uuid4().hex+'.png')
|
| 503 |
+
resize=f"scale={config['width']}:{config['height']}:force_original_aspect_ratio=decrease,pad={config['width']}:{config['height']}:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1"
|
| 504 |
+
module._DEADLINE=deadline
|
| 505 |
+
errors=[]
|
| 506 |
+
for offset in ('-0.1','-0.5','-1.0'):
|
| 507 |
+
frame.unlink(missing_ok=True)
|
| 508 |
+
try:module._run(['ffmpeg','-hide_banner','-loglevel','error','-y','-sseof',offset,'-i',str(source),'-map','0:v:0','-an','-frames:v','1','-vf',resize,str(frame)],'continuation frame extraction',30)
|
| 509 |
+
except (RuntimeError,subprocess.TimeoutExpired) as error:errors.append(str(error))
|
| 510 |
+
if frame.is_file() and frame.stat().st_size:break
|
| 511 |
+
else:
|
| 512 |
+
frame.unlink(missing_ok=True)
|
| 513 |
+
module._run(['ffmpeg','-hide_banner','-loglevel','error','-y','-i',str(source),'-map','0:v:0','-an','-vf',resize,'-fps_mode','passthrough','-update','1',str(frame)],'continuation full-decode frame extraction',deadline.remaining())
|
| 514 |
+
if not frame.is_file() or not frame.stat().st_size:raise RuntimeError('Continuation produced no reference frame')
|
| 515 |
+
references[config['last_frame_name']]=frame.relative_to(INPUT_ROOT).as_posix()
|
| 516 |
+
return {**config,'source_path':source}
|
| 517 |
+
|
| 518 |
+
def finish_continuation(paths,config,job,deadline):
|
| 519 |
+
if config is None:return paths,[]
|
| 520 |
+
import bernini_continuation as module
|
| 521 |
+
module._DEADLINE=deadline
|
| 522 |
+
outputs=[];created=[]
|
| 523 |
+
for path in paths:
|
| 524 |
+
if path.suffix.lower() not in module.VIDEO_SUFFIXES:outputs.append(path);continue
|
| 525 |
+
target=OUTPUT_ROOT/'hf-jobs'/job/('continued-'+str(len(created))+'.mp4')
|
| 526 |
+
module.merge_continuation_output(config['source_path'],path,target,config)
|
| 527 |
+
outputs.append(target);created.append(target)
|
| 528 |
+
if not created:raise RuntimeError('Continuation workflow produced no video')
|
| 529 |
+
return outputs,created
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
def diagnostics(payload,context):
|
| 533 |
+
"""Bounded, read-only inspection for version/mapping and workflow smoke checks."""
|
| 534 |
+
network=trusted_network_test(payload['network_url'],Deadline(25)) if payload.get('network_url') else None
|
| 535 |
+
if payload.get('network_only'):
|
| 536 |
+
if network is None:raise ValueError('network_only requires a trusted network_url')
|
| 537 |
+
return {'network':network}
|
| 538 |
+
requested=payload.get('requested_class_types',[])
|
| 539 |
+
if not isinstance(requested,list) or len(requested)>128 or any(not isinstance(name,str) or len(name)>160 for name in requested):
|
| 540 |
+
raise ValueError('requested_class_types must contain at most128 node names')
|
| 541 |
+
info=context.comfy('/object_info')
|
| 542 |
+
result={'network':network,'available_node_count':len(info),'missing':[name for name in requested if name not in info], 'nodes':{}}
|
| 543 |
+
for name in requested:
|
| 544 |
+
if name not in info:continue
|
| 545 |
+
node=info[name]
|
| 546 |
+
result['nodes'][name]={'input':node.get('input',{}),'output':node.get('output',[]),'output_node':bool(node.get('output_node'))}
|
| 547 |
+
stats=context.comfy('/system_stats')
|
| 548 |
+
system=stats.get('system',{})
|
| 549 |
+
result['versions']={key:system.get(key) for key in ('comfyui_version','python_version','pytorch_version','embedded_python') if key in system}
|
| 550 |
+
workflow=payload.get('workflow')
|
| 551 |
+
if workflow is not None:
|
| 552 |
+
references={}
|
| 553 |
+
for item in payload.get('files',[]):
|
| 554 |
+
if isinstance(item,dict) and isinstance(item.get('name'),str):references[item['name']]='hf-jobs/diagnostics/'+item['name']
|
| 555 |
+
normalized=normalize_and_validate(workflow,info,references,'diagnostics')
|
| 556 |
+
result['workflow']={'valid':True,'nodes':len(normalized),'model_inputs':{node_id:{key:value for key,value in node['inputs'].items() if key.lower() in MODEL_KEYS or re.fullmatch(r'lora_\d+',key.lower())} for node_id,node in normalized.items()}}
|
| 557 |
+
if len(json.dumps(result,separators=(',',':')).encode())>512*1024:raise ValueError('Requested diagnostic schema exceeds512KiB; request fewer nodes')
|
| 558 |
+
return result
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
def dialogue_timing(workflow,summary):
|
| 562 |
+
"""Preserve the host dialogue graph's speech-duration dependent frame count."""
|
| 563 |
+
if not isinstance(workflow,dict):raise ValueError('Dialogue workflow must be an object')
|
| 564 |
+
workflow=json.loads(json.dumps(workflow,allow_nan=False))
|
| 565 |
+
expected={'796':'mxSlider','535':'LTXVEmptyLatentAudio','542':'PrimitiveFloat','549':'VHS_VideoCombine'}
|
| 566 |
+
if any(workflow.get(key,{}).get('class_type')!=kind for key,kind in expected.items()):
|
| 567 |
+
raise ValueError('Dialogue workflow is missing its duration/frame-rate controls')
|
| 568 |
+
fps=finite_number(workflow['542']['inputs'],'value',24,1,120)
|
| 569 |
+
duration=finite_number(summary,'duration_seconds',0,.001,45)
|
| 570 |
+
seconds=max(3,math.ceil(duration+.25));frames=int(seconds*fps)
|
| 571 |
+
workflow['796']['inputs'].update(Xi=frames,Xf=frames)
|
| 572 |
+
workflow['535']['inputs'].update(frames_number=frames+1,frame_rate=fps)
|
| 573 |
+
workflow['542']['inputs']['value']=fps
|
| 574 |
+
workflow['549']['inputs']['frame_rate']=fps
|
| 575 |
+
return workflow
|
chat_runner.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Local-HF chat adapter matching the original host defaults."""
|
| 2 |
+
from contextlib import redirect_stdout
|
| 3 |
+
import json,sys,time
|
| 4 |
+
|
| 5 |
+
def chat(data):
|
| 6 |
+
import torch
|
| 7 |
+
from transformers import AutoModelForCausalLM,AutoTokenizer,StoppingCriteria,StoppingCriteriaList
|
| 8 |
+
tokenizer=AutoTokenizer.from_pretrained(data['model_path'],local_files_only=True,trust_remote_code=False)
|
| 9 |
+
model=AutoModelForCausalLM.from_pretrained(data['model_path'],torch_dtype='auto',device_map={'':'cuda:0'},low_cpu_mem_usage=True,local_files_only=True,trust_remote_code=False)
|
| 10 |
+
model.eval()
|
| 11 |
+
if data.get('seed') is not None:torch.manual_seed(data['seed'])
|
| 12 |
+
prompt=tokenizer.apply_chat_template(data['messages'],tokenize=False,add_generation_prompt=True)
|
| 13 |
+
encoded=tokenizer(prompt,return_tensors='pt',truncation=True,max_length=4096).to(model.device)
|
| 14 |
+
count=encoded['input_ids'].shape[-1]
|
| 15 |
+
deadline=time.monotonic()+max(1,data.get('generation_seconds',250))
|
| 16 |
+
class StopAtDeadline(StoppingCriteria):
|
| 17 |
+
def __call__(self,input_ids,scores,**kwargs):return time.monotonic()>=deadline
|
| 18 |
+
kwargs={'max_new_tokens':data['max_tokens'],'do_sample':data['temperature']>0,'repetition_penalty':data['repetition_penalty'],
|
| 19 |
+
'stopping_criteria':StoppingCriteriaList([StopAtDeadline()]),'pad_token_id':tokenizer.pad_token_id or tokenizer.eos_token_id}
|
| 20 |
+
if kwargs['do_sample']:kwargs.update(temperature=data['temperature'],top_p=data['top_p'])
|
| 21 |
+
with torch.inference_mode():tokens=model.generate(**encoded,**kwargs)
|
| 22 |
+
generated=tokens[0,count:]
|
| 23 |
+
return {'text':tokenizer.decode(generated,skip_special_tokens=True),'usage':{'prompt_tokens':count,'completion_tokens':len(generated)},
|
| 24 |
+
'summary':{'backend':'transformers','finish_reason':'time_limit' if time.monotonic()>=deadline else 'completed'},'files':[]}
|
| 25 |
+
|
| 26 |
+
if __name__=='__main__':
|
| 27 |
+
try:
|
| 28 |
+
with redirect_stdout(sys.stderr):result=chat(json.load(sys.stdin))
|
| 29 |
+
json.dump(result,sys.stdout,ensure_ascii=False)
|
| 30 |
+
except Exception as error:
|
| 31 |
+
json.dump({'error':str(error)[:1600]},sys.stdout,ensure_ascii=False)
|
| 32 |
+
raise SystemExit(1)
|
generate.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HF app entry point; shared GPU image remains unchanged."""
|
| 2 |
+
from catalog_runtime import generate
|
| 3 |
+
from catalog_boot import initialize
|
| 4 |
+
initialize()
|
index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="ja">
|
| 3 |
+
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="light dark"><title>VideoKarma1</title><link rel="stylesheet" href="./style.css"></head>
|
| 4 |
+
<body><main class="shell">
|
| 5 |
+
<header class="hero"><h1 id="title">Workflow Studio</h1><p id="description"></p></header>
|
| 6 |
+
<div class="workspace"><section class="editor">
|
| 7 |
+
<form id="app-form"><div class="top-panel"><label class="field" id="mode-field"><span>モード</span><select id="mode"></select></label></div>
|
| 8 |
+
<div id="quick-controls"></div><div id="upload-list" class="media-list"></div><div id="workflow-form"></div>
|
| 9 |
+
</form><div class="app-execution"><div class="token-summary" aria-live="polite"><p id="token-balance">トークン残高を確認しています…</p><p id="token-usage"></p></div><button id="app-execute" type="button" disabled>実行する</button><p id="ui-status" class="status" role="status" aria-live="polite">接続を確認しています…</p><progress id="transfer-progress" max="1" value="0" hidden></progress><small>起動・読み込み・実行・停止待ちはGPUの稼働時間に含まれます。タブを閉じても処理はキャンセルされません。</small></div></section>
|
| 10 |
+
<aside class="output"><div class="output-heading"><h2>プレビュー</h2></div><div class="preview-stage"><div id="preview-loading" class="preview-loading" role="status" aria-live="polite" hidden><span class="light-ring" aria-hidden="true"></span><span>生成中…</span></div><div id="results"><div class="empty-state"><span>✧</span><p>実行結果がここに表示されます。</p></div></div></div></aside></div>
|
| 11 |
+
|
| 12 |
+
</main><script id="app-config" type="application/json">{"id":"video-karma","title":"VideoKarma1","description":"画像・テキストから動画","modes":[{"id":"i2v","label":"画像から","workflow":{"84":{"class_type":"CLIPLoader","inputs":{"clip_name":"encoder-01.safetensors","type":"wan","device":"default"},"_meta":{"title":"処理 Text Encoder"}},"85":{"class_type":"KSamplerAdvanced","inputs":{"model":["103",0],"positive":["98",0],"negative":["98",1],"latent_image":["86",0],"add_noise":"disable","noise_seed":0,"steps":4,"cfg":1,"sampler_name":"euler","scheduler":"simple","start_at_step":2,"end_at_step":4,"return_with_leftover_noise":"disable"}},"86":{"class_type":"KSamplerAdvanced","inputs":{"model":["104",0],"positive":["98",0],"negative":["98",1],"latent_image":["98",2],"add_noise":"enable","noise_seed":1,"steps":4,"cfg":1,"sampler_name":"euler","scheduler":"simple","start_at_step":0,"end_at_step":2,"return_with_leftover_noise":"enable"}},"87":{"class_type":"VAEDecode","inputs":{"samples":["85",0],"vae":["90",0]}},"89":{"class_type":"CLIPTextEncode","inputs":{"clip":["84",0],"text":"low quality, blurry, artifacts, watermark, text, zoom in, close-up, crop, face distortion, identity change"},"_meta":{"title":"ネガティブプロンプト"}},"90":{"class_type":"VAELoader","inputs":{"vae_name":"decoder-01.safetensors"},"_meta":{"title":"処理 VAE"}},"93":{"class_type":"CLIPTextEncode","inputs":{"clip":["84",0],"text":"女性が笑顔で笑う"},"_meta":{"title":"ポジティブプロンプト"}},"94":{"class_type":"CreateVideo","inputs":{"images":["87",0],"fps":16}},"95":{"class_type":"UNETLoader","inputs":{"unet_name":"denoiser-01.safetensors","weight_dtype":"default"},"_meta":{"title":"High Noise Model"}},"96":{"class_type":"UNETLoader","inputs":{"unet_name":"denoiser-02.safetensors","weight_dtype":"default"},"_meta":{"title":"Low Noise Model"}},"97":{"class_type":"LoadImage","inputs":{"image":"VideoKarma-input.png"},"_meta":{"title":"入力画像"}},"98":{"class_type":"WanImageToVideo","inputs":{"positive":["93",0],"negative":["89",0],"vae":["90",0],"start_image":["97",0],"width":704,"height":704,"length":81,"batch_size":1}},"101":{"class_type":"LoraLoaderModelOnly","inputs":{"model":["95",0],"lora_name":"adapter-01.safetensors","strength_model":1}},"102":{"class_type":"LoraLoaderModelOnly","inputs":{"model":["96",0],"lora_name":"adapter-02.safetensors","strength_model":1}},"103":{"class_type":"ModelSamplingSD3","inputs":{"model":["102",0],"shift":5}},"104":{"class_type":"ModelSamplingSD3","inputs":{"model":["101",0],"shift":5}},"108":{"class_type":"SaveVideo","inputs":{"video":["94",0],"filename_prefix":"video/VideoKarma_i2v","format":"auto","codec":"auto"}}},"missingNodes":[]},{"id":"t2v","label":"テキストから","workflow":{"84":{"class_type":"CLIPLoader","inputs":{"clip_name":"encoder-01.safetensors","type":"wan","device":"default"},"_meta":{"title":"処理 Text Encoder"}},"85":{"class_type":"KSamplerAdvanced","inputs":{"model":["103",0],"positive":["93",0],"negative":["89",0],"latent_image":["86",0],"add_noise":"disable","noise_seed":0,"steps":4,"cfg":1,"sampler_name":"euler","scheduler":"simple","start_at_step":2,"end_at_step":4,"return_with_leftover_noise":"disable"}},"86":{"class_type":"KSamplerAdvanced","inputs":{"model":["104",0],"positive":["93",0],"negative":["89",0],"latent_image":["wan_t2v_latent",0],"add_noise":"enable","noise_seed":1,"steps":4,"cfg":1,"sampler_name":"euler","scheduler":"simple","start_at_step":0,"end_at_step":2,"return_with_leftover_noise":"enable"}},"87":{"class_type":"VAEDecode","inputs":{"samples":["85",0],"vae":["90",0]}},"89":{"class_type":"CLIPTextEncode","inputs":{"clip":["84",0],"text":"low quality, blurry, artifacts, watermark, text, zoom in, close-up, crop, face distortion, identity change"},"_meta":{"title":"ネガティブプロンプト"}},"90":{"class_type":"VAELoader","inputs":{"vae_name":"decoder-01.safetensors"},"_meta":{"title":"処理 VAE"}},"93":{"class_type":"CLIPTextEncode","inputs":{"clip":["84",0],"text":"女性が笑顔で笑う"},"_meta":{"title":"ポジティブプロンプト"}},"94":{"class_type":"CreateVideo","inputs":{"images":["87",0],"fps":16}},"95":{"class_type":"UNETLoader","inputs":{"unet_name":"denoiser-03.safetensors","weight_dtype":"default"},"_meta":{"title":"High Noise Model"}},"96":{"class_type":"UNETLoader","inputs":{"unet_name":"denoiser-04.safetensors","weight_dtype":"default"},"_meta":{"title":"Low Noise Model"}},"101":{"class_type":"LoraLoaderModelOnly","inputs":{"model":["95",0],"lora_name":"adapter-03.safetensors","strength_model":1}},"102":{"class_type":"LoraLoaderModelOnly","inputs":{"model":["96",0],"lora_name":"adapter-04.safetensors","strength_model":1}},"103":{"class_type":"ModelSamplingSD3","inputs":{"model":["102",0],"shift":5}},"104":{"class_type":"ModelSamplingSD3","inputs":{"model":["101",0],"shift":5}},"108":{"class_type":"SaveVideo","inputs":{"video":["94",0],"filename_prefix":"video/VideoKarma_t2v","format":"auto","codec":"auto"}},"wan_t2v_latent":{"class_type":"EmptyHunyuanLatentVideo","inputs":{"width":704,"height":704,"length":81,"batch_size":1}}},"missingNodes":[]}],"source":"host-current-r2v-deploy"}</script><script src="./app.js" defer></script></body></html>
|
irodori_runner.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import base64
|
| 4 |
+
from contextlib import redirect_stdout
|
| 5 |
+
import io
|
| 6 |
+
import json
|
| 7 |
+
import math
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
import sys
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def load_reference_audio(path):
|
| 13 |
+
# torchaudio.load on the base requires TorchCodec. Decode this local input
|
| 14 |
+
# with libsndfile instead; torchaudio functional resampling remains intact.
|
| 15 |
+
import soundfile as sf
|
| 16 |
+
import torch
|
| 17 |
+
info = sf.info(str(path))
|
| 18 |
+
if not 0 < info.duration <= 120 or not 1 <= info.channels <= 2 or not 8000 <= info.samplerate <= 192000:
|
| 19 |
+
raise ValueError('Reference audio must be 0-120 seconds, mono/stereo, 8-192 kHz')
|
| 20 |
+
audio, rate = sf.read(str(path), dtype='float32', always_2d=True)
|
| 21 |
+
if not len(audio) or not rate or not torch.isfinite(torch.from_numpy(audio)).all():
|
| 22 |
+
raise ValueError('Reference audio is empty or invalid')
|
| 23 |
+
return torch.from_numpy(audio.T.copy()), int(rate)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def synthesize(data):
|
| 27 |
+
import soundfile as sf
|
| 28 |
+
import torch
|
| 29 |
+
sys.path.insert(0, '/opt/irodori-tts')
|
| 30 |
+
from irodori_tts import inference_runtime as runtime_module
|
| 31 |
+
runtime_module._load_audio = load_reference_audio
|
| 32 |
+
runtime_module._resolve_tokenizer_source = lambda checkpoint, fallback: (data['tokenizer'], True)
|
| 33 |
+
checkpoint = Path(data['checkpoint'])
|
| 34 |
+
if not checkpoint.is_file():
|
| 35 |
+
raise ValueError('Irodori checkpoint does not exist')
|
| 36 |
+
reference = data.get('reference_wav')
|
| 37 |
+
references = data.get('reference_wavs')
|
| 38 |
+
if reference and references:
|
| 39 |
+
raise ValueError('Use one reference representation')
|
| 40 |
+
paths = references if references is not None else ([reference] if reference else [])
|
| 41 |
+
if not isinstance(paths, list) or len(paths) > 20 or any(not isinstance(path, str) or not path for path in paths):
|
| 42 |
+
raise ValueError('Invalid reference audio paths')
|
| 43 |
+
total_seconds = 0
|
| 44 |
+
for path in paths:
|
| 45 |
+
info = sf.info(path)
|
| 46 |
+
if not 0 < info.duration <= 120 or not 1 <= info.channels <= 2 or not 8000 <= info.samplerate <= 192000:
|
| 47 |
+
raise ValueError('Reference audio must be 0-120 seconds, mono/stereo, 8-192 kHz')
|
| 48 |
+
total_seconds += info.duration
|
| 49 |
+
if total_seconds > 120:
|
| 50 |
+
raise ValueError('Reference audio must be at most 120 seconds combined')
|
| 51 |
+
decoded = {str(path): load_reference_audio(path) for path in paths}
|
| 52 |
+
if decoded:
|
| 53 |
+
runtime_module._load_audio = lambda path: decoded[str(path)] if str(path) in decoded else load_reference_audio(path)
|
| 54 |
+
has_reference = bool(paths)
|
| 55 |
+
caption = data.get('caption') or None
|
| 56 |
+
runtime, reloaded = runtime_module.get_cached_runtime(runtime_module.RuntimeKey(
|
| 57 |
+
checkpoint=str(checkpoint), model_device='cuda', model_precision='bf16',
|
| 58 |
+
codec_repo=data['codec'], codec_device='cuda',
|
| 59 |
+
codec_precision='fp32', codec_deterministic_encode=True,
|
| 60 |
+
codec_deterministic_decode=True, compile_model=False, compile_dynamic=False,
|
| 61 |
+
))
|
| 62 |
+
if caption and not runtime.model_cfg.use_caption_condition:
|
| 63 |
+
raise ValueError('Selected model does not support voice descriptions')
|
| 64 |
+
if has_reference and not runtime.model_cfg.use_speaker_condition_resolved:
|
| 65 |
+
raise ValueError('Selected model does not support reference audio')
|
| 66 |
+
text_cfg, caption_cfg, speaker_cfg, messages = runtime_module.resolve_cfg_scales(
|
| 67 |
+
cfg_guidance_mode='independent', cfg_scale_text=data['cfg_scale_text'],
|
| 68 |
+
cfg_scale_caption=data['cfg_scale_caption'] if caption else 0,
|
| 69 |
+
cfg_scale_speaker=data['cfg_scale_speaker'] if has_reference else 0,
|
| 70 |
+
cfg_scale=None, use_caption_condition=bool(caption), use_speaker_condition=has_reference,
|
| 71 |
+
)
|
| 72 |
+
request = runtime_module.SamplingRequest(
|
| 73 |
+
text=data['text'], caption=caption, ref_wav=reference, ref_wavs=references, no_ref=not has_reference,
|
| 74 |
+
seconds=data.get('seconds'), duration_scale=data['duration_scale'],
|
| 75 |
+
min_seconds=0.5, max_seconds=45, max_ref_seconds=120,
|
| 76 |
+
num_candidates=1, decode_mode='sequential', num_steps=data['num_steps'],
|
| 77 |
+
seed=data['seed'], cfg_guidance_mode='independent', cfg_scale_text=text_cfg,
|
| 78 |
+
cfg_scale_caption=caption_cfg, cfg_scale_speaker=speaker_cfg,
|
| 79 |
+
context_kv_cache=True, trim_tail=True,
|
| 80 |
+
)
|
| 81 |
+
result = runtime.synthesize(request)
|
| 82 |
+
audio = result.audio.detach().float().cpu().squeeze()
|
| 83 |
+
if audio.ndim != 1 or not audio.numel() or not torch.isfinite(audio).all():
|
| 84 |
+
raise ValueError('Irodori generated invalid audio')
|
| 85 |
+
buffer = io.BytesIO()
|
| 86 |
+
sf.write(buffer, audio.numpy(), int(result.sample_rate), format='WAV', subtype='PCM_16')
|
| 87 |
+
Path(data['output']).write_bytes(buffer.getvalue())
|
| 88 |
+
return {
|
| 89 |
+
'backend': 'irodori',
|
| 90 |
+
'audio_mime': 'audio/wav', 'sample_rate': int(result.sample_rate),
|
| 91 |
+
'duration_seconds': audio.numel() / int(result.sample_rate),
|
| 92 |
+
'seed': int(result.used_seed), 'num_steps': data['num_steps'],
|
| 93 |
+
'model': checkpoint.name, 'runtime_reloaded': bool(reloaded),
|
| 94 |
+
'messages': [*messages, *result.messages],
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
if __name__ == '__main__':
|
| 99 |
+
try:
|
| 100 |
+
with redirect_stdout(sys.stderr):
|
| 101 |
+
result = synthesize(json.load(sys.stdin))
|
| 102 |
+
except Exception as exc:
|
| 103 |
+
result = {'error': str(exc)}
|
| 104 |
+
json.dump(result, sys.stdout, ensure_ascii=False)
|
models/diffusion_models/denoiser-01.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f6a29c51486a2ec073d155ce85e90cc3bf9ee6c726aa87bf3835c0aa63b05de4
|
| 3 |
+
size 14293828968
|
models/diffusion_models/denoiser-02.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:82a642cde3b39eb0c45115d6d89c39d2cbae9ec3309bc9fbd8aab8e5e72b9c8c
|
| 3 |
+
size 14293828968
|
models/diffusion_models/denoiser-03.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cad711ae211c8b23455ec68cd6a190a33a3d874234a77eb57266d73f8f0e6c9f
|
| 3 |
+
size 14293923632
|
models/diffusion_models/denoiser-04.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e71b96d7c82e638694c5e7fb98fac4bfb0e4ddc5fbbb4b1df40da8f0f1278a97
|
| 3 |
+
size 14293923632
|
models/loras/a-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d9931756c202bd8d4946c0d163c1269231a6352b51bb4235f6a19894c9ad8c68
|
| 3 |
+
size 306807976
|
models/loras/a-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:066ee4bfafb685c85f08174c8283cd11bc6d36f4845347f20d633ab44581601f
|
| 3 |
+
size 306807976
|
models/loras/adapter-01.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d176c808d6fc461999b68e321efcb7501b20b8c3797523ed0df14f7d1deff11e
|
| 3 |
+
size 1226977424
|
models/loras/adapter-02.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:024f21de095bc8fad9809ded3e9e49a2e170dcf27075da8145ba7d60d8aab7f9
|
| 3 |
+
size 1226977424
|
models/loras/adapter-03.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:698321cb86bd30c4af06c9b84e656a1048c8cb54e06d50694536fb5de37fde41
|
| 3 |
+
size 1226977424
|
models/loras/adapter-04.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ec95216e614b3c132c11bfb387b11feedf62163150ccc9068bca8a189771e75a
|
| 3 |
+
size 1226977424
|
models/loras/b-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:070fb1144c75e9087dea42035c9af34c654e605df7d6e648c4d2aa0813b2b0fa
|
| 3 |
+
size 163326248
|
models/loras/b-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:facb74f41f2109e44a76fb6ead2420c1dc290eedb96cd9fbb667776eb06a7437
|
| 3 |
+
size 163326248
|
models/loras/c-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0bead24e147726133e8e1b431f4baf5d130505551e9849104d9e4339054dd101
|
| 3 |
+
size 153490536
|
models/loras/c-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7dabb67a1100cd924172280c36615b8e971a5e70f4bfb3690d2a975bdd270919
|
| 3 |
+
size 153490536
|
models/loras/d-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fcb595ca919d7d08e64058a19e42b9ad8678b4bba6ddb0194df758a3c64246e1
|
| 3 |
+
size 153490536
|
models/loras/d-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8aa81c7830896147f879b5ea0d65474361e2c8551699846325ce3a06d415607e
|
| 3 |
+
size 153490536
|
models/loras/e-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e1eb90f7da3e33a2cc3f595bfebdda082153a488206386817a05d319e8170ac0
|
| 3 |
+
size 153490536
|
models/loras/e-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:23bec64d6034da1f37afa297142391068e317045551312bb64f0bf3de1d7c66b
|
| 3 |
+
size 153490536
|
models/loras/f-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8be52e70ae8329b3797bdaaeb52cbfa79ee9d505b1dca9d4bbeda7d21bba3542
|
| 3 |
+
size 163326224
|
models/loras/f-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:611d050a102e0ae199c6cfb178bb6197492f3c9e4e4d274fbfd455297a352b7e
|
| 3 |
+
size 163326224
|
models/loras/g-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d4d033302b54787bbfc163f1c990ecdc9c63d9f147bea9e079a41b4b46a5aeb9
|
| 3 |
+
size 163326224
|
models/loras/g-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:66e61d0815e18dc7d3f6519b28a5929ec183f7f164b4300efb09242cf7832662
|
| 3 |
+
size 163326224
|
models/loras/h-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:70b7a66c7d76e9adcab080ff26551a32b1858c65c520d0aeaa81fc64c8e3b245
|
| 3 |
+
size 163326224
|
models/loras/h-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:66f023ffdb658424895f9524c5703867332b440bdcc51e51cca308650315b7f4
|
| 3 |
+
size 153492896
|
models/loras/i-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2352ce8f95ae162b26a68b7c3b2a625471021ffda3c9b354e33d7de1e74e4bea
|
| 3 |
+
size 153453720
|
models/loras/i-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:167dd683f245c57498b2ba947cda7012286b9aef4d2909906b1dd3a6ed272be7
|
| 3 |
+
size 153453720
|
models/loras/j-high.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c29805bd62cd2fed56b31efc3a9d1a3cc226c8085a8730801e9ab7fa0ace7039
|
| 3 |
+
size 153493152
|
models/loras/j-low.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:388f52187c124abcc0d98c55bf67141226bbb4489f1f80bd5fb0c4d13c424bef
|
| 3 |
+
size 153493144
|
models/text_encoders/encoder-01.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c3355d30191f1f066b26d93fba017ae9809dce6c627dda5f6a66eaa651204f68
|
| 3 |
+
size 6735906897
|
models/vae/decoder-01.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2fc39d31359a4b0a64f55876d8ff7fa8d780956ae2cb13463b0223e15148976b
|
| 3 |
+
size 253815318
|
style.css
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root{color-scheme:dark;font:15px/1.55 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#101116;color:#e7e8ec;--muted:#9ca1b0;--line:#2e313d;--surface:#181a23;--accent:#c2f28a}*{box-sizing:border-box}body{margin:0}button,input,textarea,select{font:inherit}button,a,input,select,textarea,summary{outline-offset:4px}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible{outline:2px solid var(--accent)}.shell{max-width:1480px;padding:32px;margin:auto}.hero{padding:0 0 24px}.eyebrow{color:var(--accent);letter-spacing:.17em;font-size:11px;font-weight:700}h1{font-size:clamp(26px,4vw,40px);line-height:1.1;letter-spacing:-.035em;margin:12px 0}h2{font-size:18px;margin:7px 0 20px}h3{font-size:14px;margin:0 0 5px;font-weight:600;overflow-wrap:anywhere}p{margin:8px 0;color:var(--muted)}.hero>p{max-width:700px}.run-guide{display:inline-block;padding:8px 13px;border:1px solid #40522d;border-radius:8px;color:#d8e9c7!important;font-size:13px}.workspace{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(280px,1fr);gap:24px;align-items:start}.editor{min-width:0}.top-panel,.panel,.node-group,.media-card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:18px;margin-bottom:12px}.top-panel{margin-bottom:14px}.field{display:flex;flex-direction:column;gap:6px;min-width:0}.field>span{color:#bfc3d0;font-size:12px;overflow-wrap:anywhere}.field>input,.field>select,.field>textarea,input[type=number],#workflow-json{width:100%;min-width:0;border:1px solid #363a49;border-radius:7px;background:#11131b;color:#f1f3f8;padding:9px 11px}input[readonly]{color:var(--muted);background:#1c1e29}textarea{resize:vertical;min-height:88px}select{max-width:100%}.check-field{display:flex;align-items:center;gap:9px;flex-direction:row;font-size:13px}.check-field input[type=checkbox]{width:17px;height:17px;accent-color:var(--accent);margin:0}.check-field span{font-size:13px}.fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.field:has(textarea),.field.model-field{grid-column:1/-1}.seed-controls{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;align-items:end;margin-top:15px}.seed-controls>.check-field{padding-bottom:10px}.hint,.node-meta,small{font-size:12px;color:var(--muted);overflow-wrap:anywhere}.node-meta{display:block;margin-bottom:13px}.node-group>summary,.panel>summary{cursor:pointer;font-size:14px;font-weight:600;padding:1px 0}.node-group[open]>summary,.panel[open]>summary{margin-bottom:16px}.node-card{padding:15px 0;border-top:1px solid var(--line)}.node-card:first-of-type{padding-top:0;border-top:0}.node-card:last-child{padding-bottom:0}.node-card>.check-field{margin:12px 0}.media-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.media-card{display:flex;flex-direction:column;gap:12px;margin:0 0 12px;min-width:0}.media-card strong{font-size:13px;overflow-wrap:anywhere}.media-card input{width:100%;font-size:12px}.media-card input::file-selector-button{border:1px solid #47553b;border-radius:5px;background:#263422;color:#d9edc5;padding:7px 9px;margin-right:7px}.media-preview{display:grid;gap:8px}.media-preview:empty{display:none}.media-preview img,.media-preview video{width:100%;height:150px;object-fit:contain;border-radius:6px;background:#101116}.media-preview audio{width:100%;height:38px}.lora-row{display:grid;grid-template-columns:minmax(0,1fr) 75px;gap:10px;border-top:1px solid var(--line);padding:12px 0}.lora-row>.field:first-child{grid-column:1/-1}.lora-row>button{align-self:end}button,.file-action{display:inline-flex;align-items:center;justify-content:center;border:1px solid #434852;border-radius:7px;background:#272b36;color:#e9edf4;padding:8px 12px;cursor:pointer;font-size:12px;font-weight:600}button:hover,.file-action:hover{background:#343b48}button:disabled{opacity:.45;cursor:default}.json-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.file-action input{display:none}#workflow-json{min-height:240px;font:11px/1.5 ui-monospace,monospace}.output{position:sticky;top:16px;background:#151720;border:1px solid var(--line);border-radius:12px;overflow:hidden}.output-heading{padding:22px 22px 0}.empty-state{text-align:center;padding:60px 16px 90px}.empty-state span{font-size:58px;color:#6f8260}.empty-state p{font-size:13px}.result-card{padding:16px 20px;border-top:1px solid var(--line)}.result-card>img,.result-card>video,.result-card>audio{display:block;width:100%;max-height:560px;object-fit:contain;border-radius:7px;margin:12px 0}.result-card>a{display:inline-block;color:var(--accent);font-size:13px;margin-right:12px}.text-result,.summary{white-space:pre-wrap;overflow-wrap:anywhere;padding:18px;font:13px/1.65 ui-monospace,monospace}.error{color:#ffa7a7!important}.status{font-size:12px;padding:10px 4px;white-space:pre-wrap}.output .error{padding:16px}.chat-history{margin:14px 0;padding:12px;border:1px solid var(--line);border-radius:8px;font-size:13px}.chat-history:empty{display:none}footer{font-size:11px;color:#7f8595;margin:24px 0 0}#quick-controls>.panel{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}#workflow-form>.field{margin:12px 0}[hidden]{display:none!important}@media(max-width:850px){.shell{padding:22px 16px}.workspace{grid-template-columns:minmax(0,1fr)}.output{position:static}.output-heading{padding:18px 18px 0}.empty-state{padding:26px 10px 40px}}@media(max-width:480px){.shell{padding:18px 10px}.top-panel,.panel,.node-group,.media-card{padding:14px}.fields{grid-template-columns:minmax(0,1fr)}.media-list{grid-template-columns:minmax(0,1fr)}.seed-controls{grid-template-columns:minmax(0,1fr);gap:9px}.seed-controls>.check-field{padding-bottom:0}#quick-controls>.panel{grid-template-columns:minmax(0,1fr)}}
|
| 2 |
+
.basic-controls{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:18px}.basic-controls:empty{display:none}.basic-controls>.field:has(textarea){grid-column:1/-1}#dialogue-controls{display:grid;gap:14px}#workflow-form>.field{padding:4px 0}.hero{padding-bottom:16px}.hero h1{font-size:28px}.hero p{font-size:13px}.media-list:empty{display:none}@media(max-width:480px){.basic-controls{padding:14px;gap:14px}}
|
| 3 |
+
|
| 4 |
+
.basic-controls{grid-template-columns:minmax(0,1fr)}.basic-prompts{display:grid;gap:14px}.basic-prompts:empty,.basic-controls>.fields:empty{display:none}
|
| 5 |
+
|
| 6 |
+
/* This layout belongs to this example repository, not the GPU service. */
|
| 7 |
+
html{background:#f4f6f4;color-scheme:light}body{max-width:none;margin:0;padding:24px;background:#f4f6f4;color:#223a30;font-family:system-ui,sans-serif;--surface:#fff;--line:#d8e3db;--muted:#63776a;--accent:#286645}
|
| 8 |
+
.shell{max-width:none;width:100%;padding:0;margin:0}.workspace{display:grid;grid-template-columns:minmax(320px,.9fr) minmax(0,1.4fr);gap:24px;align-items:start}.editor{min-width:0}.output{min-width:0;background:white;border:1px solid #d8e3db;border-radius:14px;position:sticky;top:20px;overflow:hidden;min-height:440px}.output-heading h2{font-size:17px;color:#284c35}.field>input,.field>select,.field>textarea,input[type=number],#workflow-json{background:white;color:#223a30;border-color:#bed0c4}.field>span{color:#456450}.hero h1{color:#263f30}.hero p{color:#627b68}
|
| 9 |
+
.app-execution{margin-top:22px;padding-top:16px;border-top:1px solid #d6e2d8}.app-execution button{display:block;width:100%;min-height:50px;border:0;border-radius:10px;background:#245b43;color:white;font:700 15px system-ui;cursor:pointer;opacity:1}.app-execution button:disabled{background:#dce5de;color:#526757;cursor:default}.app-execution button:focus-visible{outline:3px solid #59aa7b;outline-offset:3px}.app-execution small{font:11px/1.8 system-ui;color:#6b7d71;display:block;margin-top:10px}.app-execution progress{width:100%;margin:10px 0}.output img,.output video{max-width:100%;object-fit:contain}@media(max-width:760px){body{padding:16px}.workspace{grid-template-columns:minmax(0,1fr);gap:20px}.output{position:static;min-height:260px}}
|
| 10 |
+
|
| 11 |
+
.token-summary{padding:10px 13px;border:1px solid #47556955;border-radius:8px;margin-bottom:12px;font-size:13px}.token-summary p{margin:4px 0}
|
| 12 |
+
@media (max-width:850px),(pointer:coarse){input,select,textarea,.media-card input,#workflow-json{font-size:16px}}
|
| 13 |
+
|
| 14 |
+
.preview-stage{position:relative;min-height:340px}.preview-loading{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:24px;color:#487ba7;font-size:13px;letter-spacing:.12em;background:radial-gradient(circle at center,#e5f3ff 0,transparent 65%)}.light-ring{display:block;width:84px;height:84px;border-radius:50%;border:3px solid #d9ebfa;border-top-color:#249dff;border-right-color:#74caff;box-shadow:0 0 18px #55b7ff55,inset 0 0 13px #55b7ff33;animation:preview-orbit 1s linear infinite}@keyframes preview-orbit{to{transform:rotate(360deg)}}@media(max-width:760px){.preview-stage{min-height:220px}.light-ring{width:68px;height:68px}}@media(prefers-reduced-motion:reduce){.light-ring{animation:none}}
|
| 15 |
+
@media(max-width:760px){.output{margin-bottom:24px}}
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
/* LoRA controls: readable labels and full-width touch sliders. */
|
| 21 |
+
.video-loras{margin:16px 0;padding:16px;border:1px solid #bed0c4;border-radius:14px;min-width:0;background:#fff}
|
| 22 |
+
.video-loras legend{padding:0 8px;font-size:17px;font-weight:700;color:#223a30}
|
| 23 |
+
.video-loras>.hint{margin:0 0 14px;font-size:13px;line-height:1.7;color:#52695b}
|
| 24 |
+
.video-lora-control{display:grid;grid-template-columns:minmax(0,1fr) 88px;gap:8px 12px;align-items:center;margin:10px 0;padding:12px 14px;background:#f4f8f5;border:1px solid #d8e3db;border-radius:12px;min-width:0}
|
| 25 |
+
.video-lora-control label{grid-column:1;grid-row:1;font-size:18px;font-weight:700;color:#223a30;overflow-wrap:anywhere}
|
| 26 |
+
.video-lora-control input{min-width:0;width:100%;box-sizing:border-box}
|
| 27 |
+
.video-lora-control input[type=number]{grid-column:2;grid-row:1;min-height:44px;font-size:18px;font-weight:600;text-align:center;border:1px solid #9bb6a5;border-radius:8px;background:#fff;color:#223a30;padding:8px;font-variant-numeric:tabular-nums}
|
| 28 |
+
.video-lora-control input[type=range]{grid-column:1/-1;grid-row:2;appearance:none;-webkit-appearance:none;height:48px;margin:0;padding:0;background:transparent;border:0;border-radius:8px;cursor:pointer;touch-action:pan-y;accent-color:#286645}
|
| 29 |
+
.video-lora-control input[type=range]::-webkit-slider-runnable-track{height:10px;background:#c1d8c9;border-radius:999px}
|
| 30 |
+
.video-lora-control input[type=range]::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:32px;height:32px;margin-top:-11px;border:3px solid white;border-radius:50%;background:#286645;box-shadow:0 1px 5px #153e3540}
|
| 31 |
+
.video-lora-control input[type=range]::-moz-range-track{height:10px;background:#c1d8c9;border-radius:999px}
|
| 32 |
+
.video-lora-control input[type=range]::-moz-range-thumb{box-sizing:border-box;width:32px;height:32px;border:3px solid white;border-radius:50%;background:#286645;box-shadow:0 1px 5px #153e3540}
|
| 33 |
+
.video-lora-control input:focus-visible{outline:3px solid #59aa7b;outline-offset:3px}
|
| 34 |
+
.video-loras>button{min-height:44px;margin-top:8px;background:#fff;color:#286645;border:1px solid #9bb6a5;font-size:14px}
|
| 35 |
+
@media(max-width:480px){.video-loras{padding:12px}.video-lora-control{padding:10px 12px}.video-loras>button{width:100%}}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
.wan-lora-settings{margin:16px 0}.wan-lora-settings>summary{padding:12px 14px;min-height:48px;cursor:pointer;border:1px solid #bed0c4;border-radius:12px;background:#fff;color:#223a30;font-size:16px;font-weight:600}.wan-lora-settings[open]>summary{margin-bottom:10px}.wan-lora-settings>.video-loras{margin:0}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
/* Compact LoRA controls. */
|
| 43 |
+
.video-loras{padding:10px}
|
| 44 |
+
.video-loras>.hint{font-size:12px;margin-bottom:8px}
|
| 45 |
+
.video-lora-control{grid-template-columns:minmax(0,1fr) 72px;gap:0 10px;padding:6px 10px;margin:6px 0;border-radius:8px}
|
| 46 |
+
.video-lora-control>label{grid-column:1;grid-row:1;font-size:14px;font-weight:600}
|
| 47 |
+
.video-lora-control>input[type=number]{grid-column:2;grid-row:1;min-height:32px;height:32px;padding:3px 6px;font-size:16px;font-weight:400;appearance:textfield;-moz-appearance:textfield}
|
| 48 |
+
.video-lora-control>input[type=number]::-webkit-inner-spin-button,.video-lora-control>input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
|
| 49 |
+
.video-lora-control>input[type=range]{grid-column:1/-1;grid-row:2;height:36px;touch-action:none}
|
| 50 |
+
.video-lora-control>input[type=range]::-webkit-slider-runnable-track{height:6px;background:linear-gradient(to right,#286645 var(--lora-fill,0%),#c1d8c9 var(--lora-fill,0%))}
|
| 51 |
+
.video-lora-control>input[type=range]::-webkit-slider-thumb{width:24px;height:24px;margin-top:-9px;border-width:2px}
|
| 52 |
+
.video-lora-control>input[type=range]::-moz-range-track{height:6px;background:linear-gradient(to right,#286645 var(--lora-fill,0%),#c1d8c9 var(--lora-fill,0%))}
|
| 53 |
+
.video-lora-control>input[type=range]::-moz-range-thumb{width:24px;height:24px;border-width:2px}
|
| 54 |
+
|
| 55 |
+
.video-lora-control>.lora-prompt-presets{grid-column:1/-1;grid-row:3;width:100%;min-width:0;min-height:36px;margin-top:4px;padding:6px 8px;font-size:16px;border:1px solid #bed0c4;border-radius:6px;background:#fff;color:#223a30}
|
| 56 |
+
|
| 57 |
+
/* A little more separation between LoRA controls. */
|
| 58 |
+
.video-lora-control{margin-top:12px;margin-bottom:12px}
|
| 59 |
+
|
| 60 |
+
/* Compact FPS slider with a 24px thumb and touch-friendly track. */
|
| 61 |
+
.karma-fps-control{display:grid;grid-template-columns:1fr auto;gap:4px 12px}
|
| 62 |
+
.karma-fps-control>output{font-variant-numeric:tabular-nums}
|
| 63 |
+
.karma-fps-control>input[type=range]{grid-column:1/-1;width:100%;min-width:0;height:36px;min-height:36px;padding:0;touch-action:none;appearance:none;background:transparent;border:0}
|
| 64 |
+
.karma-fps-control>input[type=range]::-webkit-slider-runnable-track{height:6px;border-radius:3px;background:#c1d8c9}
|
| 65 |
+
.karma-fps-control>input[type=range]::-webkit-slider-thumb{appearance:none;width:24px;height:24px;margin-top:-9px;border:2px solid #286645;border-radius:50%;background:#fff}
|
| 66 |
+
.karma-fps-control>input[type=range]::-moz-range-track{height:6px;border-radius:3px;background:#c1d8c9}
|
| 67 |
+
.karma-fps-control>input[type=range]::-moz-range-thumb{width:20px;height:20px;border:2px solid #286645;border-radius:50%;background:#fff}
|
upstream/Comfy-Org--Wan_2.1_ComfyUI_repackaged/617a7633e636506f850e043bc4605f290a466a8e/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
tags:
|
| 4 |
+
- comfyui
|
| 5 |
+
- diffusion-single-file
|
| 6 |
+
base_model:
|
| 7 |
+
- Wan-AI/Wan2.1-FLF2V-14B-720P
|
| 8 |
+
- alibaba-pai/Wan2.1-Fun-V1.1-1.3B-Control-Camera
|
| 9 |
+
- alibaba-pai/Wan2.1-Fun-V1.1-14B-Control-Camera
|
| 10 |
+
- alibaba-pai/Wan2.1-Fun-V1.1-1.3B-Control
|
| 11 |
+
- alibaba-pai/Wan2.1-Fun-V1.1-1.3B-InP
|
| 12 |
+
- Wan-AI/Wan2.1-I2V-14B-480P
|
| 13 |
+
- Wan-AI/Wan2.1-I2V-14B-720P
|
| 14 |
+
- MAGREF-Video/MAGREF
|
| 15 |
+
- Wan-AI/Wan2.1-T2V-1.3B
|
| 16 |
+
- Wan-AI/Wan2.1-T2V-14B
|
| 17 |
+
- Wan-AI/Wan2.1-VACE-1.3B
|
| 18 |
+
- Wan-AI/Wan2.1-VACE-14B
|
| 19 |
+
- htdong/Wan-Alpha
|
| 20 |
+
- MeiGen-AI/InfiniteTalk
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
# Wan 2.1
|
| 24 |
+
|
| 25 |
+
Repackaged model files for ComfyUI.
|
| 26 |
+
|
| 27 |
+
Original model repository:
|
| 28 |
+
|
| 29 |
+
- https://huggingface.co/Wan-AI/Wan2.1-FLF2V-14B-720P
|
| 30 |
+
- https://huggingface.co/alibaba-pai/Wan2.1-Fun-V1.1-1.3B-Control-Camera
|
| 31 |
+
- https://huggingface.co/alibaba-pai/Wan2.1-Fun-V1.1-14B-Control-Camera
|
| 32 |
+
- https://huggingface.co/alibaba-pai/Wan2.1-Fun-V1.1-1.3B-Control
|
| 33 |
+
- https://huggingface.co/alibaba-pai/Wan2.1-Fun-V1.1-1.3B-InP
|
| 34 |
+
- https://huggingface.co/Wan-AI/Wan2.1-I2V-14B-480P
|
| 35 |
+
- https://huggingface.co/Wan-AI/Wan2.1-I2V-14B-720P
|
| 36 |
+
- https://huggingface.co/MAGREF-Video/MAGREF
|
| 37 |
+
- https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B
|
| 38 |
+
- https://huggingface.co/Wan-AI/Wan2.1-T2V-14B
|
| 39 |
+
- https://huggingface.co/Wan-AI/Wan2.1-VACE-1.3B
|
| 40 |
+
- https://huggingface.co/Wan-AI/Wan2.1-VACE-14B
|
| 41 |
+
- https://huggingface.co/htdong/Wan-Alpha
|
| 42 |
+
- https://huggingface.co/MeiGen-AI/InfiniteTalk
|
| 43 |
+
|
| 44 |
+
Place the files in the following folders:
|
| 45 |
+
|
| 46 |
+
```
|
| 47 |
+
📂 ComfyUI/
|
| 48 |
+
├── 📂 models/
|
| 49 |
+
│ ├── 📂 clip_vision/
|
| 50 |
+
│ │ └── clip_vision_h.safetensors
|
| 51 |
+
│ ├── 📂 diffusion_models/
|
| 52 |
+
│ │ ├── wan2.1_flf2v_720p_14B_fp16.safetensors
|
| 53 |
+
│ │ ├── wan2.1_flf2v_720p_14B_fp8_e4m3fn.safetensors
|
| 54 |
+
│ │ ├── wan2.1_fun_camera_v1.1_1.3B_bf16.safetensors
|
| 55 |
+
│ │ ├── wan2.1_fun_camera_v1.1_14B_bf16.safetensors
|
| 56 |
+
│ │ ├── wan2.1_fun_control_1.3B_bf16.safetensors
|
| 57 |
+
│ │ ├── wan2.1_fun_inp_1.3B_bf16.safetensors
|
| 58 |
+
│ │ ├── wan2.1_i2v_480p_14B_bf16.safetensors
|
| 59 |
+
│ │ ├── wan2.1_i2v_480p_14B_fp16.safetensors
|
| 60 |
+
│ │ ├── wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors
|
| 61 |
+
│ │ ├── wan2.1_i2v_480p_14B_fp8_scaled.safetensors
|
| 62 |
+
│ │ ├── wan2.1_i2v_720p_14B_bf16.safetensors
|
| 63 |
+
│ │ ├── wan2.1_i2v_720p_14B_fp16.safetensors
|
| 64 |
+
│ │ ├── wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors
|
| 65 |
+
│ │ ├── wan2.1_i2v_720p_14B_fp8_scaled.safetensors
|
| 66 |
+
│ │ ├── wan2.1_magref_14B_fp16.safetensors
|
| 67 |
+
│ │ ├── wan2.1_t2v_1.3B_bf16.safetensors
|
| 68 |
+
│ │ ├── wan2.1_t2v_1.3B_fp16.safetensors
|
| 69 |
+
│ │ ├── wan2.1_t2v_14B_bf16.safetensors
|
| 70 |
+
│ │ ├── wan2.1_t2v_14B_fp16.safetensors
|
| 71 |
+
│ │ ├── wan2.1_t2v_14B_fp8_e4m3fn.safetensors
|
| 72 |
+
│ │ ├── wan2.1_t2v_14B_fp8_scaled.safetensors
|
| 73 |
+
│ │ ├── wan2.1_vace_1.3B_fp16.safetensors
|
| 74 |
+
│ │ ├── wan2.1_vace_1.3B_preview_fp16.safetensors
|
| 75 |
+
│ │ └── wan2.1_vace_14B_fp16.safetensors
|
| 76 |
+
│ ├── 📂 loras/
|
| 77 |
+
│ │ └── wan_alpha_2.1_rgba_lora.safetensors
|
| 78 |
+
│ ├── 📂 model_patches/
|
| 79 |
+
│ │ ├── wan2.1_infiniteTalk_multi_fp16.safetensors
|
| 80 |
+
│ │ └── wan2.1_infiniteTalk_single_fp16.safetensors
|
| 81 |
+
│ ├── 📂 text_encoders/
|
| 82 |
+
│ │ ├── umt5_xxl_fp16.safetensors
|
| 83 |
+
│ │ └── umt5_xxl_fp8_e4m3fn_scaled.safetensors
|
| 84 |
+
│ ├── 📂 vae/
|
| 85 |
+
│ │ ├── wan_2.1_vae.safetensors
|
| 86 |
+
│ │ ├── wan_alpha_2.1_vae_alpha_channel.safetensors
|
| 87 |
+
│ │ └── wan_alpha_2.1_vae_rgb_channel.safetensors
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
---
|
| 91 |
+
|
| 92 |
+
Wan 2.1 repackaged for ComfyUI use. For examples see: https://comfyanonymous.github.io/ComfyUI_examples/wan
|
upstream/Comfy-Org--Wan_2.2_ComfyUI_Repackaged/c4f60d30c55a624e35427060fdd217579a6c1d77/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
tags:
|
| 4 |
+
- comfyui
|
| 5 |
+
- diffusion-single-file
|
| 6 |
+
base_model:
|
| 7 |
+
- nvidia/ChronoEdit-14B-Diffusers
|
| 8 |
+
- Wan-AI/Wan2.2-Animate-14B
|
| 9 |
+
- alibaba-pai/Wan2.2-Fun-A14B-Control-Camera
|
| 10 |
+
- alibaba-pai/Wan2.2-Fun-A14B-Control
|
| 11 |
+
- alibaba-pai/Wan2.2-Fun-5B-Control
|
| 12 |
+
- alibaba-pai/Wan2.2-Fun-5B-InP
|
| 13 |
+
- alibaba-pai/Wan2.2-Fun-A14B-InP
|
| 14 |
+
- alibaba-pai/Wan2.2-VACE-Fun-A14B
|
| 15 |
+
- Wan-AI/Wan2.2-I2V-A14B
|
| 16 |
+
- Wan-AI/Wan2.2-S2V-14B
|
| 17 |
+
- Wan-AI/Wan2.2-T2V-A14B
|
| 18 |
+
- Wan-AI/Wan2.2-TI2V-5B
|
| 19 |
+
- lightx2v/Wan2.2-Distill-Loras
|
| 20 |
+
pipeline_tag: image-to-video
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
# Wan 2.2
|
| 24 |
+
|
| 25 |
+
Repackaged model files for ComfyUI.
|
| 26 |
+
|
| 27 |
+
Original model repository:
|
| 28 |
+
|
| 29 |
+
- https://huggingface.co/nvidia/ChronoEdit-14B-Diffusers
|
| 30 |
+
- https://huggingface.co/Wan-AI/Wan2.2-Animate-14B
|
| 31 |
+
- https://huggingface.co/alibaba-pai/Wan2.2-Fun-A14B-Control-Camera
|
| 32 |
+
- https://huggingface.co/alibaba-pai/Wan2.2-Fun-A14B-Control
|
| 33 |
+
- https://huggingface.co/alibaba-pai/Wan2.2-Fun-5B-Control
|
| 34 |
+
- https://huggingface.co/alibaba-pai/Wan2.2-Fun-5B-InP
|
| 35 |
+
- https://huggingface.co/alibaba-pai/Wan2.2-Fun-A14B-InP
|
| 36 |
+
- https://huggingface.co/alibaba-pai/Wan2.2-VACE-Fun-A14B
|
| 37 |
+
- https://huggingface.co/Wan-AI/Wan2.2-I2V-A14B
|
| 38 |
+
- https://huggingface.co/Wan-AI/Wan2.2-S2V-14B
|
| 39 |
+
- https://huggingface.co/Wan-AI/Wan2.2-T2V-A14B
|
| 40 |
+
- https://huggingface.co/Wan-AI/Wan2.2-TI2V-5B
|
| 41 |
+
- https://huggingface.co/lightx2v/Wan2.2-Distill-Loras
|
| 42 |
+
|
| 43 |
+
Place the files in the following folders:
|
| 44 |
+
|
| 45 |
+
```
|
| 46 |
+
📂 ComfyUI/
|
| 47 |
+
├── 📂 models/
|
| 48 |
+
│ ├── 📂 audio_encoders/
|
| 49 |
+
│ │ └── wav2vec2_large_english_fp16.safetensors
|
| 50 |
+
│ ├── 📂 diffusion_models/
|
| 51 |
+
│ │ ├── chrono_edit_14B_fp16.safetensors
|
| 52 |
+
│ │ ├── wan2.2_animate_14B_bf16.safetensors
|
| 53 |
+
│ │ ├── wan2.2_animate_14B_int8_convrot.safetensors
|
| 54 |
+
│ │ ├── wan2.2_fun_camera_high_noise_14B_bf16.safetensors
|
| 55 |
+
│ │ ├── wan2.2_fun_camera_high_noise_14B_fp8_scaled.safetensors
|
| 56 |
+
│ │ ├── wan2.2_fun_camera_low_noise_14B_bf16.safetensors
|
| 57 |
+
│ │ ├── wan2.2_fun_camera_low_noise_14B_fp8_scaled.safetensors
|
| 58 |
+
│ │ ├── wan2.2_fun_control_5B_bf16.safetensors
|
| 59 |
+
│ │ ├── wan2.2_fun_control_high_noise_14B_bf16.safetensors
|
| 60 |
+
│ │ ├── wan2.2_fun_control_high_noise_14B_fp8_scaled.safetensors
|
| 61 |
+
│ │ ├── wan2.2_fun_control_low_noise_14B_bf16.safetensors
|
| 62 |
+
│ │ ├── wan2.2_fun_control_low_noise_14B_fp8_scaled.safetensors
|
| 63 |
+
│ │ ├── wan2.2_fun_inpaint_5B_bf16.safetensors
|
| 64 |
+
│ │ ├── wan2.2_fun_inpaint_high_noise_14B_bf16.safetensors
|
| 65 |
+
│ │ ├── wan2.2_fun_inpaint_high_noise_14B_fp8_scaled.safetensors
|
| 66 |
+
│ │ ├── wan2.2_fun_inpaint_low_noise_14B_bf16.safetensors
|
| 67 |
+
│ │ ├── wan2.2_fun_inpaint_low_noise_14B_fp8_scaled.safetensors
|
| 68 |
+
│ │ ├── wan2.2_fun_vace_high_noise_14B_bf16.safetensors
|
| 69 |
+
│ │ ├── wan2.2_fun_vace_high_noise_14B_fp8_scaled.safetensors
|
| 70 |
+
│ │ ├── wan2.2_fun_vace_low_noise_14B_bf16.safetensors
|
| 71 |
+
│ │ ├── wan2.2_fun_vace_low_noise_14B_fp8_scaled.safetensors
|
| 72 |
+
│ │ ├── wan2.2_i2v_high_noise_14B_fp16.safetensors
|
| 73 |
+
│ │ ├── wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors
|
| 74 |
+
│ │ ├── wan2.2_i2v_low_noise_14B_fp16.safetensors
|
| 75 |
+
│ │ ├── wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors
|
| 76 |
+
│ │ ├── wan2.2_s2v_14B_bf16.safetensors
|
| 77 |
+
│ │ ├── wan2.2_s2v_14B_fp8_scaled.safetensors
|
| 78 |
+
│ │ ├── wan2.2_t2v_high_noise_14B_fp16.safetensors
|
| 79 |
+
│ │ ├── wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors
|
| 80 |
+
│ │ ├── wan2.2_t2v_low_noise_14B_fp16.safetensors
|
| 81 |
+
│ │ ├── wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors
|
| 82 |
+
│ │ └── wan2.2_ti2v_5B_fp16.safetensors
|
| 83 |
+
│ ├── 📂 loras/
|
| 84 |
+
│ │ ├── chronoedit_distill_lora.safetensors
|
| 85 |
+
│ │ ├── wan2.2_animate_14B_relight_lora_bf16.safetensors
|
| 86 |
+
│ │ ├── wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors
|
| 87 |
+
│ │ ├── wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors
|
| 88 |
+
│ │ ├── wan2.2_t2v_lightx2v_4steps_lora_v1.1_high_noise.safetensors
|
| 89 |
+
│ │ └── wan2.2_t2v_lightx2v_4steps_lora_v1.1_low_noise.safetensors
|
| 90 |
+
│ ├── 📂 text_encoders/
|
| 91 |
+
│ │ ├── umt5_xxl_fp16.safetensors
|
| 92 |
+
│ │ └── umt5_xxl_fp8_e4m3fn_scaled.safetensors
|
| 93 |
+
│ ├── 📂 vae/
|
| 94 |
+
│ │ ├── wan2.2_vae.safetensors
|
| 95 |
+
│ │ └── wan_2.1_vae.safetensors
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
Examples: https://comfyanonymous.github.io/ComfyUI_examples/wan22/
|