jmpplbp commited on
Commit
90c71dd
·
0 Parent(s):

Duplicate from jmpplbp/VideoKarma1-rebuild-20260927

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +35 -0
  2. APP_FILES.json +62 -0
  3. ATTRIBUTION.md +15 -0
  4. MODEL_SOURCES.json +112 -0
  5. README.md +12 -0
  6. RUNTIME_RELEASE.json +5 -0
  7. RUNTIME_SOURCES.json +64 -0
  8. SUPPORT_SOURCES.json +31 -0
  9. app-runtime.json +45 -0
  10. app.js +4 -0
  11. bernini_continuation.py +143 -0
  12. catalog_boot.py +76 -0
  13. catalog_runtime.py +575 -0
  14. chat_runner.py +32 -0
  15. generate.py +4 -0
  16. index.html +12 -0
  17. irodori_runner.py +104 -0
  18. models/diffusion_models/denoiser-01.safetensors +3 -0
  19. models/diffusion_models/denoiser-02.safetensors +3 -0
  20. models/diffusion_models/denoiser-03.safetensors +3 -0
  21. models/diffusion_models/denoiser-04.safetensors +3 -0
  22. models/loras/a-high.safetensors +3 -0
  23. models/loras/a-low.safetensors +3 -0
  24. models/loras/adapter-01.safetensors +3 -0
  25. models/loras/adapter-02.safetensors +3 -0
  26. models/loras/adapter-03.safetensors +3 -0
  27. models/loras/adapter-04.safetensors +3 -0
  28. models/loras/b-high.safetensors +3 -0
  29. models/loras/b-low.safetensors +3 -0
  30. models/loras/c-high.safetensors +3 -0
  31. models/loras/c-low.safetensors +3 -0
  32. models/loras/d-high.safetensors +3 -0
  33. models/loras/d-low.safetensors +3 -0
  34. models/loras/e-high.safetensors +3 -0
  35. models/loras/e-low.safetensors +3 -0
  36. models/loras/f-high.safetensors +3 -0
  37. models/loras/f-low.safetensors +3 -0
  38. models/loras/g-high.safetensors +3 -0
  39. models/loras/g-low.safetensors +3 -0
  40. models/loras/h-high.safetensors +3 -0
  41. models/loras/h-low.safetensors +3 -0
  42. models/loras/i-high.safetensors +3 -0
  43. models/loras/i-low.safetensors +3 -0
  44. models/loras/j-high.safetensors +3 -0
  45. models/loras/j-low.safetensors +3 -0
  46. models/text_encoders/encoder-01.safetensors +3 -0
  47. models/vae/decoder-01.safetensors +3 -0
  48. style.css +67 -0
  49. upstream/Comfy-Org--Wan_2.1_ComfyUI_repackaged/617a7633e636506f850e043bc4605f290a466a8e/README.md +92 -0
  50. 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/