bjooo commited on
Commit
89f7a50
·
verified ·
1 Parent(s): 167f729

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. .gitignore +22 -0
  2. HANDOFF.md +192 -0
  3. _archive/nodes_unregistered/old/__init__.py +53 -0
  4. _archive/nodes_unregistered/old/arch_logic.py +141 -0
  5. _archive/nodes_unregistered/old/dolphin_vision_director_pre_continuity_20260702.py +778 -0
  6. _archive/nodes_unregistered/old/promp_auto_sequence.py +101 -0
  7. _archive/nodes_unregistered/old/promp_logic_v30_story.py +176 -0
  8. _archive/nodes_unregistered/old/promp_logic_v31_vision.py +191 -0
  9. _archive/nodes_unregistered/old/promp_logic_v32_vision.py +247 -0
  10. _archive/workflows/0705_v32_matrix.json +0 -0
  11. _archive/workflows/0705_vision.json +0 -0
  12. _archive/workflows/0705_vision2 (1).json +0 -0
  13. _archive/workflows/0705_vision2.json +0 -0
  14. _archive/workflows/0705_vision2.json.bak_20260703_before_previewonly +0 -0
  15. _archive/workflows/0705_vision2.json.bak_20260703_before_trimwidgets +0 -0
  16. _archive/workflows/0705_vision2.json.bak_20260724_1316_before_perclip_refactor +0 -0
  17. _archive/workflows/0705_vision2.json.bak_20260724_before_fixseed +0 -0
  18. _archive/workflows/0705_vision3.json +0 -0
  19. _archive/workflows/0705_vision3_svigray_test.json +0 -0
  20. _archive/workflows/0705_vision3_svimotion_test.json +0 -0
  21. _archive/workflows/0705_vision4.json +0 -0
  22. _archive/workflows/dolphin_lora_classification.json +0 -0
  23. _archive/workflows/dolphin_tags_cache.json +254 -0
  24. _archive/workflows/i2vt2v.json +2074 -0
  25. _archive/workflows/krea2 inpaint.json +1510 -0
  26. _archive/workflows/krea2 inpaint_2.json +1500 -0
  27. _archive/workflows/lora_test.json +0 -0
  28. _archive/workflows/slowmo_ab_test_A_core_wanimagetovideo.json +1131 -0
  29. _archive/workflows/slowmo_ab_test_B_svigray.json +1151 -0
  30. _archive/workflows/slowmo_scenario1_hi_cfg35_lo_lightx2v.json +948 -0
  31. _archive/workflows/slowmo_scenario2_3ksampler_warmup.json +1141 -0
  32. _archive/workflows/slowmo_svi_scenarioA_2clip_continuation.json +1737 -0
  33. _archive/workflows/slowmo_svi_scenarioA_official_recipe.json +1113 -0
  34. _archive/workflows/slowmo_svi_simple_nag_test.json +1243 -0
  35. _archive/workflows/v30.json +0 -0
  36. _archive/workflows/wan22_v2v_high_denoise_compare.json +1198 -0
  37. arch_logic.py +141 -0
  38. arch_logic_v16.py +80 -0
  39. disk_cache_provider.py +201 -0
  40. dolphin_director.py +93 -0
  41. dolphin_lora_prompt.py +299 -0
  42. dolphin_minimax_director.py +1430 -0
  43. dolphin_prompt_director.py +331 -0
  44. dolphin_seed_controller.py +70 -0
  45. dolphin_vision_director.py +1028 -0
  46. h3_0824_wanrefine.json +0 -0
  47. h3_wan_refine.json +866 -0
  48. lora_profile.py +299 -0
  49. mask_region_color_match.py +103 -0
  50. match_size.py +47 -0
.gitignore ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+
4
+ # runtime logs, regenerated on every ComfyUI restart
5
+ comfyui_restart_log.txt
6
+ comfyui_restart_log.txt.err
7
+ *.log
8
+
9
+ # regenerable local cache
10
+ dolphin_tags_cache.json
11
+
12
+ # personal/local Claude Code settings (machine-specific)
13
+ .claude/settings.local.json
14
+
15
+ # isolated musubi-tuner training env (nested git repo + venv, not part of this repo)
16
+ dataset_builder/train/venv/
17
+ dataset_builder/train/musubi_tuner/
18
+ dataset_builder/train/cache/
19
+ dataset_builder/train/output/
20
+
21
+ # downloadable model weights (YOLO detector for dataset_builder/detect.py, etc.)
22
+ *.pt
HANDOFF.md ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dolphin ComfyUI 노드 프로젝트 — 인수인계
2
+
3
+ ## 프로젝트 목적
4
+ ComfyUI에서 **이미지 1장(또는 텍스트 서사) + 로라 + 필수동작**을 넣으면
5
+ **4개의 연속된 5초 비디오 클립(총 20초)** 을 만드는 커스텀 노드 세트.
6
+
7
+ 비디오 체인은 WAN 2.2 Lightning 기반: `First`(clip1) + `Extend`×3(clip2~4 이어붙이기),
8
+ 각 클립마다 TripleKSampler(base/high/low 3스테이지)로 샘플링.
9
+
10
+ ---
11
+
12
+ ## 현재 쓰는 파일 (7개 노드 + 워크플로우)
13
+
14
+ ### 노드 코드 (커스텀 노드 폴더에 넣기)
15
+ 1. **`__init__.py`** — 아래 5개 노드를 등록. 각 등록은 개별 try/except.
16
+ 2. **`wan_resizer.py`** — `WanNativeResize_Dolphin`. 이미지를 WAN 해상도(480/720/1080P)로 비율 유지 리사이즈 + 정렬(16배수). (원본 그대로, 수정 안 함)
17
+ 3. **`promp_logic_v32_story.py`** — `DolphinMultiActionPromptNode_V32`. 텍스트 서사 → LLM이 4샷 프롬프트로 분배. JSON 강제 + 폴백. 모델 프리셋 드롭다운 있음.
18
+ 4. **`arch_logic_v16.py`** — `DolphinArchitectV16`. 클립별 LoRA를 model에만 적용(CLIP 로라 연산 제거로 속도↑) → TripleKSampler 공급. 트리거 통합 1출력. 해시 캐싱.
19
+ 5. **`dolphin_director.py`** — `DolphinCinematicDirector`. ⚠️ **사실상 고장난 죽은 코드** (2026-07-03 확인). `_llm_split()`는 아무것도 반환하지 않고 호출되지도 않음, `direct()`는 `mode`/LLM 호출을 무시하고 `clips = [master_story] * 4`로 하드코딩, 로라도 전혀 적용 안 함. `__init__.py`엔 등록돼 있지만 실사용 금지 — 쓰려면 사실상 재작성 필요.
20
+ 6. **`dolphin_vision_director.py`** — `DolphinVisionDirector`. **최신 주력 노드**. 이미지 직접 인식(VLM) + 필수동작 N샷 분배(`num_clips`) + 클립당 로라 4개(개별 웨이트, on/off 토글) + 트리거 자동 추출/주입.
21
+ 7. **`dolphin_prompt_director.py`** — `DolphinPromptDirector` (2026-07-21 신설). `DolphinVisionDirector`와 **완전히 같은 비전+LLM 프롬프트 엔진**을 쓰되 `MODEL`/로라 관련을 전부 제거하고 프롬프트 문자열(`c1~c4_prompt`)만 뽑는 버전. 로직 중복을 피하려고 `DolphinVisionDirector`를 내부에서 인스턴스화해 `_vision_analyze`/`_plan_shots`/`_local_split`/`_build_final` 등 로라를 안 건드리는 순수 메서드만 위임 호출 — 프롬프트 정책(연속성 규칙 등)을 고치면 `dolphin_vision_director.py` 한 곳만 고치면 이 노드에도 자동 반영됨. 로라/모델 적용은 `DolphinArchitectV16` 같은 별도 노드에서 처리한다는 전제.
22
+
23
+ ### 워크플로우 JSON
24
+ - **`0705_vision2.json`** — **현재 실사용 중인 최신 워크플로우** (2026-07-03 기준). VisionDirector + JoyCaption(`JC` 노드) 로컬 캡션 우선 사용, `Model Loader`(f9a6f9ae 서브그래프)에서 lightx2v rank256 + SVI_v2_PRO rank128 사전 적용 → DolphinVisionDirector에서 클립별 PENISLORA(rank32) 패치 → ModelSamplingSD3 → NAG → TripleKSamplerWan22LightningAdvanced(First/Extend). 61개 노드, 서브그래프 16개.
25
+ - `0705_vision.json` — vision2 이전 버전.
26
+ - `0705_director.json` — CinematicDirector(텍스트) 버전. 위 5번 참고 — 노드 자체가 고장나 있어 이 워크플로우도 실동작 안 함.
27
+ - `0705_optimized.json` — V32 + Architect V16 분리형 버전.
28
+ (원본은 사용자가 처음 올린 `0705.json`. API 키는 vision/director/optimized 등 배포용 JSON에선 비워두는 게 원칙이나, `0705_vision2.json`은 사용자 로컬 작업본이라 실키가 들어있음 — 아래 "미해결" 참고.)
29
+
30
+ ---
31
+
32
+ ## DolphinVisionDirector 상세 (주력 노드)
33
+
34
+ ### 입력
35
+ - `image` (IMAGE) — WanNativeResize 출력에서
36
+ - `model_base / model_high / model_low` (MODEL) — Model Loader에서
37
+ - `essential_actions` — **필수 동작만** 콤마로. LLM이 이걸 N샷으로 분배.
38
+ - `num_clips` (2026-07-03 추가) — `"4 (20s)"`/`"3 (15s)"`/`"2 (10s)"`, 기본 4. 실제 렌더링/저장될 클립 수를 여기서 단일 결정 (아래 "클립 개수 중앙 제어" 참고).
39
+ - `character_name` (AUTO면 LLM이 작명), `artistic_vibe`, `pacing`, `detail_level`
40
+ - `inject_triggers`(토글), `use_vision`(토글)
41
+ - `openrouter_api_key`
42
+ - `vlm_preset` + `vlm_model` — 비전 모델 (기본 Qwen2.5-VL 72B)
43
+ - `llm_preset` + `llm_model` — 텍스트 분배 모델 (기본 DeepSeek V3.2)
44
+ - `creativity`, `seed`
45
+ - 로라: **`c{1..4}_lora_{1..3}`** (드롭다운) + **`c{1..4}_w_{1..3}`** (웨이트 문자열 `"base, high, low"`)
46
+ - `manual_triggers`
47
+
48
+ ### 출력 (23개, 2026-07-03 기준)
49
+ - `c1_base, c1_high, c1_low, c1_prompt` × 4클립 (16개) — `num_clips`보다 큰 클립은 base/high/low가 `ExecutionBlocker(None)`
50
+ - `triggers` — 사용된 모든 로라 트리거 통합
51
+ - `scene_analysis` — VLM이 이미지에서 읽은 앵커
52
+ - `debug_info`
53
+ - `c1_is_final, c2_is_final, c3_is_final, c4_is_final` (2026-07-03 추가) — `c == num_clips`인 클립만 True. 각 단계별 VideoCombine의 `save_output` 입력에 연결해서 선택한 길이만 저장되게 함.
54
+
55
+ ### 로라 웨이트 규칙 (스크린샷의 Triple Lora Matrix 방식 채택)
56
+ - 로라당 웨이트를 `"base, high, low"` 한 줄로. 예: `0.3, 0.8, 0.0`
57
+ - 값 부족 시 마지막 값으로 채움 (`1.0`→전부 1.0, `0.5, 0.7`→base 0.5/high·low 0.7)
58
+ - **스테이지 분리 로라 지원**: WAN 2.2에서 HIGH 로라는 `0.3, 0.8, 0.0`(low=0), LOW 로라는 `0.0, 0.0, 0.4`(base·high=0)로 나눠 씀. 검증 완료.
59
+ - 웨이트 0인 스테이지는 그 로라를 안 붙임.
60
+
61
+ ### 프롬프트 설계 (사용자 요구 반영 완료)
62
+ - **동작 순서 보존**: essential_actions는 시간순. 뒤 동작은 clip3 후반~clip4에만 (front-loading 금지).
63
+ - **묘사 배제**: 환경/조명/의상/분위기 다 빼고 **동작(신체 움직임)만**.
64
+ - **타임코드 제거**: `[0-1s]` 방식은 WAN I2V에서 노이즈라 뺌.
65
+ - **슬로우모션 방지**: (1) 5초를 연속 동작으로 채움 (2) 속도 어휘(swiftly/rapidly/explosively) (3) "real-time speed, no slow motion, no freeze" 명시.
66
+ - master_scene은 비움(묘사 안 함).
67
+ - 비전 역할은 "묘사"가 아니라 "앵커(캐릭터 상태/자세/환경) 추출"로 좁혀 장황함 억제.
68
+
69
+ ---
70
+
71
+ ## 모델 프리셋 (OpenRouter, NSFW/무삭제 대응)
72
+
73
+ ### 텍스트 LLM (4샷 분배용)
74
+ - `deepseek/deepseek-v3.2` (기본, 무삭제+JSON안정+한국어 좋음) ← 추천
75
+ - `deepseek/deepseek-chat-v3-0324`
76
+ - `anthracite-org/magnum-v4-72b` (표현 자연스러움)
77
+ - `thedrummer/cydonia-24b-v4.1` (경량)
78
+ - `cognitivecomputations/dolphin-mistral-24b-venice-edition:free` (무료)
79
+ - custom 선택 시 텍스트 필드값 사용
80
+
81
+ ### 비전 VLM (이미지 인식용)
82
+ - `qwen/qwen2.5-vl-72b-instruct` (기본, 특수상황 강함) ← 추천
83
+ - `qwen/qwen2.5-vl-32b-instruct` (+ `:free` 무료)
84
+ - `mistralai/mistral-small-3.1-24b-instruct` (Pixtral)
85
+ - `google/gemma-3-27b-it`
86
+
87
+ 이미지 전송: ComfyUI IMAGE 텐서 → JPEG base64 data URI → OpenRouter `image_url` content block. (PIL 필요, ComfyUI 기본 포함)
88
+
89
+ ---
90
+
91
+ ## 미해결 / 다음에 할 수 있는 것
92
+
93
+ ### 다음 세션 최우선 확인 사항 (2026-07-03 세션 종료 시점)
94
+ 1. **`lightning_steps` 8→4 수정 실사용 검증** — 슬로우모션 증상의 유력 원인으로 지목하고 고쳤지만(아래 3번 상세), 아직 실제 렌더로 개선 확인 안 함. ComfyUI는 이미 재시작됨(코드 변경 반영 완료), 워크플로우만 새로고침해서 한 번 돌려볼 것.
95
+ 2. **`num_clips`/`Extend range` 관계 숙지** — `num_clips`가 이제 유일한 클립 개수 컨트롤. "Extend range"(Fast Groups Bypasser) 토글은 **절대 건드리지 말 것**(구 방식과 충돌).
96
+ 3. ~~`openrouter_api_key` 평문 제거~~ **완료(2026-07-21)** — 아래 6번 참고.
97
+ 4. **git 저장소 신설(2026-07-21)** — `dolphin_nodes`에 로컬 git 초기화 + 현재 상태 전체(코드/워크플로우/`old/`/`.bak_*`) 첫 커밋 완료. 앞으로 파일 수정 전 `.bak_*`/`_v조` 파일 복사하는 습관은 그만두고 `git commit`으로 대체할 것. `old/` 폴더와 17개 `.bak_*` 파일은 이제 git 히스토리로도 안전하게 보존돼 있으니, 폴더 정리(삭제)는 다음 세션에서 사용자 확인 후 진행 가능.
98
+
99
+ 1. **NSFW 비전 모델 (해결)** — `0705_vision2.json`은 로컬 JoyCaption(`JC` 노드, `vision_caption` 입력)으로 OpenRouter VLM을 우회 중. `use_vision` 위젯은 **재확인 결과 이미 `true`(ON)로 켜져 있음** — JoyCaption이 성공하면 `vision_caption`을 우선 쓰고 VLM 호출 자체가 안 일어나지만, JoyCaption이 실패/빈 응답이면 이제 안전망으로 OpenRouter VLM 폴백이 작동함. 실행 로그(`debug_info`)에서도 `vlm=vision ok (local caption node)`로 정상 확인됨.
100
+ - 클라우드 무삭제+이미지 동시지원 모델은 여전히 드묾(Qwen2.5-VL이 그나마 관대). 완전 로컬 VLM 전환은 대안 B로 남아있음(현재는 JoyCaption 로컬 캡션으로 사실상 우회 중이라 급하지 않음).
101
+
102
+ 2. **로라 드롭다운 검색/필터** — 노드 코드로 불가. ComfyUI 프론트엔드 기능이라 ComfyUI 업데이트로 해결하거나 rgthree-comfy 설치 필요.
103
+
104
+ 3. **슬로우모션/연기 일관성**:
105
+ - **클립 "안"의 슬로우모션 (2026-07-03 원인 특정, 수정 적용함 — 실사용 검증 아직)**: 로라 파일명(`Wan_2_2_I2V_A14B_HIGH/LOW_lightx2v_4step_lora_...`, 그 외 D:/sd/models/loras의 다른 Lightning 로라들도 전부 "4step/4steps")이 전부 **총 4스텝 증류**를 명시하는데, 워크플로우의 TripleKSampler 4개(clip1~4, node 532/484/480/1077) 전부 `lightning_steps=8`(HIGH 4 + LOW 4, 50% 지점 분할)로 설정돼 있었음 — 증류 스텝의 **정확히 2배**. 증류된 Lightning 모델에 계산보다 많은 스텝을 쓰면 스케줄을 벗어나서 동작이 억눌리고 뭉개지는(슬로우모션처럼 보이는) 현상이 흔함. **`lightning_steps`를 4개 노드 전부 8→4로 변경 완료**(��업: `0705_vision2.json.bak_20260703_before_lightningsteps`). 나머지 값(sigma_shift=5, switch_strategy="50% of steps" 등)은 그대로 — 2 HIGH + 2 LOW로 재분배됨. **다음 세션에서 실제 렌더로 개선 여부 확인 필요**.
106
+ - 참고로 조사 중 "외부 ModelSamplingSD3 서브그래프(shift=5) + TripleKSampler 자체 내부 sigma_shift=5 이중 적용" 가설도 검토했으나, `comfy/model_sampling.py`의 `set_parameters()`가 매번 새 model_sampling 객체로 완전히 덮어쓰는 방식이라 **이중 적용해도 누적 안 됨**(같은 값으로 두 번 걸면 한 번 건 것과 동일) — 이건 범인 아님, 낭비적이지만 무해한 중복이라 안 건드림.
107
+ - **클립 "사이" 연속성(2026-07-03 해결)**: `_plan_shots` 시스템 프롬프트에 rule 3 "INTER-CLIP CONTINUITY" 추가 — clip_N이 clip_(N-1) 끝난 자세/모멘텀에서 이어서 시작하도록 명시. I2V 프레임 체인(마지막 프레임 그대로 다음 클립 시작)은 원래도 있었지만, 텍스트 프롬프트가 그 연속성을 언급 안 해서 "따로 노는 동작"처럼 보일 수 있었던 gap을 메움. 실사용 검증은 아직 안 함 — 다음 세션에서 4클립 이어보고 확인 필요.
108
+
109
+ 4. **동일 로라 중복 로딩** — 4클립 같은 로라면 4번 패치(클립별 유연성 위한 의도적 트레이드오프). 캐싱 최적화 가능하나 미적용.
110
+
111
+ 5. `dolphin_director.py`(텍스트 버전) — 위 "현재 쓰는 파일" 참고, 로라 방식 통일 이전에 **노드 자체가 동작하지 않음**. 재작성하거나 `__init__.py`에서 등록 제거할지 결정 필요.
112
+
113
+ 6. **`0705_vision2.json` 워크플로우 정리 항목 (2026-07-03 감사)**:
114
+ - ~~`use_vision` OFF → ON 권장~~ **재확인 결과 이미 `true`로 바뀌어 있었음** (핸드오프 기록이 낡았던 것, 실제 위젯값 재확인 완료).
115
+ - ~~죽은 `StringConcatenate` 노드(id 1062)~~ **이미 삭제돼 있음** (재확인 완료).
116
+ - ~~`openrouter_api_key` 위젯에 여전히 실키가 평문 저장돼 있음~~ **해결됨(2026-07-21)**: 키를 Windows User 환경변수 `OPENROUTER_API_KEY`로 옮기고, `0705_vision2.json` + 모든 `.bak_*` + `v30.json` + 중복본(`0705_vision2 (1).json`)에서 키 문자열 전부 제거 후 git 커밋(그래서 git 히스토리에도 안 들어감). `resolve_api_key()`가 위젯 비어있으면 자동으로 env var 사용. ComfyUI도 새 env var 반영을 위해 재시작함 — **다음 세션에서 debug_info가 `vlm=no api key`로 뜨진 않는지만 확인하면 끝**.
117
+ - `triggers` 출력 포트 미연결(디버깅용, 급하지 않음).
118
+
119
+ 7. **검증 완료 (2026-07-03) — 더 이상 의심 안 해도 됨**:
120
+ - **로라 인젝션 체인 전수 확인**: `Model Loader`(553) → `DolphinVisionDirector`(1086) → `ModelSamplingSD3` → `NAG` → `TripleKSamplerWan22LightningAdvanced`(First/Extend) 까지 4클립 전부 끊김/교차배선 없이 연결됨. 파일 경로 전부 실재, weight 0/None 오류 없음. 실행 로그(`comfyui_restart_log.txt`)에도 트리거워드 `PENISLORA` 주입 확인됨.
121
+ - **lightx2v는 이미 rank256으로 적용 중** (HIGH/LOW 둘 다, weight 1.0), SVI_v2_PRO rank128과 스택. `model_base`(stage1)엔 lightx2v 미적용(의도된 설계 — stage1은 non-lightning 풀스텝), `model_high`/`model_low`(stage2/3)에만 적용.
122
+ - PENISLORA HIGH(e320)/LOW(e496)는 rank32 (안전텐서 텐서 shape로 직접 확인).
123
+
124
+ ---
125
+
126
+ ## 최근 수정 이력 (dolphin_vision_director.py)
127
+
128
+ ### 2026-07-03 세션
129
+ - **`_plan_shots` 에러 삼킴 버그 수정**: LLM 4샷 분배 API 호출 실패 시 실제 원인(HTTP 코드/본문, 예외 타입, 불완전 JSON)이 `last` 변수에만 담기고 버려져서 `debug_info`엔 항상 "⚠️ plan failed"만 찍혔음. 이제 원인이 그대로 `debug_info`/콘솔에 남음.
130
+ - **API 키 환경변수 폴백 추가**: `resolve_api_key()` 함수 — `openrouter_api_key` 위젯이 비어있으면 `OPENROUTER_API_KEY` 환경변수 사용. 기존 위젯에 키가 있으면 동작 그대로(하위호환), 워크플로우 JSON에 실키를 안 박아둬도 되게 하려는 목적.
131
+ - **클립 간 물리적 연속성 규칙 추가** (`_plan_shots` 시스템 프롬프트): 새 rule 3 "INTER-CLIP CONTINUITY" — clip_N은 clip_(N-1)이 끝난 정확한 신체 위치/모멘텀에서 이어서 시작(단, "continuing from" 같은 연결 문구를 텍스트에 직접 쓰는 건 금지 — 물리적 일관성만 지키고 "동작만 쓴다" 규칙과 안 부딪히게). 기존 rule 2(clip_1은 이미지 속 자세에서 시작)는 clip_1에만 적용됐던 걸 clip_2~4로 확장한 셈. 규칙 번호 3~8이 4~9로 밀림(내부 상호참조도 전부 갱신됨).
132
+ - **미사용 데드코드 제거**: `_fallback_name_deterministic` (호출부 없음, 주석에도 "미사용"이라 표시돼 있었음).
133
+ - **백업**: 위 변경 전 버전을 `old/dolphin_vision_director_pre_continuity_20260702.py`에 저장.
134
+ - **`0705_vision2.json` 전수 감사** + **���라 인젝션 체인 전수 검증** — 상세는 위 "미해결" 6번/7번 항목 참고.
135
+ - **ComfyUI 재시작 완료**: 기존 프로세스(PID 5452) 종료 후 동일 실행 옵션(`--enable-dynamic-vram --use-sage-attention --output-directory D:\sd\output`)으로 재기동, `dolphin_nodes` 에러 없이 로드 확인, `DolphinVisionDirector` API 등록 확인. 재시작 로그는 `comfyui_restart_log.txt`(stdout) / `comfyui_restart_log.txt.err`(stderr, 진행상황 대부분 여기).
136
+ - 문법 컴파일(`py_compile`) 확인 완료. **아직 실제 4클립 생성 재실행으로 rule 3 효과는 검증 안 함** — 다음 세션 우선 확인 사항.
137
+
138
+ ### 2026-07-03 세션 (이어서) — 메모리 정리 / 클립 개수 중앙 제어 / 속도 실험
139
+
140
+ **1. VRAM 정리 자동화 (해결)**
141
+ - 증상: 워크플로우 실행 후 VRAM이 절반쯤 계속 점유된 채 안 비워짐. 원인은 버그가 아니라 ComfyUI의 정상 캐싱 동작(다음 실행 빠르게 하려고 마지막 쓴 모델을 VRAM에 남겨둠) — 다만 이걸 강제로 비워주는 노드가 그래프에 없었음.
142
+ - 이미 설치돼 있던 `comfyui_memory_cleanup` 커스텀 노드(`VRAMCleanup`, 표시명 "🎈VRAM-Cleanup")를 최종 저장 노드(`VHS_VideoCombine` id 487)의 출력에 연결(node 1092, `offload_model=True, offload_cache=True`) → 렌더 끝나면 자동으로 `unload_all_models()` + `soft_empty_cache()` 실행됨. 위치는 First-I2V bypass 그룹 박스 안에 잘못 들어갔던 걸 나중에 밖으로 재배치함(그룹 좌표 [3450, -509]로 이동, 어떤 그룹에도 안 걸리게).
143
+ - 트레이드오프: 다음 생성 시작 시 모델을 디스크에서 다시 로드해야 해서 첫 스텝 전 로딩 시간이 늘어남.
144
+
145
+ **2. 클립 개수(10s/15s/20s) 중앙 제어 — `DolphinVisionDirector`가 단일 컨트롤 포인트 (해결)**
146
+ - 처음엔 워크플로우에 이미 있던 "Extend range"(rgthree `Fast Groups Bypasser`, node 332) 토글로 clip3/4 렌더를 스킵하는 방식을 검토했으나, 이 노드는 **순수 프론트엔드 전용**(백엔드 미등록, `RgthreeBaseVirtualNode`)이라 다른 노드가 프로그래밍적으로 제어 불가 + `num_clips`와 별도로 수동 동기화해야 하는 문제가 있어서 폐기.
147
+ - 대신 `DolphinVisionDirector`에 `num_clips` 위젯(`"4 (20s)"`/`"3 (15s)"`/`"2 (10s)"`, 기본 4, 하위호환) 추가. `_split_actions_to_clips`/`_plan_shots`(LLM 시스템 프롬프트+JSON 스키마)/`_local_split`(폴백) 전부 클립 개수에 맞춰 동적으로 동작하도록 일반화(마지막 동작=마지막 클립 규칙, 연속성 규칙 모두 N클립 대응). num_clips보다 큰 클립 인덱스는 essential_actions 배분에서 제외되고, 마지막 실사용 클립 내용을 반복해서 채움(안전장치).
148
+ - **실제 렌더 스킵은 ComfyUI 네이티브 `ExecutionBlocker`로 구현** (`from comfy_execution.graph_utils import ExecutionBlocker`): `num_clips`보다 큰 클립의 `c{N}_base/high/low` 출력에 `ExecutionBlocker(None)`을 반환 → 그 아래 `ModelSamplingSD3→NAG→TripleKSampler→해당 클립 VideoCombine` 체인 전체가 연산 없이 자동 스킵됨(서브그래프 내부는 안 건드림 — DolphinVisionDirector가 top-level에서 서브그래프 인스턴스로 모델을 넘기는 구조라 출력 쪽만 바꾸면 됨).
149
+ - **정확한 길이만 저장**: 새 BOOLEAN 출력 4개 `c1_is_final~c4_is_final`(RETURN_TYPES/NAMES 19→23개로 확장, `direct()`가 `c == n_clips`로 계산) 추가 → 각 단계별 `VHS_VideoCombine`(1091=5s/456=10s/464=15s/487=20s)의 `save_output` 위젯을 입력으로 변환(convert widget to input, `frame_rate`가 이미 이렇게 돼있던 패턴 재사용)해서 연결. 실행되지만 최종이 아닌 중간 클립은 미리보기로만 남고, `num_clips`가 선택한 길이 딱 하나만 디스크에 저장됨. 각 VideoCombine 파일명도 `_5s/_10s/_15s/_20s` 접미사로 구분해둠(예전엔 `Wan2`라는 구분 안 되는 접두사였음).
150
+ - "Extend range" 노드는 이제 완전히 불필요 — **토글을 절대 건드리지 말 것**(건드리면 옛날 bypass 방식과 새 ExecutionBlocker 로직이 충돌해서 혼란스러운 결과 가능). 삭제할지는 미결정, 일단 놔둠.
151
+ - 백업 다수: `0705_vision2.json.bak_20260703_before_vramcleanup`, `..._before_torchcompile`, `..._before_numclips`, `..._before_isfinal_wiring`, `..._before_torchcompile_revert`, `..._before_lightningsteps`.
152
+
153
+ **3. 속도 실험 — 둘 다 실익 없어서 원복/포기**
154
+ - **torch.compile**: `TorchCompileModel` 노드 3개(base/high/low, 클립당 1회만 컴파일되게 Model Loader 출력 쪽에 배치)로 테스트 → 실행 로그에 `torch._dynamo hit config.recompile_limit (8)` 반복(원인: `attention_sage`와 WAN 모델 forward의 `tensor_shape` 속성 접근이 dynamo가 추적하기 어려운 커스텀 텐서 서브클래스라 매번 재컴파일 필요 → 8회 만에 조용히 eager 폴백). 컴파일 이득 0, 오히려 실패 컴파일 시도로 시간 낭비 ��� **노드 3개 제거, 원래 직결 배선 복원함**.
155
+ - **SageAttention 3 (Blackwell/RTX5090 전용, FP4 텐서코어, 논문상 RTX5090 5x/비디오모델 end-to-end 약 3x)**: 소스 빌드 시도했으나 순서대로 3개 문제 부딪힘 — ① PyTorch(cu130)와 시스템 nvcc(12.8) 메이저 버전 불일치(→ `torch.utils.cpp_extension._check_cuda_version` 몽키패치로 우회 성공) ② Windows `MAX_PATH`(260자) 초과로 CUTLASS 헤더를 못 찾음(→ `C:\sage3build`처럼 짧은 경로로 재클론해서 해결) ③ **CUTLASS 템플릿(`BlockScaledConfig::LayoutSF`/`SfAtom`)에서 MSVC C2061 컴파일 에러** — CUTLASS 버전을 sageattn3 코드 작성 시점(2026-01-14 커밋)까지 맞춰봐도 동일 에러 재현, 패키지 PyPI 분류자도 `"Operating System :: Unix"`라 애초에 Linux/GCC 전용 개발로 보임 → **MSVC 템플릿 2단계 name lookup 호환성 문제로 결론, 빌드 포기**. 빌드 흔적(`C:\sage3build`, 스크래치 클론) 삭제 완료, `sageattn3`은 site-packages에 설치 안 됨(ComfyUI 환경 무변경).
156
+ - 참고: KJNodes(`comfyui-kjnodes`)의 `PathchSageAttentionKJ` 노드(`KJNodes/experimental` 카테고리)가 `sageattn3` 모드를 이미 지원하므로, 나중에 WSL2 등 Linux 환경에서 별도로 빌드에 성공하면 **ComfyUI 코어를 안 건드리고** 이 노드만 워크플로우에 추가해서 바로 쓸 수 있음(다만 WSL에서 빌드한 건 Windows 네이티브 Python엔 못 씀 — ComfyUI 자체를 WSL로 옮기는 게 아니라면 의미 없음, 별도의 큰 결정 필요).
157
+ - 현재 상태: 기존 `sageattention` v2.2.0(cu130torch2.9.0 빌드) 그대로 유지, `--use-sage-attention` 플래그로 정상 작동 중. RTX 5090(sm_120) 전용 커널은 없지만(v2는 sm80/89/90까지) 크래시 없이 잘 돎.
158
+
159
+ ### 3대 버그 수정
160
+ - **① 캐릭터 이름 AUTO로 샘**: 비전 앵커로 동양/서양 판단해 작명하도록 프롬프트 강화 + `_fallback_name()` 안전장치(LLM이 AUTO/빈값 반환 시 앵커 키워드로 이름 선택).
161
+ - **② 마지막 동작을 처음부터 함**: `_split_actions_to_clips()`가 동작을 코드에서 클립별로 미리 배정(마지막 동작은 반드시 clip4), LLM엔 배정표를 못박아 전달. 순서 위반이 최우선 금지 규칙.
162
+ - **③ 트리거워드 안 들어감**: `triggers` 출력 부활 + 클립별 로라 트리거 디버그(`_last_trig_dbg`) 콘솔 출력.
163
+
164
+ ### 비전 진단 강화 (사용자가 "비전 작동 의심" 제기 후)
165
+ - `tensor_to_base64`: PIL/None/인코딩 실패를 명확한 콘솔 에러로. 성공 시 이미지 크기 로그.
166
+ - `_vision_analyze`: VLM 응답 원문/HTTP 에러/빈 응답을 콘솔에 출력.
167
+ - `direct` STEP1: use_vision OFF / no key / no image / encode fail 각각 구체적 debug_v 메시지.
168
+ - **debug_info 출력만 봐도 비전 실패 원인 파악 가능**:
169
+ - `vlm=no api key` → 키 미입력 (사용자 증상의 유력 원인)
170
+ - `vlm=vision OFF` → 토글 꺼짐
171
+ - `vlm=no image` → IMAGE 미연결
172
+ - `vlm=vision HTTP 400` → VLM 거부(슬러그 오타/NSFW 거부)
173
+ - `vlm=vision empty response` → 빈 응답
174
+ - `vlm=vision ok` → 정상
175
+
176
+ ### 진단 결론
177
+ "씬 묘사 없음 + AUTO + 바로 동작" 증상 = 비전+LLM 실패 후 로컬 폴백. **가장 유력한 원인은 openrouter_api_key 미입력**(워크플로우에서 보안상 비워둠). 다음 실행 시 debug_info 확인 필요.
178
+
179
+ ### 프롬프트 정책 (확정)
180
+ - 타임코드([0-1s]) 제거 (WAN 노이즈). 대신 연속동작+속도어휘로 슬로우모션 방지.
181
+ - 묘사(환경/조명/의상) 배제, 동작만. master_scene 비움.
182
+ - 동작 순서 보존, 마지막 동작은 clip4.
183
+
184
+ ---
185
+
186
+ ## 중요 참고
187
+ - **git 저장소 있음 (2026-07-21부터)**: `dolphin_nodes` 자체가 로컬 git 저장소(`git init` 완료, 원격 없음/로컬 전용). 상위 ComfyUI 저장소의 `.gitignore`가 `custom_nodes/`를 통째로 무시해서 이 폴더는 별도 독립 저장소로 관리. `.gitignore`는 `__pycache__/`, 런타임 로그, `dolphin_tags_cache.json`, `.claude/settings.local.json` 제외. 커밋 전 `git status`/`git diff`로 확인 습관화, 새 `.bak_*` 파일 만드는 대신 `git commit` 사용.
188
+ - **위젯 이름이 바뀌면** 기존 저장 워크플로우의 로라 설정이 초기화될 수 있음. VisionDirector는 최근 `_h_clip` 헤더 제거 + `c{c}_lora_{n}` 이름으로 변경됨.
189
+ - 모든 노드: `INPUT_TYPES`의 required 순서 = 함수 시그니처 순서 (ComfyUI 규칙). 수정 시 AST로 일치 검증할 것.
190
+ - 워크플로우 편집 후엔 항상 무결성 체크: dangling_in / backref_mismatch / slot_oob 전부 0이어야 함. (단, `outputs[].links`에 참조된 링크 id가 top-level `links`에도 서브그래프 `definitions.subgraphs[].links`에도 없는 "유령 출력링크"는 정상적으로 존재할 수 있음 — 과거 편집 잔재로, ComfyUI가 로드시 무시하는 것으로 보임. `0705_vision2.json`의 negative prompt 노드(817)/서브그래프(553)에 ~1700개 있었���데 실행엔 지장 없었음.)
191
+ - 원본 `0705.json`의 UUID 노드들은 subgraph (First/Extend/Encode 등). `definitions.subgraphs[]`에 정의가 있고, 인스턴스 노드의 `type` 필드가 그 서브그래프의 `id`(UUID)와 일치.
192
+ - **`0705_vision2.json` LoRA/모델 체인 구조** (디버깅 시 참고): `Model Loader`(553, "f9a6f9ae..." 서브그래프)에서 GGUF 베이스 로드 + lightx2v(rank256) + SVI_v2_PRO(rank128)를 미리 적용해 `MODEL`(slot0, =model_high, lightx2v 있음)/`MODEL_1`(slot1, =model_low, lightx2v 있음)/`MODEL_2`(slot4, =model_base, lightx2v **없음** — stage1은 non-lightning 풀스텝이라 SVI만 적용) 3종을 출력 → `DolphinVisionDirector`가 클립별 캐릭터 로라를 추가 패치 → 클립마다 `ModelSamplingSD3` 서브그래프 → `NAG`(WanVideoNAG) 서브그래프 → `First`/`Extend` 서브그래프 내부 `TripleKSamplerWan22LightningAdvanced`로 도달. 클립1=786/787/453(First), 클립2=791/792/462(Extend), 클립3=793/794/463(Extend), 클립4=1070/1071/1080(Extend) — 각각 ModelSamplingSD3/NAG/샘플러 순. (444는 별개의 "Encode" 서브그래프로 이미지→latent 변환만 담당, 이 모델 체인과 무관.)
_archive/nodes_unregistered/old/__init__.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import traceback
2
+
3
+ NODE_CLASS_MAPPINGS = {}
4
+ NODE_DISPLAY_NAME_MAPPINGS = {}
5
+
6
+ # 🔥 KeyError를 물리적으로 불가능하게 만드는 동시 등록 함수
7
+ def register_node(class_name, node_class, display_name):
8
+ NODE_CLASS_MAPPINGS[class_name] = node_class
9
+ NODE_DISPLAY_NAME_MAPPINGS[class_name] = display_name
10
+
11
+ # 1. 리사이즈 노드
12
+ try:
13
+ from .wan_resizer import WanNativeResize_Dolphin
14
+ register_node("WanNativeResize_Dolphin", WanNativeResize_Dolphin, "🐬 Wan Native Resize (Dolphin)")
15
+ except Exception as e:
16
+ print(f"\n❌ [Dolphin] Wan Resizer 로드 실패: {e}")
17
+
18
+ # 2. 아키텍트 노드 (에러의 주범!)
19
+ try:
20
+ from .arch_logic import DolphinArchitectV15
21
+ register_node("DolphinArchitectV15", DolphinArchitectV15, "🐬 Dolphin Architect (V15 - 울트라 슬림 4:4)")
22
+ except Exception as e:
23
+ print(f"\n❌ [Dolphin] Architect 로드 실패 (arch_logic.py 확인 필요):\n{traceback.format_exc()}")
24
+
25
+ # 3. V25 오토 시퀀서 노드
26
+ try:
27
+ from .promp_auto_sequence import DolphinAutoSequencerNode
28
+ register_node("DolphinAutoSequencerNode", DolphinAutoSequencerNode, "🐬 Dolphin Sequencer (V25 - 무한 타임라인)")
29
+ except Exception as e:
30
+ print(f"\n❌ [Dolphin] Auto Sequencer 로드 실패: {e}")
31
+
32
+ # 4. V28 스티칭 노드
33
+ try:
34
+ from .promp_logic_v28_stitch import DolphinMultiActionPromptNode_V28
35
+ register_node("DolphinMultiActionPromptNode_V28", DolphinMultiActionPromptNode_V28, "🐬 Dolphin Prompter (V28 - 5초 개별 클립 x 4단 이어붙이기)")
36
+ except Exception as e:
37
+ print(f"\n❌ [Dolphin] V28 Prompter 로드 실패: {e}")
38
+
39
+ # 5. [신규] V30 스토리 스플리터 노드
40
+ try:
41
+ from .promp_logic_v30_story import DolphinMultiActionPromptNode_V30
42
+ register_node("DolphinMultiActionPromptNode_V30", DolphinMultiActionPromptNode_V30, "🐬 Dolphin Prompter (V30 - 단일 시나리오 분할)")
43
+ except Exception as e:
44
+ print(f"\n❌ [Dolphin] V30 Prompter 로드 실패: {e}")
45
+
46
+ # 6. [신규] V31 비전 마스터 노드
47
+ try:
48
+ from .promp_logic_v31_vision import DolphinMultiActionPromptNode_V31
49
+ register_node("DolphinMultiActionPromptNode_V31", DolphinMultiActionPromptNode_V31, "🐬 Dolphin Prompter (V31 - Vision Direct / 눈 달린 AI)")
50
+ except Exception as e:
51
+ print(f"\n❌ [Dolphin] V31 Vision Prompter 로드 실패: {e}")
52
+
53
+ __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
_archive/nodes_unregistered/old/arch_logic.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import folder_paths
4
+ import comfy.sd
5
+ import comfy.utils
6
+ from safetensors import safe_open
7
+ import hashlib
8
+ import urllib.request
9
+
10
+ CACHE_FILE = os.path.join(os.path.dirname(__file__), "dolphin_tags_cache.json")
11
+
12
+ def load_dolphin_cache():
13
+ if os.path.exists(CACHE_FILE):
14
+ try:
15
+ with open(CACHE_FILE, "r", encoding="utf-8") as f:
16
+ return json.load(f)
17
+ except: return {}
18
+ return {}
19
+
20
+ def save_dolphin_cache(cache_data):
21
+ try:
22
+ with open(CACHE_FILE, "w", encoding="utf-8") as f:
23
+ json.dump(cache_data, f, indent=4)
24
+ except: pass
25
+
26
+ class DolphinArchitectV15:
27
+ @classmethod
28
+ def INPUT_TYPES(s):
29
+ loras = ["None"] + folder_paths.get_filename_list("loras")
30
+
31
+ res = {
32
+ "required": {
33
+ "global_switch": ("BOOLEAN", {"default": True, "label_on": "GLOBAL ON", "label_off": "GLOBAL OFF (SKIP)"}),
34
+ "model_base": ("MODEL",), "clip_base": ("CLIP",),
35
+ "model_high": ("MODEL",), "clip_high": ("CLIP",),
36
+ "model_low": ("MODEL",), "clip_low": ("CLIP",),
37
+ },
38
+ "optional": {}
39
+ }
40
+
41
+ # [다이어트 완료] 오직 이름과 웨이트만 남겨서 가장 얇은 UI를 만듭니다.
42
+ res["optional"]["header_master"] = ("STRING", {"default": "─── [ MASTER: B+H ] ───", "multiline": False})
43
+ for i in range(1, 5):
44
+ res["optional"][f"master_l_{i}"] = (loras, {"default": "None"})
45
+ res["optional"][f"master_b_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
46
+ res["optional"][f"master_h_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
47
+
48
+ res["optional"]["header_low"] = ("STRING", {"default": "─── [ LOW ONLY ] ───", "multiline": False})
49
+ for i in range(1, 5):
50
+ res["optional"][f"low_l_{i}"] = (loras, {"default": "None"})
51
+ res["optional"][f"low_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
52
+
53
+ res["optional"]["manual_triggers"] = ("STRING", {"default": "", "multiline": True})
54
+ return res
55
+
56
+ RETURN_TYPES = ("MODEL", "CLIP", "MODEL", "CLIP", "MODEL", "CLIP", "STRING")
57
+ RETURN_NAMES = ("M_BASE", "C_BASE", "M_HIGH", "C_HIGH", "M_LOW", "C_LOW", "triggers")
58
+ FUNCTION = "exec"
59
+ CATEGORY = "Dolphin"
60
+
61
+ def get_trig(self, lora_name):
62
+ if lora_name == "None": return []
63
+ path = folder_paths.get_full_path("loras", lora_name)
64
+ if not path or not os.path.exists(path): return []
65
+
66
+ triggers = []
67
+ try:
68
+ with safe_open(path, framework="pt", device="cpu") as f:
69
+ m = f.metadata()
70
+ if m and "modelspec.trigger_words" in m:
71
+ tw = json.loads(m["modelspec.trigger_words"])
72
+ if isinstance(tw, list): triggers.extend(tw)
73
+ elif isinstance(tw, str): triggers.append(tw)
74
+ except: pass
75
+ if triggers: return triggers
76
+
77
+ cache = load_dolphin_cache()
78
+ if lora_name in cache and cache[lora_name]: return cache[lora_name]
79
+
80
+ try:
81
+ sha256_hash = hashlib.sha256()
82
+ with open(path, "rb") as f:
83
+ for byte_block in iter(lambda: f.read(4096 * 1024), b""):
84
+ sha256_hash.update(byte_block)
85
+
86
+ url = f"https://civitai.com/api/v1/model-versions/by-hash/{sha256_hash.hexdigest()}"
87
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
88
+ with urllib.request.urlopen(req, timeout=5) as response:
89
+ data = json.loads(response.read().decode('utf-8'))
90
+ api_triggers = data.get("trainedWords", [])
91
+ if api_triggers:
92
+ cache[lora_name] = api_triggers
93
+ save_dolphin_cache(cache)
94
+ return api_triggers
95
+ except: pass
96
+ return triggers
97
+
98
+ def exec(self, global_switch, model_base, clip_base, model_high, clip_high, model_low, clip_low, manual_triggers="", **kwargs):
99
+ mb, cb = model_base, clip_base
100
+ mh, ch = model_high, clip_high
101
+ ml, cl = model_low, clip_low
102
+
103
+ if not global_switch:
104
+ trigs = [t.strip() for t in manual_triggers.split(',') if t.strip()]
105
+ ut = ", ".join(list(dict.fromkeys(trigs)))
106
+ return (mb, cb, mh, ch, ml, cl, ut)
107
+
108
+ applied_names = []
109
+
110
+ for i in range(1, 5):
111
+ n = kwargs.get(f"master_l_{i}", "None")
112
+ w_b = kwargs.get(f"master_b_w_{i}", 1.0)
113
+ w_h = kwargs.get(f"master_h_w_{i}", 1.0)
114
+
115
+ # [최적화] None이 아니면서, 웨이트가 0.0이 아닐 때만 적용 (0.0이면 스위치 OFF 효과)
116
+ if n != "None" and (w_b != 0.0 or w_h != 0.0):
117
+ applied_names.append(n)
118
+ lora_data = comfy.utils.load_torch_file(folder_paths.get_full_path("loras", n))
119
+ mb, cb = comfy.sd.load_lora_for_models(mb, cb, lora_data, w_b, w_b)
120
+ mh, ch = comfy.sd.load_lora_for_models(mh, ch, lora_data, w_h, w_h)
121
+
122
+ for i in range(1, 5):
123
+ n = kwargs.get(f"low_l_{i}", "None")
124
+ w = kwargs.get(f"low_w_{i}", 1.0)
125
+
126
+ if n != "None" and w != 0.0:
127
+ applied_names.append(n)
128
+ lora_data = comfy.utils.load_torch_file(folder_paths.get_full_path("loras", n))
129
+ ml, cl = comfy.sd.load_lora_for_models(ml, cl, lora_data, w, w)
130
+
131
+ trigs = []
132
+ for n in set(applied_names):
133
+ trigs.extend(self.get_trig(n))
134
+
135
+ if manual_triggers.strip():
136
+ manual_list = [t.strip() for t in manual_triggers.split(',') if t.strip()]
137
+ trigs.extend(manual_list)
138
+
139
+ unique_trigs = ", ".join(list(dict.fromkeys(trigs)))
140
+
141
+ return (mb, cb, mh, ch, ml, cl, unique_trigs)
_archive/nodes_unregistered/old/dolphin_vision_director_pre_continuity_20260702.py ADDED
@@ -0,0 +1,778 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import io
4
+ import json
5
+ import base64
6
+ import hashlib
7
+ import sqlite3
8
+ import urllib.request
9
+ import urllib.error
10
+
11
+ import numpy as np
12
+ import folder_paths
13
+ import comfy.sd
14
+ import comfy.lora
15
+ import comfy.utils
16
+ from safetensors import safe_open
17
+
18
+ try:
19
+ from PIL import Image
20
+ _HAS_PIL = True
21
+ except Exception:
22
+ _HAS_PIL = False
23
+
24
+ CACHE_FILE = os.path.join(os.path.dirname(__file__), "dolphin_tags_cache.json")
25
+
26
+
27
+ # ======================================================================
28
+ # 모델 프리셋 (텍스트 LLM = 4샷 분배 / 비전 VLM = 이미지 인식)
29
+ # ======================================================================
30
+ LLM_PRESETS = {
31
+ "🔞 DeepSeek V3.2 (무삭제·JSON안정·추천)": "deepseek/deepseek-v3.2",
32
+ "🔞 Magnum v4 72B (표현 자연스러움)": "anthracite-org/magnum-v4-72b",
33
+ "🔞 Cydonia 24B v4.1 (경량·빠름)": "thedrummer/cydonia-24b-v4.1",
34
+ "🔞 Dolphin Mistral Venice (무료)": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free",
35
+ "⚙️ custom (아래 텍스트 사용)": "__custom__",
36
+ }
37
+ LLM_PRESET_LABELS = list(LLM_PRESETS.keys())
38
+
39
+ # 비전 모델: 특수 상황 이해 + 다국어. Qwen2.5-VL 계열이 포즈/맥락 파악에 강함.
40
+ VLM_PRESETS = {
41
+ "👁 Qwen2.5-VL 72B (특수상황·추천)": "qwen/qwen2.5-vl-72b-instruct",
42
+ "👁 Qwen2.5-VL 32B (균형)": "qwen/qwen2.5-vl-32b-instruct",
43
+ "👁 Qwen2.5-VL 32B (무료)": "qwen/qwen2.5-vl-32b-instruct:free",
44
+ "👁 Mistral Small 3.1 24B (Pixtral)": "mistralai/mistral-small-3.1-24b-instruct",
45
+ "👁 Gemma 3 27B (경량)": "google/gemma-3-27b-it",
46
+ "⚙️ custom (아래 텍스트 사용)": "__custom__",
47
+ }
48
+ VLM_PRESET_LABELS = list(VLM_PRESETS.keys())
49
+
50
+
51
+ def resolve_slug(table, label, custom_text, default):
52
+ slug = table.get(label, "__custom__")
53
+ if slug == "__custom__":
54
+ return custom_text.strip() or default
55
+ return slug
56
+
57
+
58
+ def resolve_api_key(widget_value):
59
+ """위젯에 키가 있으면 그대로 사용. 비어있으면 OPENROUTER_API_KEY 환경변수로 폴백.
60
+ -> 워크플로우 JSON(공유/백업 시 유출 위험)에 실키를 박아둘 필요가 없어짐."""
61
+ key = (widget_value or "").strip()
62
+ if key:
63
+ return key
64
+ return os.environ.get("OPENROUTER_API_KEY", "").strip()
65
+
66
+
67
+ # ======================================================================
68
+ # 캐시 & 트리거 (검증된 로직 재사용)
69
+ # ======================================================================
70
+ def _load_cache():
71
+ if os.path.exists(CACHE_FILE):
72
+ try:
73
+ with open(CACHE_FILE, "r", encoding="utf-8") as f:
74
+ return json.load(f)
75
+ except (json.JSONDecodeError, OSError):
76
+ return {}
77
+ return {}
78
+
79
+
80
+ def _save_cache(cache):
81
+ try:
82
+ with open(CACHE_FILE, "w", encoding="utf-8") as f:
83
+ json.dump(cache, f, indent=4, ensure_ascii=False)
84
+ except OSError:
85
+ pass
86
+
87
+
88
+ def _file_sig(path):
89
+ st = os.stat(path)
90
+ return f"{os.path.basename(path)}::{st.st_size}::{int(st.st_mtime)}"
91
+
92
+
93
+ def _sha256(path):
94
+ h = hashlib.sha256()
95
+ with open(path, "rb") as f:
96
+ for block in iter(lambda: f.read(4096 * 1024), b""):
97
+ h.update(block)
98
+ return h.hexdigest()
99
+
100
+
101
+ # CivitAI 및 미러(civitaired) 해시 조회 엔드포인트. 앞에서부터 순서대로 시도.
102
+ TRIGGER_HASH_APIS = [
103
+ "https://civitai.com/api/v1/model-versions/by-hash/{h}",
104
+ "https://civitaired.com/api/v1/model-versions/by-hash/{h}",
105
+ ]
106
+
107
+ # LoRA Manager(comfyui-lora-manager)가 스캔 시 만들어두는 로컬 SQLite 캐시.
108
+ # sha256으로 trained_words를 즉시 조회 가능 -> 네트워크 호출 없이 트리거워드 확보.
109
+ LORA_MANAGER_DB = os.path.join(
110
+ os.environ.get("LOCALAPPDATA", ""), "ComfyUI-LoRA-Manager", "cache", "model", "comfyui.sqlite")
111
+
112
+
113
+ def _query_lora_manager_db(digest):
114
+ """LoRA Manager SQLite 캐시에서 sha256으로 trained_words 조회. 실패/미발견 시 None."""
115
+ if not digest or not os.path.exists(LORA_MANAGER_DB):
116
+ return None
117
+ try:
118
+ uri = f"file:{LORA_MANAGER_DB}?mode=ro"
119
+ con = sqlite3.connect(uri, uri=True, timeout=3)
120
+ try:
121
+ cur = con.cursor()
122
+ cur.execute(
123
+ "SELECT trained_words FROM models WHERE model_type='lora' AND sha256=? LIMIT 1",
124
+ (digest,))
125
+ row = cur.fetchone()
126
+ finally:
127
+ con.close()
128
+ if not row or not row[0]:
129
+ return None
130
+ tw = json.loads(row[0])
131
+ return tw if isinstance(tw, list) else None
132
+ except (sqlite3.Error, OSError, ValueError, json.JSONDecodeError) as e:
133
+ print(f"⚠️ [Director] LoRA Manager DB 조회 실패: {type(e).__name__}: {e}")
134
+ return None
135
+
136
+
137
+ def _query_trigger_api(url):
138
+ """단일 해시 조회 엔드포인트에서 trainedWords 리스트를 반환. 실패 시 ([], 사유)."""
139
+ try:
140
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
141
+ with urllib.request.urlopen(req, timeout=8) as r:
142
+ data = json.loads(r.read().decode('utf-8'))
143
+ return (data.get("trainedWords", []) or []), None
144
+ except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError,
145
+ OSError, ValueError, json.JSONDecodeError) as e:
146
+ return [], f"{type(e).__name__}: {e}"
147
+
148
+
149
+ def get_triggers(lora_name, cache):
150
+ if lora_name == "None":
151
+ return []
152
+ path = folder_paths.get_full_path("loras", lora_name)
153
+ if not path or not os.path.exists(path):
154
+ return []
155
+ try:
156
+ with safe_open(path, framework="pt", device="cpu") as f:
157
+ m = f.metadata()
158
+ if m and "modelspec.trigger_words" in m:
159
+ tw = json.loads(m["modelspec.trigger_words"])
160
+ if isinstance(tw, list):
161
+ return tw
162
+ if isinstance(tw, str):
163
+ return [tw]
164
+ except (OSError, ValueError, json.JSONDecodeError):
165
+ pass
166
+ try:
167
+ sig = _file_sig(path)
168
+ except OSError:
169
+ sig = lora_name
170
+ # 캐시에 '비어있지 않은' 결과가 있을 때만 신뢰. 과거에 실패로 저장된 빈 값은
171
+ # 무시하고 재조회한다 (빈 값 영구화 회귀 방지).
172
+ cached = cache.get(sig)
173
+ if cached:
174
+ return cached
175
+
176
+ # 해시는 1회만 계산해서 LoRA Manager DB -> CivitAI -> civitaired 순으로 조회.
177
+ result, base = [], os.path.basename(path)
178
+ try:
179
+ digest = _sha256(path)
180
+ except OSError as e:
181
+ print(f"⚠️ [Director] 해시 계산 실패({base}): {e}")
182
+ digest = None
183
+ if digest:
184
+ found = _query_lora_manager_db(digest)
185
+ if found:
186
+ print(f"✅ [Director] 트리거 조회 성공({base}) @ LoRA Manager DB: {found}")
187
+ result = found
188
+ if digest and not result:
189
+ for tmpl in TRIGGER_HASH_APIS:
190
+ host = tmpl.split('/')[2]
191
+ found, err = _query_trigger_api(tmpl.format(h=digest))
192
+ if found:
193
+ print(f"✅ [Director] 트리거 조회 성공({base}) @ {host}: {found}")
194
+ result = found
195
+ break
196
+ print(f"⚠️ [Director] 트리거 조회 실패({base}) @ {host}: {err or 'no trainedWords'}")
197
+
198
+ # 성공(비어있지 않음)일 때만 캐싱. 빈 결과는 저장하지 않아 다음 실행에서 재시도됨.
199
+ if result:
200
+ cache[sig] = result
201
+ _save_cache(cache)
202
+ return result
203
+
204
+
205
+ def apply_model_lora(model, lora_data, weight):
206
+ try:
207
+ return comfy.sd.load_lora_for_models(model, None, lora_data, weight, 0)[0]
208
+ except (AttributeError, TypeError):
209
+ new_model = model.clone()
210
+ key_map = comfy.lora.model_lora_keys_unet(new_model.model)
211
+ loaded = comfy.lora.load_lora(lora_data, key_map)
212
+ new_model.add_patches(loaded, weight)
213
+ return new_model
214
+
215
+
216
+ def tensor_to_base64(image_tensor, max_side=1024):
217
+ """ComfyUI IMAGE 텐서(B,H,W,C, 0~1 float) 첫 프레임을 JPEG base64 data URI로."""
218
+ if not _HAS_PIL:
219
+ print("❌ [Director] PIL(Pillow) 없음 - 비전 불가. `pip install Pillow` 필요.")
220
+ return None
221
+ if image_tensor is None:
222
+ print("❌ [Director] image 입력이 None - IMAGE 연결 확인.")
223
+ return None
224
+ try:
225
+ img = image_tensor
226
+ if hasattr(img, "cpu"):
227
+ img = img.cpu().numpy()
228
+ img = np.asarray(img)
229
+ if img.ndim == 4:
230
+ img = img[0]
231
+ arr = np.clip(img * 255.0, 0, 255).astype(np.uint8)
232
+ pil = Image.fromarray(arr)
233
+ w, h = pil.size
234
+ if max(w, h) > max_side:
235
+ scale = max_side / float(max(w, h))
236
+ pil = pil.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
237
+ buf = io.BytesIO()
238
+ pil.convert("RGB").save(buf, format="JPEG", quality=90)
239
+ b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
240
+ print(f"🖼 [Director] image encoded ok: {w}x{h} -> {pil.size}, {len(b64)//1024}KB base64")
241
+ return f"data:image/jpeg;base64,{b64}"
242
+ except Exception as e:
243
+ print(f"❌ [Director] image encode failed: {type(e).__name__}: {e}")
244
+ return None
245
+
246
+
247
+ # ======================================================================
248
+ # Vision Director: 이미지 인식 + 필수동작 실행 + 클립당 3로라
249
+ # ======================================================================
250
+ class DolphinVisionDirector:
251
+ """
252
+ 이미지 하나와 '필수 동작'과 로라만 넣으면 4개 연속 클립이 완성되는 올인원 노드.
253
+
254
+ 1) IMAGE -> 비전 VLM 이 상황(캐릭터 상태/자세/환경)만 앵커로 파악
255
+ 2) 그 맥락 위에서 '내가 준 필수 동작(essential_actions)'만 4샷으로 분배
256
+ 3) 클립마다 로라 3개씩 개별 적용(웨이트도 개별) + 트리거 자동 주입
257
+
258
+ 핵심: VLM 은 '묘사'가 아니라 '앵커 추출' 역할로 좁혀서 장황함을 억제한다.
259
+ 로라: c{클립}_lora_{n} (c=1..4, n=1..3), 각 슬롯 웨이트는 "base, high, low" 문자열.
260
+ 트리거워드는 사용된 로라에서 자동 추출되어 각 클립 프롬프트에 주입 + triggers 출력으로도 제공.
261
+ """
262
+
263
+ @classmethod
264
+ def INPUT_TYPES(s):
265
+ loras = ["None"] + folder_paths.get_filename_list("loras")
266
+ req = {
267
+ "image": ("IMAGE",),
268
+ "model_base": ("MODEL",),
269
+ "model_high": ("MODEL",),
270
+ "model_low": ("MODEL",),
271
+ "essential_actions": ("STRING", {"multiline": True,
272
+ "default": ("돌진한다, 검을 휘둘러 적을 쓰러뜨린다, 총알을 튕겨낸다, 숨통을 끊는다")}),
273
+ "character_name": ("STRING", {"default": "AUTO"}),
274
+ "artistic_vibe": ("STRING", {"multiline": True,
275
+ "default": "cinematic lighting, dynamic motion"}),
276
+ "pacing": (["auto", "slow-burn (1 beat)", "balanced (multi beat)", "rapid (4 distinct)"],
277
+ {"default": "auto"}),
278
+ "detail_level": (["짧게(간결)", "필수+앵커만", "상세"], {"default": "필수+앵커만"}),
279
+ "inject_triggers": ("BOOLEAN", {"default": True,
280
+ "label_on": "TRIGGERS ON", "label_off": "TRIGGERS OFF"}),
281
+ "use_vision": ("BOOLEAN", {"default": True,
282
+ "label_on": "VISION ON", "label_off": "VISION OFF (text only)"}),
283
+ "openrouter_api_key": ("STRING", {"default": ""}),
284
+ "vlm_preset": (VLM_PRESET_LABELS, {"default": "👁 Qwen2.5-VL 72B (특수상황·추천)"}),
285
+ "vlm_model": ("STRING", {"default": "qwen/qwen2.5-vl-72b-instruct"}),
286
+ "llm_preset": (LLM_PRESET_LABELS, {"default": "🔞 DeepSeek V3.2 (무삭제·JSON안정·추천)"}),
287
+ "llm_model": ("STRING", {"default": "deepseek/deepseek-v3.2"}),
288
+ "creativity": ("FLOAT", {"default": 0.7, "min": 0.1, "max": 1.5, "step": 0.05}),
289
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
290
+ }
291
+ opt = {}
292
+ # 클립당 로라 3개. 헤더 위젯 제거(편집칸이라 혼란). 이름을 명확히:
293
+ # c{클립}_lora_{n} (드롭다운) + c{클립}_w_{n} ("base, high, low")
294
+ for c in range(1, 5):
295
+ for n in range(1, 4):
296
+ opt[f"c{c}_lora_{n}"] = (loras, {"default": "None"})
297
+ opt[f"c{c}_w_{n}"] = ("STRING", {"default": "1.0, 1.0, 1.0", "multiline": False})
298
+ opt["manual_triggers"] = ("STRING", {"default": "", "multiline": True})
299
+ # 로컬 캡션 노드(JoyCaption 등)의 STRING 출력을 여기 연결하면 그걸 앵커로 사용하고
300
+ # OpenRouter VLM 호출을 완전히 건너뛴다 (429/거부 문제 회피). 비워두면 기존
301
+ # OpenRouter VLM 경로로 자동 폴백 (하위 호환).
302
+ opt["vision_caption"] = ("STRING", {"multiline": True, "default": "",
303
+ "forceInput": True})
304
+ return {"required": req, "optional": opt}
305
+
306
+ RETURN_TYPES = (
307
+ "MODEL", "MODEL", "MODEL", "STRING",
308
+ "MODEL", "MODEL", "MODEL", "STRING",
309
+ "MODEL", "MODEL", "MODEL", "STRING",
310
+ "MODEL", "MODEL", "MODEL", "STRING",
311
+ "STRING", "STRING", "STRING",
312
+ )
313
+ RETURN_NAMES = (
314
+ "c1_base", "c1_high", "c1_low", "c1_prompt",
315
+ "c2_base", "c2_high", "c2_low", "c2_prompt",
316
+ "c3_base", "c3_high", "c3_low", "c3_prompt",
317
+ "c4_base", "c4_high", "c4_low", "c4_prompt",
318
+ "triggers", "scene_analysis", "debug_info",
319
+ )
320
+ FUNCTION = "direct"
321
+ CATEGORY = "Dolphin"
322
+
323
+ # ------------------------------------------------------------------ helpers
324
+ def _build_final(self, action, master_scene, name, trigger_str):
325
+ head = ", ".join([p for p in [name, trigger_str] if p])
326
+ body = " ".join([p for p in [master_scene.strip().strip(",. "), action.strip()] if p])
327
+ if head and body:
328
+ return f"{head}\n{body}"
329
+ return head or body
330
+
331
+ def _pacing_hint(self, p):
332
+ return {
333
+ "auto": "Decide pacing yourself based on how many distinct beats the actions contain.",
334
+ "slow-burn (1 beat)": "One climactic beat: clips 1-3 build tension, clip 4 detonates.",
335
+ "balanced (multi beat)": "Early clips build, later clips deliver; keep motion continuous.",
336
+ "rapid (4 distinct)": "Four distinct consecutive actions, one per clip, each kinetic.",
337
+ }.get(p, "Decide pacing yourself.")
338
+
339
+ def _detail_hint(self, d):
340
+ return {
341
+ "짧게(간결)": "Keep each clip to a short punchy motion phrase. Pure movement verbs only.",
342
+ "필수+앵커만": "Each clip is one concise continuous motion. No description, only movement.",
343
+ "상세": "Motion may be described in more detail, but ONLY body movement — no scenery.",
344
+ }.get(d, "Motion only, no description.")
345
+
346
+ def _extract_json(self, text):
347
+ if not text:
348
+ return None
349
+ text = re.sub(r'```(?:json)?', '', text).strip()
350
+ a, b = text.find('{'), text.rfind('}')
351
+ if a == -1 or b <= a:
352
+ return None
353
+ blob = text[a:b + 1]
354
+ try:
355
+ return json.loads(blob)
356
+ except json.JSONDecodeError:
357
+ try:
358
+ return json.loads(re.sub(r',\s*([}\]])', r'\1', blob))
359
+ except json.JSONDecodeError:
360
+ return None
361
+
362
+ def _call_or(self, messages, model, key, temp, timeout=120, force_json=False):
363
+ payload = {"model": model.strip(), "messages": messages, "temperature": temp}
364
+ if force_json:
365
+ payload["response_format"] = {"type": "json_object"}
366
+ req = urllib.request.Request(
367
+ "https://openrouter.ai/api/v1/chat/completions",
368
+ data=json.dumps(payload).encode('utf-8'),
369
+ headers={'Authorization': f'Bearer {key}', 'Content-Type': 'application/json'})
370
+ raw = json.loads(urllib.request.urlopen(req, timeout=timeout).read().decode('utf-8'))
371
+ # OpenRouter는 실패해도 HTTP 200 + {"error": {...}} 를 줄 때가 있음.
372
+ # 그 경우 'choices' KeyError로 뭉개지 말고 진짜 원인을 드러낸다.
373
+ if "choices" not in raw:
374
+ err = raw.get("error") or raw
375
+ msg = err.get("message") if isinstance(err, dict) else str(err)
376
+ code = err.get("code") if isinstance(err, dict) else "?"
377
+ raise ValueError(f"API error (code={code}): {msg}")
378
+ return raw['choices'][0]['message']['content'].strip()
379
+
380
+
381
+ # ---- STEP 1: 비전으로 이미지 앵커 추출 ----
382
+ def _vision_analyze(self, data_uri, vlm_model, key, temp):
383
+ sys = ("You are a visual scene analyst for a video pipeline. "
384
+ "Look at the image and extract ONLY concrete anchors, no storytelling. "
385
+ "Report exactly these lines:\n"
386
+ "(1) character appearance & clothing.\n"
387
+ "(2) current pose/posture (sitting, crouched, standing, balance, limb positions).\n"
388
+ "(3) SUPPORT & CONTACT: what the character is on top of, sitting on, holding, "
389
+ "leaning against, or touching (e.g. 'seated balanced on a yoga ball', "
390
+ "'gripping a sword', 'kneeling on the floor'). This is critical for physics — "
391
+ "state it explicitly even if obvious.\n"
392
+ "(4) environment & lighting (brief).\n"
393
+ "Be terse, one short line each. English only. "
394
+ "Do not invent actions or narrative. If content is mature, describe it plainly and factually.")
395
+ messages = [
396
+ {"role": "system", "content": sys},
397
+ {"role": "user", "content": [
398
+ {"type": "text", "text": "Extract the anchors from this image."},
399
+ {"type": "image_url", "image_url": {"url": data_uri}},
400
+ ]},
401
+ ]
402
+ import time
403
+ last_err = "vision error"
404
+ for attempt in range(1, 4): # 429/일시 오류 대비 재시도 + 백오프
405
+ try:
406
+ resp = self._call_or(messages, vlm_model, key, temp, timeout=90).strip()
407
+ if not resp:
408
+ print(f"⚠️ [Director] VLM({vlm_model}) 응답이 비었음 (거부 or 빈 반환)")
409
+ return "", "vision empty response"
410
+ print(f"👁 [Director] VLM({vlm_model}) anchors (attempt {attempt}):\n{resp[:300]}")
411
+ return resp, None
412
+ except urllib.error.HTTPError as e:
413
+ body = ""
414
+ try:
415
+ body = e.read().decode('utf-8')[:200]
416
+ except Exception:
417
+ pass
418
+ print(f"❌ [Director] VLM HTTP {e.code} (attempt {attempt}): {body}")
419
+ last_err = f"vision HTTP {e.code}"
420
+ # 429(레이트리밋)/503(과부하)만 재시도. 그 외는 즉시 포기.
421
+ if e.code in (429, 503) and attempt < 3:
422
+ wait = 2 ** attempt # 2s, 4s
423
+ print(f"⏳ [Director] rate limited, {wait}s 후 재시도...")
424
+ time.sleep(wait)
425
+ continue
426
+ return "", last_err
427
+ except (urllib.error.URLError, TimeoutError, KeyError, ValueError) as e:
428
+ print(f"❌ [Director] VLM error (attempt {attempt}): {type(e).__name__}: {e}")
429
+ last_err = f"vision error: {e}"
430
+ if attempt < 3:
431
+ time.sleep(2 ** attempt)
432
+ continue
433
+ return "", last_err
434
+ return "", last_err
435
+
436
+ # ---- STEP 2: 앵커 + 필수동작 -> 4샷 JSON ----
437
+ def _fallback_name(self, anchors):
438
+ """LLM이 이름을 안 줬을 때 앵커 텍스트의 민족/설정 단서로 이름 선택."""
439
+ import random
440
+ a = (anchors or "").lower()
441
+ east = ["asian", "east asian", "japanese", "korean", "chinese", "anime",
442
+ "kimono", "hanbok", "qipao", "oriental"]
443
+ western = ["western", "european", "caucasian", "american", "blonde", "redhead"]
444
+ east_names = ["Yuna", "Kaede", "Jin", "Mei", "Haruka", "Rin", "Sora", "Aoi"]
445
+ west_names = ["Elena", "Ryan", "Ava", "Lucas", "Mila", "Ethan", "Nora", "Leo"]
446
+ if any(k in a for k in east):
447
+ pool = east_names
448
+ elif any(k in a for k in western):
449
+ pool = west_names
450
+ else:
451
+ pool = east_names + west_names # 단서 없으면 혼합
452
+ return random.choice(pool)
453
+
454
+ def _split_actions_to_clips(self, actions):
455
+ """동작을 파싱해 4클립에 순서대로 배정. 마지막 동작은 반드시 clip4.
456
+ 반환: {1:[동작...], 2:[...], 3:[...], 4:[...]}"""
457
+ parts = re.split(r'(?<=[.!?。])\s+|[,、]|\n+', actions)
458
+ parts = [p.strip() for p in parts if len(p.strip()) > 1]
459
+ if not parts:
460
+ parts = [actions.strip()]
461
+ n = len(parts)
462
+ assign = {1: [], 2: [], 3: [], 4: []}
463
+ if n == 1:
464
+ # 단일 동작: 1~3 준비/전개, 4 완결
465
+ assign[1] = [f"(build-up toward) {parts[0]}"]
466
+ assign[2] = [f"(begin) {parts[0]}"]
467
+ assign[3] = [f"(escalate) {parts[0]}"]
468
+ assign[4] = [parts[0]]
469
+ elif n <= 4:
470
+ # 마지막 동작 -> clip4 고정, 나머지를 앞에서부터
471
+ assign[4] = [parts[-1]]
472
+ head = parts[:-1]
473
+ for i, ph in enumerate(head):
474
+ assign[min(i + 1, 3)].append(ph)
475
+ # 빈 앞클립은 다음 동작 빌드업
476
+ for c in range(1, 4):
477
+ if not assign[c]:
478
+ nxt = parts[min(c, n - 1)]
479
+ assign[c] = [f"(move toward) {nxt}"]
480
+ else:
481
+ # 5개+ : 마지막은 clip4 단독, 나머지 앞 3클립 균등
482
+ assign[4] = [parts[-1]]
483
+ rest = parts[:-1]
484
+ k, m = divmod(len(rest), 3)
485
+ idx = 0
486
+ for c in range(1, 4):
487
+ end = idx + k + (1 if (c - 1) < m else 0)
488
+ assign[c] = rest[idx:end]
489
+ idx = end
490
+ return assign
491
+
492
+ def _plan_shots(self, anchors, actions, name, vibe, pacing, detail, llm_model, key, temp, seed):
493
+ # 코드가 동작을 클립별로 미리 배정 -> LLM 지시에 못박음 (순서 강제)
494
+ assign = self._split_actions_to_clips(actions)
495
+ assign_block = "\n".join(
496
+ f" clip_{c}: {', '.join(assign[c]) if assign[c] else '(continue previous motion)'}"
497
+ for c in range(1, 5))
498
+
499
+ sys = (
500
+ "You are an elite fight/action choreographer for a 20-second video made of "
501
+ "four consecutive 5-second clips. You are given SCENE ANCHORS (the character's "
502
+ "starting look/pose/place from an image) and a STRICT per-clip action assignment.\n"
503
+ "\n"
504
+ "ABSOLUTE RULES:\n"
505
+ "1) FOLLOW THE ASSIGNMENT EXACTLY. Each clip animates ONLY the action(s) assigned "
506
+ "to it below. You MUST NOT move a later clip's action into an earlier clip. "
507
+ "clip_1 must NOT contain clip_4's action. The final action happens ONLY in clip_4. "
508
+ "This is the most important rule - violating the order is a failure. "
509
+ "(ONE exception: clip_1 may prepend a brief transition OUT of the starting physics from "
510
+ "rule 2 — e.g. leaving the yoga ball — before its assigned action. This is not borrowing "
511
+ "a later action; it is grounding the first one.)\n"
512
+ "2) STARTING PHYSICS (use the anchors here). Read the character's CURRENT pose AND "
513
+ "what they are supported by / in contact with from the SCENE ANCHORS (e.g. seated on a "
514
+ "yoga ball, kneeling, gripping a weapon, leaning on a wall). clip_1 MUST begin the first "
515
+ "action FROM that exact physical situation and honor its constraints. If the support is "
516
+ "unstable or unusual (a yoga ball, a ledge, a moving surface), the motion MUST account "
517
+ "for it — e.g. anchor 'seated balanced on a yoga ball' -> clip_1 = 'pushing off the "
518
+ "wobbling ball and rising to their feet as it rolls away' BEFORE any further action. "
519
+ "Never ignore or contradict the starting support (do not stand if seated on the ball "
520
+ "without first leaving it; do not assume a weapon is drawn if the anchor shows it "
521
+ "sheathed). This makes the motion physically continuous with the input image. Reference "
522
+ "the support ONLY to ground how the motion STARTS — do NOT describe its appearance, "
523
+ "color, or the wider environment.\n"
524
+ "3) ACTION ONLY. Write physical body movement. NO scenery, NO lighting, NO clothing, "
525
+ "NO mood, NO camera talk. If a word is not a movement or body part, delete it. "
526
+ "(Referring to the starting pose in clip_1 per rule 2 is allowed since it describes "
527
+ "how the body moves.)\n"
528
+ "4) FILL 5 SECONDS. Expand the assigned action into a single continuous flow of motion "
529
+ "that occupies the whole clip (use 'then', 'immediately', 'without pausing'). "
530
+ "Do not borrow the next action to fill time - elaborate the CURRENT action instead.\n"
531
+ "5) ANTI-SLOW-MOTION: real-time speed, kinetic adverbs (swiftly, rapidly, explosively, "
532
+ "in a split second). NO slow motion, NO freeze, NO holding a pose.\n"
533
+ "6) CHARACTER NAME: study the SCENE ANCHORS. Judge the character's apparent ethnicity/"
534
+ "setting (East Asian, Western, etc.) and INVENT a specific fitting first name that "
535
+ "matches it (e.g. East Asian -> 'Yuna','Kaede','Jin'; Western -> 'Elena','Ryan'). "
536
+ "NEVER output 'AUTO' or an empty name. If a NAME is given below (not 'AUTO'), use it.\n"
537
+ f"7) {detail}\n"
538
+ "8) MOTION ENERGY: a MOTION STYLE cue is given below (e.g. 'cinematic lighting, dynamic "
539
+ "motion'). Use it ONLY to calibrate how forceful/graceful/frantic the movement FEELS "
540
+ "(word choice, verb intensity). Do NOT quote it and do NOT let any scenery/lighting/mood "
541
+ "words from it leak into the output — rule 3 still applies.\n"
542
+ "\n"
543
+ "master_scene: leave it EMPTY.\n"
544
+ "\n"
545
+ 'OUTPUT ONLY JSON: {"character":"<a real name, never AUTO>","master_scene":"",'
546
+ '"clip_1":"<only clip_1 action, continuous & fast>",'
547
+ '"clip_2":"<only clip_2 action>","clip_3":"<only clip_3 action>",'
548
+ '"clip_4":"<only clip_4 action, the finisher>"}'
549
+ )
550
+ usr = (f"NAME: {name}\nPACING: {self._pacing_hint(pacing)}\n\n"
551
+ f"MOTION STYLE (kinetic energy cue only, per rule 8 — do not describe scenery/"
552
+ f"lighting/mood from this): {vibe.strip() or '(none)'}\n\n"
553
+ f"SCENE ANCHORS (use the POSE to start clip_1's motion per rule 2; "
554
+ f"use overall look only for naming — do NOT copy look into the clips):\n"
555
+ f"{anchors or '(none — assume a neutral ready stance)'}\n\n"
556
+ f"STRICT PER-CLIP ACTION ASSIGNMENT (animate ONLY what is listed per clip, "
557
+ f"in this order, finisher in clip_4):\n{assign_block}\n")
558
+ messages = [{"role": "system", "content": sys}, {"role": "user", "content": usr}]
559
+ t = temp
560
+ last = "⚠️ plan failed (no attempts ran)"
561
+ for attempt in (1, 2):
562
+ try:
563
+ content = self._call_or(messages, llm_model, key, t, force_json=True)
564
+ parsed = self._extract_json(content)
565
+ if parsed and all(parsed.get(f"clip_{i}") for i in range(1, 5)):
566
+ return parsed, f"✅ plan ok (attempt {attempt})"
567
+ last = f"⚠️ incomplete JSON (attempt {attempt}): {content[:150]!r}"
568
+ print(f"⚠️ [Director] plan attempt {attempt} returned incomplete JSON: {content[:300]}")
569
+ except urllib.error.HTTPError as e:
570
+ body = ""
571
+ try:
572
+ body = e.read().decode('utf-8')[:200]
573
+ except Exception:
574
+ pass
575
+ last = f"❌ plan HTTP {e.code} (attempt {attempt}): {body}"
576
+ print(f"❌ [Director] plan {last}")
577
+ except (urllib.error.URLError, TimeoutError, KeyError, ValueError) as e:
578
+ last = f"❌ plan error (attempt {attempt}): {type(e).__name__}: {e}"
579
+ print(f"❌ [Director] {last}")
580
+ t = min(temp, 0.4)
581
+ return None, last
582
+
583
+ def _local_split(self, actions):
584
+ """LLM 폴백: 동작 순서 유지, 마지막 동작은 뒤 클립(3후반/4)에. 타임코드 없이
585
+ 연속 동작 + 실시간 속도 어휘로 슬로우모션을 억제한다."""
586
+ parts = re.split(r'(?<=[.!?。])\s+|[,、]|\n+', actions)
587
+ parts = [p.strip() for p in parts if len(p.strip()) > 1]
588
+ if not parts:
589
+ parts = [actions.strip()]
590
+ n = len(parts)
591
+
592
+ def fast(phrase, lead="swiftly"):
593
+ p = phrase.strip()
594
+ return f"{lead} {p}, continuous real-time motion, no slow motion" if p else ""
595
+
596
+ if n == 1:
597
+ a = parts[0]
598
+ out = [
599
+ f"rapidly moves into position toward {a}, no pause",
600
+ f"immediately begins {a}, brisk continuous motion, no slow motion",
601
+ f"presses {a} without stopping, fast decisive movement",
602
+ f"explosively completes {a} at real-time speed, no slow motion, no freeze",
603
+ ]
604
+ elif n <= 4:
605
+ out = ["", "", "", ""]
606
+ out[3] = fast(parts[-1], "explosively")
607
+ head = parts[:-1]
608
+ for i, ph in enumerate(head):
609
+ s = min(i, 2)
610
+ out[s] = (out[s] + ", then " + fast(ph)).strip(", ") if out[s] else fast(ph)
611
+ for i in range(3):
612
+ if not out[i]:
613
+ key = parts[min(i, n - 1)]
614
+ out[i] = f"rapidly moves toward {key}, brisk continuous motion, no slow motion"
615
+ else:
616
+ k, m = divmod(n - 1, 3)
617
+ idx, chunks = 0, []
618
+ for i in range(3):
619
+ end = idx + k + (1 if i < m else 0)
620
+ seg = parts[idx:end]
621
+ idx = end
622
+ chunks.append(", then ".join(fast(p) for p in seg) if seg else "")
623
+ out = chunks + [fast(parts[-1], "explosively")]
624
+ return out[:4]
625
+
626
+ # ---- lora ----
627
+ def _load(self, name):
628
+ return comfy.utils.load_torch_file(folder_paths.get_full_path("loras", name))
629
+
630
+ @staticmethod
631
+ def _parse_weights(text):
632
+ """'base, high, low' 문자열 -> (b, h, l) float. 값이 모자라면 마지막 값으로 채움.
633
+ 한 개만 주면 세 스테이지 모두 동일. 빈 문자열/파싱 실패는 (1,1,1)."""
634
+ try:
635
+ nums = [float(x) for x in re.split(r'[,\s]+', text.strip()) if x != ""]
636
+ except (ValueError, AttributeError):
637
+ nums = []
638
+ if not nums:
639
+ return 1.0, 1.0, 1.0
640
+ if len(nums) == 1:
641
+ return nums[0], nums[0], nums[0]
642
+ if len(nums) == 2:
643
+ return nums[0], nums[1], nums[1]
644
+ return nums[0], nums[1], nums[2]
645
+
646
+ def _patch_clip(self, mb, mh, ml, slots, cache, inject):
647
+ """slots: [(lora_name, weights_str), ...]. weights_str='base,high,low'.
648
+ 트리거는 inject 여부와 무관하게 항상 조회한다(triggers 출력 포트용). inject는
649
+ 각 클립 프롬프트 텍스트에 트리거를 넣을지만 결정한다."""
650
+ cb, ch, cl = mb, mh, ml
651
+ used = []
652
+ for (name, w_str) in slots:
653
+ if name == "None":
654
+ continue
655
+ w_b, w_h, w_l = self._parse_weights(w_str)
656
+ if w_b == 0.0 and w_h == 0.0 and w_l == 0.0:
657
+ continue
658
+ data = self._load(name)
659
+ if w_b != 0.0:
660
+ cb = apply_model_lora(cb, data, w_b)
661
+ if w_h != 0.0:
662
+ ch = apply_model_lora(ch, data, w_h)
663
+ if w_l != 0.0:
664
+ cl = apply_model_lora(cl, data, w_l)
665
+ used.append(name)
666
+ trig = []
667
+ trig_dbg = []
668
+ # 조회는 항상 수행 (used 로라 기준). 회귀 원인이던 inject 게이팅 제거.
669
+ for n in dict.fromkeys(used):
670
+ found = get_triggers(n, cache)
671
+ trig.extend(found)
672
+ trig_dbg.append(f"{os.path.basename(n)}:{len(found)}")
673
+ self._last_trig_dbg = trig_dbg # 콘솔 디버그용
674
+ trig_str = ", ".join(dict.fromkeys(t for t in trig if t))
675
+ # inject가 꺼져 있으면 프롬프트 텍스트에는 넣지 않지만, 조회 결과(trig_str)는
676
+ # 항상 반환해 triggers 출력 포트로 나가게 한다.
677
+ prompt_trig = trig_str if inject else ""
678
+ return cb, ch, cl, prompt_trig, trig_str
679
+
680
+ # ------------------------------------------------------------------ main
681
+ def direct(self, image, model_base, model_high, model_low, essential_actions,
682
+ character_name, artistic_vibe, pacing, detail_level, inject_triggers,
683
+ use_vision, openrouter_api_key, vlm_preset, vlm_model, llm_preset, llm_model,
684
+ creativity, seed, **kw):
685
+
686
+ vlm = resolve_slug(VLM_PRESETS, vlm_preset, vlm_model, "qwen/qwen2.5-vl-72b-instruct")
687
+ llm = resolve_slug(LLM_PRESETS, llm_preset, llm_model, "deepseek/deepseek-v3.2")
688
+ user_name = "" if character_name.strip().upper() in ["AUTO", ""] else character_name.strip()
689
+ key = resolve_api_key(openrouter_api_key)
690
+
691
+ # ---- STEP 1: 비전 ----
692
+ # 우선순위: 1) vision_caption 입력(JoyCaption 등 로컬 노드 STRING) 있으면 그걸 그대로
693
+ # 앵커로 사용 - OpenRouter VLM 호출 자체를 건너뛴다 (429/거부 회피).
694
+ # 2) 없으면 기존 OpenRouter VLM 경로로 폴백 (하위 호환).
695
+ vision_caption = str(kw.get("vision_caption", "") or "").strip()
696
+ anchors, dbg_v = "", ""
697
+ if vision_caption:
698
+ anchors = vision_caption
699
+ dbg_v = "vision ok (local caption node)"
700
+ print(f"👁 [Director] 로컬 캡션 입력 사용 (JoyCaption 등):\n{anchors[:300]}")
701
+ elif not use_vision:
702
+ dbg_v = "vision OFF (토글 확인)"
703
+ print("ℹ️ [Director] use_vision=OFF - 비전 건너뜀")
704
+ elif not key:
705
+ dbg_v = "no api key"
706
+ print("❌ [Director] openrouter_api_key 비어있음 (위젯도, OPENROUTER_API_KEY 환경변수도 없음) - 비전/LLM 불가")
707
+ elif image is None:
708
+ dbg_v = "no image"
709
+ print("❌ [Director] image 입력 없음 - IMAGE 연결 확인")
710
+ else:
711
+ data_uri = tensor_to_base64(image)
712
+ if data_uri:
713
+ anchors, err = self._vision_analyze(data_uri, vlm, key, min(creativity, 0.5))
714
+ dbg_v = err or "vision ok"
715
+ else:
716
+ dbg_v = "image encode failed / PIL missing"
717
+ if not vision_caption and use_vision and key and image is not None and not anchors:
718
+ print(f"⚠️ [Director] 비전 앵커가 비었음 -> 이름/씬 근거 없음. 원인: {dbg_v}")
719
+
720
+ # ---- STEP 2: 4샷 분배 ----
721
+ if key:
722
+ parsed, dbg_p = self._plan_shots(anchors, essential_actions, character_name,
723
+ artistic_vibe, pacing, self._detail_hint(detail_level),
724
+ llm, key, creativity, seed)
725
+ if parsed:
726
+ clips = [str(parsed[f"clip_{i}"]).strip() for i in range(1, 5)]
727
+ master = str(parsed.get("master_scene", "")).strip()
728
+ cand = str(parsed.get("character", "")).strip()
729
+ # LLM 이 AUTO/빈값 반환 시 앵커 기반 폴백 작명
730
+ if not cand or cand.upper() == "AUTO":
731
+ cand = self._fallback_name(anchors)
732
+ final_name = user_name or cand
733
+ else:
734
+ clips = self._local_split(essential_actions)
735
+ master = anchors.replace("\n", " ")[:160]
736
+ final_name = user_name or self._fallback_name(anchors)
737
+ else:
738
+ parsed, dbg_p = None, "no api key"
739
+ clips = self._local_split(essential_actions)
740
+ master = anchors.replace("\n", " ")[:160]
741
+ final_name = user_name
742
+
743
+ # ---- STEP 3: 클립별 로라 3개 패치 + 트리거 주입 ----
744
+ cache = _load_cache()
745
+ manual_list = [t.strip() for t in kw.get("manual_triggers", "").split(',') if t.strip()]
746
+ outs = []
747
+ all_trigs_collected = [] # 전 클립 트리거 통합 (triggers 출력용)
748
+ for c in range(1, 5):
749
+ slots = [(kw.get(f"c{c}_lora_{n}", "None"),
750
+ kw.get(f"c{c}_w_{n}", "1.0, 1.0, 1.0")) for n in range(1, 4)]
751
+ cb, ch, cl, prompt_trig, always_trig = self._patch_clip(
752
+ model_base, model_high, model_low, slots, cache, inject_triggers)
753
+ # 프롬프트에 넣는 트리거: inject ON일 때만 (+manual)
754
+ all_trig = ", ".join(dict.fromkeys(
755
+ ([prompt_trig] if prompt_trig else []) + manual_list)) \
756
+ if (prompt_trig or manual_list) else ""
757
+ # triggers 출력 포트: inject 무관하게 항상 수집
758
+ if always_trig:
759
+ all_trigs_collected.extend(t.strip() for t in always_trig.split(',') if t.strip())
760
+ prompt = self._build_final(clips[c - 1], master, final_name, all_trig)
761
+ outs.extend([cb, ch, cl, prompt])
762
+ print(f" [clip{c}] loras/triggers: {getattr(self, '_last_trig_dbg', [])} "
763
+ f"inject={inject_triggers}")
764
+
765
+ # 통합 트리거 출력 (클립별 로라 트리거 + manual, 중복 제거)
766
+ triggers_out = ", ".join(dict.fromkeys(
767
+ [t for t in all_trigs_collected if t] + manual_list))
768
+
769
+ debug = f"vlm={dbg_v} | plan={dbg_p} | char={final_name}"
770
+ print(f"\n🎬 [Dolphin Vision Director] {debug}\nANCHORS: {anchors[:120]}")
771
+ for i in range(4):
772
+ print(f" CLIP {i+1}: {outs[i*4+3][:90]}")
773
+
774
+ return tuple(outs) + (triggers_out, anchors, debug)
775
+
776
+
777
+ NODE_CLASS_MAPPINGS = {"DolphinVisionDirector": DolphinVisionDirector}
778
+ NODE_DISPLAY_NAME_MAPPINGS = {"DolphinVisionDirector": "🎬👁 Dolphin Vision Director (Image→4Clips)"}
_archive/nodes_unregistered/old/promp_auto_sequence.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import re
4
+ import json
5
+ import urllib.request
6
+
7
+ class DolphinAutoSequencerNode:
8
+ @classmethod
9
+ def INPUT_TYPES(s):
10
+ return {
11
+ "required": {
12
+ "mode": (["🤖 Auto LLM (Timeline)", "✍️ Manual Override (수동)"], {"default": "🤖 Auto LLM (Timeline)"}),
13
+ "character_name": ("STRING", {"multiline": False, "default": "AUTO"}),
14
+ "tagger_context": ("STRING", {"multiline": True, "forceInput": True, "default": ""}),
15
+ "artistic_vibe": ("STRING", {"multiline": True, "default": "cinematic lighting, dynamic motion"}),
16
+
17
+ # 🔥 [핵심] 시간 입력 없이 엔터키로만 액션을 나열합니다. 3줄이든 10줄이든 무한정 쓸 수 있습니다!
18
+ "action_sequence": ("STRING", {
19
+ "multiline": True,
20
+ "default": "방문을 열고 조심스럽게 들어온다.\n갑자기 나타난 적을 보고 놀라 뒷걸음질 친다.\n재빠르게 허리에서 권총을 뽑아 든다.\n적을 향해 두 발을 발사한다."
21
+ }),
22
+
23
+ # ⏱️ 한 줄당 몇 초를 배정할지, 영상의 프레임레이트는 몇인지 세팅합니다.
24
+ "seconds_per_line": ("INT", {"default": 5, "min": 1, "max": 30, "step": 1}),
25
+ "video_fps": ("INT", {"default": 24, "min": 8, "max": 120, "step": 1}),
26
+
27
+ "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}),
28
+ "openrouter_model": ("STRING", {"multiline": False, "default": "meta-llama/llama-3.3-70b-instruct"}),
29
+ "creativity": ("FLOAT", {"default": 0.85, "min": 0.1, "max": 1.5, "step": 0.05}),
30
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
31
+ },
32
+ }
33
+
34
+ # 📤 [출력] FizzNodes 등 스케줄러 노드에 바로 꽂을 수 있는 단일 문자열 1개와 확인용 텍스트를 내보냅니다.
35
+ RETURN_TYPES = ("STRING", "STRING")
36
+ RETURN_NAMES = ("Keyframe_Dict (스케줄러용)", "Raw_Text (로그 확인용)")
37
+ FUNCTION = "generate_timeline"
38
+ CATEGORY = "Dolphin"
39
+
40
+ def generate_timeline(self, mode, character_name, tagger_context, artistic_vibe, action_sequence, seconds_per_line, video_fps, openrouter_api_key, openrouter_model, creativity, seed):
41
+
42
+ # 1. 입력받은 시퀀스를 '엔터키(\n)' 기준으로 쪼개고, 몇 줄인지 계산합니다.
43
+ raw_lines = [line.strip() for line in action_sequence.split('\n') if line.strip()]
44
+ num_actions = len(raw_lines)
45
+
46
+ if num_actions == 0:
47
+ return ("\"0\": \"\"", "입력된 액션이 없습니다.")
48
+
49
+ user_defined_name = "" if character_name.upper() in ["AUTO", ""] else character_name.strip()
50
+
51
+ def build_final(action, raw_tags, master_scene, name):
52
+ parts = []
53
+ if name: parts.append(name)
54
+ if raw_tags.strip(): parts.append(raw_tags.strip().strip(",. "))
55
+ if master_scene.strip(): parts.append(master_scene.strip().strip(",. "))
56
+ prefix = ", ".join(parts)
57
+ return f"{prefix}, {action.strip()}" if action.strip() else prefix
58
+
59
+ # 🟢 [수동 모드] LLM을 거치지 않고 바로 프레임 계산 후 송출
60
+ if mode == "✍️ Manual Override (수동)":
61
+ keyframe_out, raw_out = [], []
62
+ for idx, line in enumerate(raw_lines):
63
+ frame_num = idx * seconds_per_line * video_fps
64
+ final_prompt = build_final(line, tagger_context, "", user_defined_name)
65
+ keyframe_out.append(f'"{frame_num}": "{final_prompt}"')
66
+ raw_out.append(f"[{frame_num} Frame] {final_prompt}")
67
+ return (",\n".join(keyframe_out), "\n".join(raw_out))
68
+
69
+ # 🔵 [자동 LLM 모드]
70
+ random.seed(seed)
71
+
72
+ # LLM에게 보낼 번호 매기기 (내부적으로만 처리)
73
+ actions_formatted = "\n".join([f"ACTION {i+1}: {line}" for i, line in enumerate(raw_lines)])
74
+
75
+ # 유저가 쓴 줄 개수(num_actions)만큼 동적으로 파트(PART)를 쪼개라고 LLM에게 명령
76
+ sys_prompt = (
77
+ f"You are an Elite Cinematic Action Choreographer.\n"
78
+ f"1. CHARACTER: If NAME is 'AUTO', assign a culturally appropriate name based on TAGS. If '{user_defined_name}', use it.\n"
79
+ f"2. INITIAL POSTURE: Analyze TAGS for starting physical state. 1 short sentence.\n"
80
+ f"3. MASTER SCENE: 1-sentence environment description based on TAGS and VIBE.\n"
81
+ f"4. CHOREOGRAPHY: You will receive {num_actions} basic actions. EXPAND each into highly detailed, physical, kinetic choreography. Maintain logical flow. No abstract emotions.\n"
82
+ f"Format EXACTLY like this:\nCHARACTER: [Name]\nINITIAL POSTURE: [State]\nMASTER SCENE: [Description]\n"
83
+ )
84
+ for i in range(num_actions):
85
+ sys_prompt += f"PART {i+1}: [Action]\n"
86
+
87
+ usr_prompt = f"NAME: {character_name}\nTAGS: {tagger_context}\nVIBE: {artistic_vibe}\n{actions_formatted}"
88
+
89
+ url = "https://openrouter.ai/api/v1/chat/completions"
90
+ payload = {"model": openrouter_model.strip(), "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": usr_prompt}], "temperature": creativity}
91
+
92
+ llm_prompt = ""
93
+ try:
94
+ req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers={'Authorization': f'Bearer {openrouter_api_key}', 'Content-Type': 'application/json'})
95
+ response = urllib.request.urlopen(req, timeout=120)
96
+ llm_prompt = json.loads(response.read().decode('utf-8'))['choices'][0]['message']['content'].strip()
97
+ print(f"\n✅ [Dolphin Auto-Sequencer]\n{llm_prompt}\n")
98
+ except Exception as e:
99
+ print(f"❌ [에러] {e}")
100
+
101
+ final_char_name = user_defined_
_archive/nodes_unregistered/old/promp_logic_v30_story.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import re
4
+ import json
5
+ import urllib.request
6
+ import urllib.error
7
+
8
+ class DolphinMultiActionPromptNode_V30:
9
+ @classmethod
10
+ def INPUT_TYPES(s):
11
+ return {
12
+ "required": {
13
+ "mode": (["🤖 Auto LLM (Story Splitter)", "✍️ Manual Override (수동)"], {"default": "🤖 Auto LLM (Story Splitter)"}),
14
+ "character_name": ("STRING", {"multiline": False, "default": "AUTO"}),
15
+ "tagger_context": ("STRING", {"multiline": True, "default": ""}),
16
+ "artistic_vibe": ("STRING", {"multiline": True, "default": "cinematic lighting, dynamic motion"}),
17
+
18
+ "master_story": ("STRING", {
19
+ "multiline": True,
20
+ "default": "어두운 골목길. 갑자기 나타난 적들을 향해 돌진한다, 화려하게 검을 휘둘러 적을 쓰러뜨린다, 날아오는 총알을 튕겨낸다, 적에게 다가가 숨통을 끊는다."
21
+ }),
22
+
23
+ "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}),
24
+ "openrouter_model": ("STRING", {"multiline": False, "default": "meta-llama/llama-3.3-70b-instruct"}),
25
+ "creativity": ("FLOAT", {"default": 0.85, "min": 0.1, "max": 1.5, "step": 0.05}),
26
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
27
+ },
28
+ }
29
+
30
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
31
+ RETURN_NAMES = ("prompt_1 (Clip 1: 0-5s)", "prompt_2 (Clip 2: 0-5s)", "prompt_3 (Clip 3: 0-5s)", "prompt_4 (Clip 4: 0-5s)")
32
+ FUNCTION = "generate_sequence"
33
+ CATEGORY = "Dolphin"
34
+
35
+ def generate_sequence(self, mode, character_name, tagger_context, artistic_vibe, master_story, openrouter_api_key, openrouter_model, creativity, seed):
36
+
37
+ user_defined_name = "" if character_name.upper() in ["AUTO", ""] else character_name.strip()
38
+
39
+ def build_final(action, raw_tags, master_scene, name):
40
+ tags_list = []
41
+ if name: tags_list.append(name)
42
+ if raw_tags.strip(): tags_list.append(raw_tags.strip().strip(",. "))
43
+ tag_block = ", ".join(tags_list)
44
+
45
+ sentence_list = []
46
+ if master_scene.strip(): sentence_list.append(master_scene.strip().strip(",. "))
47
+ if action.strip(): sentence_list.append(action.strip())
48
+ sentence_block = " ".join(sentence_list)
49
+
50
+ if tag_block and sentence_block:
51
+ return f"{tag_block}\n{sentence_block}"
52
+ elif tag_block:
53
+ return tag_block
54
+ else:
55
+ return sentence_block
56
+
57
+ if mode == "✍️ Manual Override (수동)":
58
+ return (
59
+ build_final(master_story, tagger_context, "", user_defined_name),
60
+ build_final(master_story, tagger_context, "", user_defined_name),
61
+ build_final(master_story, tagger_context, "", user_defined_name),
62
+ build_final(master_story, tagger_context, "", user_defined_name)
63
+ )
64
+
65
+ random.seed(seed)
66
+
67
+ # 1차: 마침표, 쉼표, 느낌표, 줄바꿈 기준으로 확실하게 쪼갬
68
+ clean_story = re.sub(r'([.!?,\n])', r'\1|', master_story)
69
+ raw_sentences = clean_story.split('|')
70
+ sentences = [s.strip() for s in raw_sentences if len(s.strip()) > 1]
71
+
72
+ # 🔥 [핵심 로직] 문장 개수에 따른 '시네마틱 호흡(Pacing)' 자동 설계
73
+
74
+ if len(sentences) == 1:
75
+ # 단 1개의 문장: 1~3번은 분위기 빌드업, 4번에 폭발!
76
+ chunk_1 = f"Atmospheric Build-up: Focus STRICTLY on the environment, lighting, and vibe. Character is mostly still. DO NOT execute the main action yet. (Upcoming target: {sentences[0]})"
77
+ chunk_2 = f"Rising Tension: Subtle character movements or camera panning. Anticipation builds. DO NOT execute the main action yet. (Upcoming target: {sentences[0]})"
78
+ chunk_3 = f"Pre-action Wind-up: Extreme tension right before the strike. Character prepares to move. DO NOT execute yet. (Upcoming target: {sentences[0]})"
79
+ chunk_4 = f"CLIMAX: Explosively and swiftly execute this exact action -> {sentences[0]}"
80
+
81
+ elif len(sentences) == 2:
82
+ # 2개의 문장: 1번 빌드업 -> 2번 첫 액션 -> 3번 호흡 고르기 -> 4번 피니시
83
+ chunk_1 = f"Atmospheric Build-up: Focus strictly on the environment and tension. DO NOT execute the action yet. (Upcoming target: {sentences[0]})"
84
+ chunk_2 = f"Phase 1 Action: Execute this action -> {sentences[0]}"
85
+ chunk_3 = f"Tension / Transition: Character prepares for the final blow. (Upcoming target: {sentences[1]})"
86
+ chunk_4 = f"CLIMAX Action: Explosively execute this action -> {sentences[1]}"
87
+
88
+ elif len(sentences) == 3:
89
+ # 3개의 문장: 첫 문장을 10초(1,2번 파트)에 걸쳐 부드럽게 배분
90
+ chunk_1 = f"Phase 1: Start this action slowly -> {sentences[0]}"
91
+ chunk_2 = f"Phase 2: Complete this action -> {sentences[0]}"
92
+ chunk_3 = sentences[1]
93
+ chunk_4 = sentences[2]
94
+
95
+ else:
96
+ # 4개 이상: 기존의 수학적 4등분 배분
97
+ k, m = divmod(len(sentences), 4)
98
+ chunks = []
99
+ start = 0
100
+ for i in range(4):
101
+ end = start + k + (1 if i < m else 0)
102
+ chunks.append(" ".join(sentences[start:end]))
103
+ start = end
104
+ chunk_1, chunk_2, chunk_3, chunk_4 = chunks
105
+
106
+ sys_prompt = (
107
+ "You are an Elite Cinematic Action Choreographer.\n"
108
+ f"1. CHARACTER: If NAME is 'AUTO', assign a culturally appropriate name based on TAGS. If '{user_defined_name}', use it.\n"
109
+ "2. INITIAL POSTURE: Analyze TAGS for the character's starting physical state. State it in 1 short sentence.\n"
110
+ "3. MASTER SCENE: A vivid 1-sentence environment description based on TAGS and VIBE.\n"
111
+ "4. STRICT CHOREOGRAPHY ISOLATION (CRITICAL):\n"
112
+ " - You will receive 4 explicit target events. You MUST translate them into English cinematic actions.\n"
113
+ " - FATAL ERROR PREVENTION: You MUST rigidly confine the actions. DO NOT animate Part 2, 3, or 4's event in Part 1. Keep them completely isolated in time.\n"
114
+ " - 🔥 OUTPUT RULE: DO NOT quote the original Korean text. ONLY output the English descriptions.\n"
115
+ " - 🔥 ANTI-SLOW MOTION RULE: Inject aggressive kinetic adverbs (swiftly, suddenly, explosively) into EVERY action description.\n"
116
+ " - Each Part MUST reset timestamps to [0-1s], [2-3s], [4-5s].\n"
117
+ "Format EXACTLY:\nCHARACTER: [Name]\nINITIAL POSTURE: [State]\nMASTER SCENE: [Description]\n"
118
+ "PART 1: [0-1s] [Action A] [2-3s] [Action B] [4-5s] [Action C]\n"
119
+ "PART 2: [0-1s] [Action D] [2-3s] [Action E] [4-5s] [Action F]\n"
120
+ "PART 3: [0-1s] [Action G] [2-3s] [Action H] [4-5s] [Action I]\n"
121
+ "PART 4: [0-1s] [Action J] [2-3s] [Action K] [4-5s] [Action L]"
122
+ )
123
+
124
+ usr_prompt = (
125
+ f"NAME: {character_name}\nTAGS: {tagger_context}\nVIBE: {artistic_vibe}\n\n"
126
+ "=== ACTION ISOLATION SCRIPT ===\n"
127
+ f"▶ For PART 1 (0-5s), you MUST ONLY animate this specific event: \"{chunk_1}\"\n"
128
+ f"▶ For PART 2 (5-10s), you MUST ONLY animate this specific event: \"{chunk_2}\"\n"
129
+ f"▶ For PART 3 (10-15s), you MUST ONLY animate this specific event: \"{chunk_3}\"\n"
130
+ f"▶ For PART 4 (15-20s), you MUST ONLY animate this specific event: \"{chunk_4}\"\n"
131
+ "CRITICAL REMINDER: Do NOT allow any actions from Part 2, 3, or 4 to bleed into Part 1!"
132
+ )
133
+
134
+ url = "https://openrouter.ai/api/v1/chat/completions"
135
+ payload = {"model": openrouter_model.strip(), "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": usr_prompt}], "temperature": creativity}
136
+
137
+ try:
138
+ req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers={'Authorization': f'Bearer {openrouter_api_key}', 'Content-Type': 'application/json'})
139
+ response = urllib.request.urlopen(req, timeout=120)
140
+ llm_prompt = json.loads(response.read().decode('utf-8'))['choices'][0]['message']['content'].strip()
141
+ print(f"\n✅ [Dolphin V30 - Cinematic Pacing Applied]\n{llm_prompt}\n")
142
+ except Exception as e:
143
+ print(f"❌ [에러] {e}")
144
+ llm_prompt = ""
145
+
146
+ final_char_name = user_defined_name
147
+ r_master, p1, p2, p3, p4 = "", "", "", "", ""
148
+
149
+ if llm_prompt and "[removed]" not in llm_prompt:
150
+ cl = re.sub(r'[*#]', '', llm_prompt)
151
+ m_char = re.search(r'CHARACTER:\s*(.*?)(?=INITIAL POSTURE|$)', cl, re.I | re.S)
152
+ m_master = re.search(r'MASTER SCENE:\s*(.*?)(?=PART 1|$)', cl, re.I | re.S)
153
+
154
+ m1 = re.search(r'PART 1:\s*(.*?)(?=PART 2|$)', cl, re.I | re.S)
155
+ m2 = re.search(r'PART 2:\s*(.*?)(?=PART 3|$)', cl, re.I | re.S)
156
+ m3 = re.search(r'PART 3:\s*(.*?)(?=PART 4|$)', cl, re.I | re.S)
157
+ m4 = re.search(r'PART 4:\s*(.*?)(?=\n\n|===|Note:|$)', cl, re.I | re.S)
158
+
159
+ if not user_defined_name and m_char:
160
+ final_char_name = m_char.group(1).strip()
161
+
162
+ r_master = m_master.group(1).strip() if m_master else ""
163
+
164
+ p1 = m1.group(1).strip() if m1 else "⚠️ Formatting Error: Part 1"
165
+ p2 = m2.group(1).strip() if m2 else "⚠️ Formatting Error: Part 2"
166
+ p3 = m3.group(1).strip() if m3 else "⚠️ Formatting Error: Part 3"
167
+ p4 = m4.group(1).strip() if m4 else "⚠️ Formatting Error: Part 4"
168
+ else:
169
+ p1 = p2 = p3 = p4 = "⚠️ API Error: LLM request failed."
170
+
171
+ return (
172
+ build_final(p1, tagger_context, r_master, final_char_name),
173
+ build_final(p2, tagger_context, r_master, final_char_name),
174
+ build_final(p3, tagger_context, r_master, final_char_name),
175
+ build_final(p4, tagger_context, r_master, final_char_name)
176
+ )
_archive/nodes_unregistered/old/promp_logic_v31_vision.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import re
4
+ import json
5
+ import urllib.request
6
+ import urllib.error
7
+ import base64
8
+ import io
9
+ import numpy as np
10
+ from PIL import Image
11
+ import torch
12
+
13
+ class DolphinMultiActionPromptNode_V31:
14
+ @classmethod
15
+ def INPUT_TYPES(s):
16
+ return {
17
+ "required": {
18
+ "image": ("IMAGE",),
19
+ "mode": (["🤖 Auto Vision+LLM", "✍️ Manual Override"], {"default": "🤖 Auto Vision+LLM"}),
20
+ "character_name": ("STRING", {"multiline": False, "default": "AUTO"}),
21
+ "artistic_vibe": ("STRING", {"multiline": True, "default": "cinematic lighting, high-speed action, dark fantasy"}),
22
+
23
+ "master_story": ("STRING", {
24
+ "multiline": True,
25
+ "default": "어두운 골목길. 갑자기 나타난 적들을 향해 돌진한다, 화려하게 검을 휘둘러 적을 쓰러뜨린다, 날아오는 총알을 튕겨낸다, 적에게 다가가 숨통을 끊는다."
26
+ }),
27
+
28
+ "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}),
29
+ "openrouter_model": ("STRING", {"multiline": False, "default": "qwen/qwen-2-vl-72b-instruct"}),
30
+ "creativity": ("FLOAT", {"default": 0.85, "min": 0.1, "max": 1.5, "step": 0.05}),
31
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
32
+ },
33
+ }
34
+
35
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
36
+ RETURN_NAMES = ("prompt_1 (Clip 1: 0-5s)", "prompt_2 (Clip 2: 0-5s)", "prompt_3 (Clip 3: 0-5s)", "prompt_4 (Clip 4: 0-5s)")
37
+ FUNCTION = "generate_sequence"
38
+ CATEGORY = "Dolphin"
39
+
40
+ def generate_sequence(self, image, mode, character_name, artistic_vibe, master_story, openrouter_api_key, openrouter_model, creativity, seed):
41
+
42
+ user_defined_name = "" if character_name.upper() in ["AUTO", ""] else character_name.strip()
43
+
44
+ def build_final(action, master_scene, name):
45
+ tags_list = []
46
+ if name: tags_list.append(name)
47
+ tag_block = ", ".join(tags_list)
48
+
49
+ sentence_list = []
50
+ if master_scene.strip(): sentence_list.append(master_scene.strip().strip(",. "))
51
+ if action.strip(): sentence_list.append(action.strip())
52
+ sentence_block = " ".join(sentence_list)
53
+
54
+ if tag_block and sentence_block:
55
+ return f"{tag_block}\n{sentence_block}"
56
+ elif tag_block:
57
+ return tag_block
58
+ else:
59
+ return sentence_block
60
+
61
+ if mode == "✍️ Manual Override":
62
+ return (
63
+ build_final(master_story, "", user_defined_name),
64
+ build_final(master_story, "", user_defined_name),
65
+ build_final(master_story, "", user_defined_name),
66
+ build_final(master_story, "", user_defined_name)
67
+ )
68
+
69
+ random.seed(seed)
70
+
71
+ img_tensor = image[0]
72
+ i = 255. * img_tensor.cpu().numpy()
73
+ img_pil = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
74
+ buffered = io.BytesIO()
75
+ img_pil.save(buffered, format="JPEG")
76
+ base64_image = f"data:image/jpeg;base64,{base64.b64encode(buffered.getvalue()).decode('utf-8')}"
77
+
78
+ clean_story = re.sub(r'([.!?,\n])', r'\1|', master_story)
79
+ raw_sentences = clean_story.split('|')
80
+ sentences = [s.strip() for s in raw_sentences if len(s.strip()) > 1]
81
+
82
+ # 🔥 '가만히 있기(still)'를 금지하고, 1문장이라도 시작부터 빠르게 움직이도록 전면 수정
83
+ if len(sentences) == 1:
84
+ chunk_1 = f"Phase 1: Rapid approach and high-speed dynamic movement. DO NOT stand still. (Target: {sentences[0]})"
85
+ chunk_2 = f"Phase 2: Swift, explosive execution of the action. (Target: {sentences[0]})"
86
+ chunk_3 = f"Phase 3: The climax at full 1x real-time speed. Lightning fast! (Target: {sentences[0]})"
87
+ chunk_4 = f"Phase 4: Fast-paced completion and quick recovery. (Target: {sentences[0]})"
88
+ elif len(sentences) == 2:
89
+ chunk_1 = f"Phase 1: High-speed buildup and rapid preparation. (Target: {sentences[0]})"
90
+ chunk_2 = f"Phase 2: Explosively execute -> {sentences[0]}"
91
+ chunk_3 = f"Phase 3: Rapid transition, sprinting or moving quickly. (Target: {sentences[1]})"
92
+ chunk_4 = f"Phase 4: Lightning-fast execution -> {sentences[1]}"
93
+ elif len(sentences) == 3:
94
+ chunk_1 = f"Phase 1: Start this action rapidly -> {sentences[0]}"
95
+ chunk_2 = f"Phase 2: Explosively complete -> {sentences[0]}"
96
+ chunk_3 = sentences[1]
97
+ chunk_4 = sentences[2]
98
+ else:
99
+ k, m = divmod(len(sentences), 4)
100
+ chunks = []
101
+ start = 0
102
+ for i in range(4):
103
+ end = start + k + (1 if i < m else 0)
104
+ chunks.append(" ".join(sentences[start:end]))
105
+ start = end
106
+ chunk_1, chunk_2, chunk_3, chunk_4 = chunks
107
+
108
+ # 🔥 슬로우 모션 발작 단어(micro-expression 등) 완전 삭제 및 매크로 스피드 강조
109
+ sys_prompt = (
110
+ "You are an Elite Action Director prioritizing RAW SPEED and KINETIC ENERGY.\n"
111
+ f"1. CHARACTER: If NAME is 'AUTO', assign a name. If '{user_defined_name}', use it.\n"
112
+ "2. VISUAL ANALYSIS: You MUST base your descriptions EXACTLY on the character's clothing and weapons in the attached IMAGE.\n"
113
+ "3. MASTER SCENE: Write a 1-sentence environment description (lighting, weather).\n"
114
+ "4. SPEED-FOCUSED CHOREOGRAPHY (CRITICAL):\n"
115
+ " - 🚫 BAN SLOW-MOTION TRIGGERS: NEVER use words like 'micro-expressions', 'muscle tension', 'slowly turning', 'floating', or 'gradually'. These cause AI video models to render in slow-motion.\n"
116
+ " - ✅ FORCE 1x REAL-TIME SPEED: Describe large, sweeping, high-velocity movements. Use aggressive verbs (dashing, sprinting, whipping, snapping).\n"
117
+ " - ✅ KINETIC ADVERBS: Inject phrases like 'in a flash', 'at lightning speed', 'with explosive real-time velocity' into EVERY part.\n"
118
+ " - Example: 'suddenly dashes forward at full speed and delivers a lightning-fast horizontal strike, moving so quickly the rain splatters'.\n"
119
+ " - Strictly confine the actions. DO NOT animate future events early.\n"
120
+ " - 🔥 OUTPUT RULE: DO NOT quote the Korean text. Only output English.\n"
121
+ "Format EXACTLY:\nCHARACTER: [Name]\nMASTER SCENE: [Description]\n"
122
+ "PART 1: [0-1s] [Action A] [2-3s] [Action B] [4-5s] [Action C]\n"
123
+ "PART 2: [0-1s] [Action D] [2-3s] [Action E] [4-5s] [Action F]\n"
124
+ "PART 3: [0-1s] [Action G] [2-3s] [Action H] [4-5s] [Action I]\n"
125
+ "PART 4: [0-1s] [Action J] [2-3s] [Action K] [4-5s] [Action L]"
126
+ )
127
+
128
+ usr_text = (
129
+ f"NAME: {character_name}\n"
130
+ f"VIBE: {artistic_vibe}\n\n"
131
+ "=== HIGH-SPEED ACTION SCRIPT ===\n"
132
+ f"▶ For PART 1 (0-5s), ONLY animate this: \"{chunk_1}\"\n"
133
+ f"▶ For PART 2 (5-10s), ONLY animate this: \"{chunk_2}\"\n"
134
+ f"▶ For PART 3 (10-15s), ONLY animate this: \"{chunk_3}\"\n"
135
+ f"▶ For PART 4 (15-20s), ONLY animate this: \"{chunk_4}\"\n"
136
+ "CRITICAL: Keep the action moving FAST. Avoid still poses or micro-details that look like slow-mo!"
137
+ )
138
+
139
+ url = "https://openrouter.ai/api/v1/chat/completions"
140
+ payload = {
141
+ "model": openrouter_model.strip(),
142
+ "messages": [
143
+ {"role": "system", "content": sys_prompt},
144
+ {"role": "user", "content": [
145
+ {"type": "text", "text": usr_text},
146
+ {"type": "image_url", "image_url": {"url": base64_image}}
147
+ ]}
148
+ ],
149
+ "temperature": creativity
150
+ }
151
+
152
+ try:
153
+ req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers={'Authorization': f'Bearer {openrouter_api_key}', 'Content-Type': 'application/json'})
154
+ response = urllib.request.urlopen(req, timeout=120)
155
+ llm_prompt = json.loads(response.read().decode('utf-8'))['choices'][0]['message']['content'].strip()
156
+ print(f"\n✅ [Dolphin V31 - Action Speed Optimized]\n{llm_prompt}\n")
157
+ except Exception as e:
158
+ print(f"❌ [에러] API 호출 실패: {e}")
159
+ llm_prompt = ""
160
+
161
+ final_char_name = user_defined_name
162
+ r_master, p1, p2, p3, p4 = "", "", "", "", ""
163
+
164
+ if llm_prompt and "[removed]" not in llm_prompt:
165
+ cl = re.sub(r'[*#]', '', llm_prompt)
166
+ m_char = re.search(r'CHARACTER:\s*(.*?)(?=MASTER SCENE|$)', cl, re.I | re.S)
167
+ m_master = re.search(r'MASTER SCENE:\s*(.*?)(?=PART 1|$)', cl, re.I | re.S)
168
+
169
+ m1 = re.search(r'PART 1:\s*(.*?)(?=PART 2|$)', cl, re.I | re.S)
170
+ m2 = re.search(r'PART 2:\s*(.*?)(?=PART 3|$)', cl, re.I | re.S)
171
+ m3 = re.search(r'PART 3:\s*(.*?)(?=PART 4|$)', cl, re.I | re.S)
172
+ m4 = re.search(r'PART 4:\s*(.*?)(?=\n\n|===|Note:|$)', cl, re.I | re.S)
173
+
174
+ if not user_defined_name and m_char:
175
+ final_char_name = m_char.group(1).strip()
176
+
177
+ r_master = m_master.group(1).strip() if m_master else ""
178
+
179
+ p1 = m1.group(1).strip() if m1 else "⚠️ Formatting Error: Part 1"
180
+ p2 = m2.group(1).strip() if m2 else "⚠️ Formatting Error: Part 2"
181
+ p3 = m3.group(1).strip() if m3 else "⚠️ Formatting Error: Part 3"
182
+ p4 = m4.group(1).strip() if m4 else "⚠️ Formatting Error: Part 4"
183
+ else:
184
+ p1 = p2 = p3 = p4 = "⚠️ API Error: Vision LLM request failed."
185
+
186
+ return (
187
+ build_final(p1, r_master, final_char_name),
188
+ build_final(p2, r_master, final_char_name),
189
+ build_final(p3, r_master, final_char_name),
190
+ build_final(p4, r_master, final_char_name)
191
+ )
_archive/nodes_unregistered/old/promp_logic_v32_vision.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import re
4
+ import json
5
+ import urllib.request
6
+ import urllib.error
7
+ import base64
8
+ import io
9
+ import time
10
+ import numpy as np
11
+ from PIL import Image
12
+ import torch
13
+
14
+
15
+ class DolphinMultiActionPromptNode_V32:
16
+ @classmethod
17
+ def INPUT_TYPES(s):
18
+ return {
19
+ "required": {
20
+ "image": ("IMAGE",),
21
+ "mode": (["🤖 Auto Vision+LLM", "✍️ Manual Override"], {"default": "🤖 Auto Vision+LLM"}),
22
+ "character_name": ("STRING", {"multiline": False, "default": "AUTO"}),
23
+ "artistic_vibe": ("STRING", {"multiline": True, "default": "cinematic lighting, high-speed action, dark fantasy"}),
24
+
25
+ "master_story": ("STRING", {
26
+ "multiline": True,
27
+ "default": "어두운 골목길. 갑자기 나타난 적들을 향해 돌진한다, 화려하게 검을 휘둘러 적을 쓰러뜨린다, 날아오는 총알을 튕겨낸다, 적에게 다가가 숨통을 끊는다."
28
+ }),
29
+
30
+ "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}),
31
+ "openrouter_model": ("STRING", {"multiline": False, "default": "qwen/qwen-2-vl-72b-instruct"}),
32
+ "creativity": ("FLOAT", {"default": 0.85, "min": 0.1, "max": 1.5, "step": 0.05}),
33
+ "max_tokens": ("INT", {"default": 1500, "min": 256, "max": 8192, "step": 64}),
34
+ "retries": ("INT", {"default": 2, "min": 0, "max": 5}),
35
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
36
+ },
37
+ }
38
+
39
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING")
40
+ RETURN_NAMES = (
41
+ "prompt_1 (Clip 1: 0-5s)",
42
+ "prompt_2 (Clip 2: 0-5s)",
43
+ "prompt_3 (Clip 3: 0-5s)",
44
+ "prompt_4 (Clip 4: 0-5s)",
45
+ "raw_llm_output",
46
+ )
47
+ FUNCTION = "generate_sequence"
48
+ CATEGORY = "Dolphin"
49
+
50
+ # -----------------------------------------------------------------
51
+ def _encode_image(self, image):
52
+ img_tensor = image[0]
53
+ i = 255. * img_tensor.cpu().numpy()
54
+ img_pil = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
55
+ if img_pil.mode != "RGB":
56
+ img_pil = img_pil.convert("RGB")
57
+ buffered = io.BytesIO()
58
+ img_pil.save(buffered, format="JPEG", quality=90)
59
+ b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
60
+ return f"data:image/jpeg;base64,{b64}"
61
+
62
+ def _split_sentences(self, master_story):
63
+ clean_story = re.sub(r'([.!?,\n])', r'\1|', master_story)
64
+ raw_sentences = clean_story.split('|')
65
+ return [s.strip() for s in raw_sentences if len(s.strip()) > 1]
66
+
67
+ def _build_chunks(self, sentences):
68
+ n = len(sentences)
69
+ if n == 0:
70
+ base = "dynamic high-speed action"
71
+ return (base, base, base, base)
72
+ if n == 1:
73
+ s = sentences[0]
74
+ return (
75
+ f"Phase 1: Rapid approach and high-speed dynamic movement. DO NOT stand still. (Target: {s})",
76
+ f"Phase 2: Swift, explosive execution of the action. (Target: {s})",
77
+ f"Phase 3: The climax at full 1x real-time speed. Lightning fast! (Target: {s})",
78
+ f"Phase 4: Fast-paced completion and quick recovery. (Target: {s})",
79
+ )
80
+ if n == 2:
81
+ return (
82
+ f"Phase 1: High-speed buildup and rapid preparation. (Target: {sentences[0]})",
83
+ f"Phase 2: Explosively execute -> {sentences[0]}",
84
+ f"Phase 3: Rapid transition, sprinting or moving quickly. (Target: {sentences[1]})",
85
+ f"Phase 4: Lightning-fast execution -> {sentences[1]}",
86
+ )
87
+ if n == 3:
88
+ return (
89
+ f"Phase 1: Start this action rapidly -> {sentences[0]}",
90
+ f"Phase 2: Explosively complete -> {sentences[0]}",
91
+ sentences[1],
92
+ sentences[2],
93
+ )
94
+ # n >= 4: 균등 분배
95
+ k, m = divmod(n, 4)
96
+ chunks = []
97
+ start = 0
98
+ for idx in range(4):
99
+ end = start + k + (1 if idx < m else 0)
100
+ chunks.append(" ".join(sentences[start:end]))
101
+ start = end
102
+ return tuple(chunks)
103
+
104
+ def _call_llm(self, url, payload, api_key, retries, timeout=120):
105
+ last_err = None
106
+ for attempt in range(retries + 1):
107
+ try:
108
+ req = urllib.request.Request(
109
+ url,
110
+ data=json.dumps(payload).encode('utf-8'),
111
+ headers={'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'}
112
+ )
113
+ response = urllib.request.urlopen(req, timeout=timeout)
114
+ body = json.loads(response.read().decode('utf-8'))
115
+ return body['choices'][0]['message']['content'].strip(), None
116
+ except Exception as e:
117
+ last_err = e
118
+ if attempt < retries:
119
+ time.sleep(1.5 * (attempt + 1))
120
+ return None, last_err
121
+
122
+ # -----------------------------------------------------------------
123
+ def generate_sequence(self, image, mode, character_name, artistic_vibe, master_story,
124
+ openrouter_api_key, openrouter_model, creativity, max_tokens, retries, seed):
125
+
126
+ user_defined_name = "" if character_name.upper() in ["AUTO", ""] else character_name.strip()
127
+
128
+ def build_final(action, master_scene, name):
129
+ tags_list = []
130
+ if name:
131
+ tags_list.append(name)
132
+ tag_block = ", ".join(tags_list)
133
+
134
+ sentence_list = []
135
+ if master_scene.strip():
136
+ sentence_list.append(master_scene.strip().strip(",. "))
137
+ if action.strip():
138
+ sentence_list.append(action.strip())
139
+ sentence_block = " ".join(sentence_list)
140
+
141
+ if tag_block and sentence_block:
142
+ return f"{tag_block}\n{sentence_block}"
143
+ elif tag_block:
144
+ return tag_block
145
+ return sentence_block
146
+
147
+ # ---- Manual Override ----
148
+ if mode == "✍️ Manual Override":
149
+ fp = build_final(master_story, "", user_defined_name)
150
+ return (fp, fp, fp, fp, "[Manual Override]")
151
+
152
+ random.seed(seed)
153
+
154
+ base64_image = self._encode_image(image)
155
+ sentences = self._split_sentences(master_story)
156
+ chunk_1, chunk_2, chunk_3, chunk_4 = self._build_chunks(sentences)
157
+
158
+ sys_prompt = (
159
+ "You are an Elite Action Director prioritizing RAW SPEED and KINETIC ENERGY.\n"
160
+ f"1. CHARACTER: If NAME is 'AUTO', assign a name. If '{user_defined_name}', use it.\n"
161
+ "2. VISUAL ANALYSIS: You MUST base your descriptions EXACTLY on the character's clothing and weapons in the attached IMAGE.\n"
162
+ "3. MASTER SCENE: Write a 1-sentence environment description (lighting, weather).\n"
163
+ "4. SPEED-FOCUSED CHOREOGRAPHY (CRITICAL):\n"
164
+ " - 🚫 BAN SLOW-MOTION TRIGGERS: NEVER use words like 'micro-expressions', 'muscle tension', 'slowly turning', 'floating', or 'gradually'. These cause AI video models to render in slow-motion.\n"
165
+ " - ✅ FORCE 1x REAL-TIME SPEED: Describe large, sweeping, high-velocity movements. Use aggressive verbs (dashing, sprinting, whipping, snapping).\n"
166
+ " - ✅ KINETIC ADVERBS: Inject phrases like 'in a flash', 'at lightning speed', 'with explosive real-time velocity' into EVERY part.\n"
167
+ " - Example: 'suddenly dashes forward at full speed and delivers a lightning-fast horizontal strike, moving so quickly the rain splatters'.\n"
168
+ " - Strictly confine the actions. DO NOT animate future events early.\n"
169
+ " - 🔥 OUTPUT RULE: DO NOT quote the Korean text. Only output English.\n"
170
+ "Format EXACTLY:\nCHARACTER: [Name]\nMASTER SCENE: [Description]\n"
171
+ "PART 1: [0-1s] [Action A] [2-3s] [Action B] [4-5s] [Action C]\n"
172
+ "PART 2: [0-1s] [Action D] [2-3s] [Action E] [4-5s] [Action F]\n"
173
+ "PART 3: [0-1s] [Action G] [2-3s] [Action H] [4-5s] [Action I]\n"
174
+ "PART 4: [0-1s] [Action J] [2-3s] [Action K] [4-5s] [Action L]"
175
+ )
176
+
177
+ usr_text = (
178
+ f"NAME: {character_name}\n"
179
+ f"VIBE: {artistic_vibe}\n\n"
180
+ "=== HIGH-SPEED ACTION SCRIPT ===\n"
181
+ f"▶ For PART 1 (0-5s), ONLY animate this: \"{chunk_1}\"\n"
182
+ f"▶ For PART 2 (5-10s), ONLY animate this: \"{chunk_2}\"\n"
183
+ f"▶ For PART 3 (10-15s), ONLY animate this: \"{chunk_3}\"\n"
184
+ f"▶ For PART 4 (15-20s), ONLY animate this: \"{chunk_4}\"\n"
185
+ "CRITICAL: Keep the action moving FAST. Avoid still poses or micro-details that look like slow-mo!"
186
+ )
187
+
188
+ url = "https://openrouter.ai/api/v1/chat/completions"
189
+ payload = {
190
+ "model": openrouter_model.strip(),
191
+ "messages": [
192
+ {"role": "system", "content": sys_prompt},
193
+ {"role": "user", "content": [
194
+ {"type": "text", "text": usr_text},
195
+ {"type": "image_url", "image_url": {"url": base64_image}}
196
+ ]}
197
+ ],
198
+ "temperature": creativity,
199
+ "max_tokens": max_tokens,
200
+ }
201
+
202
+ if not openrouter_api_key.strip():
203
+ err_msg = "⚠️ API Error: OpenRouter API key is empty."
204
+ return (err_msg, err_msg, err_msg, err_msg, err_msg)
205
+
206
+ llm_prompt, err = self._call_llm(url, payload, openrouter_api_key, retries)
207
+ if llm_prompt:
208
+ print(f"\n✅ [Dolphin V32 - Action Speed Optimized]\n{llm_prompt}\n")
209
+ else:
210
+ print(f"❌ [에러] API 호출 실패: {err}")
211
+ llm_prompt = ""
212
+
213
+ final_char_name = user_defined_name
214
+ r_master = ""
215
+ p1 = p2 = p3 = p4 = ""
216
+
217
+ if llm_prompt and "[removed]" not in llm_prompt:
218
+ cl = re.sub(r'[*#]', '', llm_prompt)
219
+ m_char = re.search(r'CHARACTER:\s*(.*?)(?=MASTER SCENE|$)', cl, re.I | re.S)
220
+ m_master = re.search(r'MASTER SCENE:\s*(.*?)(?=PART 1|$)', cl, re.I | re.S)
221
+
222
+ m1 = re.search(r'PART 1:\s*(.*?)(?=PART 2|$)', cl, re.I | re.S)
223
+ m2 = re.search(r'PART 2:\s*(.*?)(?=PART 3|$)', cl, re.I | re.S)
224
+ m3 = re.search(r'PART 3:\s*(.*?)(?=PART 4|$)', cl, re.I | re.S)
225
+ m4 = re.search(r'PART 4:\s*(.*?)(?=\n\n|===|Note:|$)', cl, re.I | re.S)
226
+
227
+ if not user_defined_name and m_char:
228
+ final_char_name = m_char.group(1).strip()
229
+
230
+ r_master = m_master.group(1).strip() if m_master else ""
231
+
232
+ # 파싱 실패 시 해당 청크(영문 지시문)를 폴백으로 사용해 비디오 프롬프트가 비지 않도록 함
233
+ p1 = m1.group(1).strip() if m1 else chunk_1
234
+ p2 = m2.group(1).strip() if m2 else chunk_2
235
+ p3 = m3.group(1).strip() if m3 else chunk_3
236
+ p4 = m4.group(1).strip() if m4 else chunk_4
237
+ else:
238
+ # API 실패 시에도 스크립트 청크를 폴백으로 반환 (완전 실패보다 유용)
239
+ p1, p2, p3, p4 = chunk_1, chunk_2, chunk_3, chunk_4
240
+
241
+ return (
242
+ build_final(p1, r_master, final_char_name),
243
+ build_final(p2, r_master, final_char_name),
244
+ build_final(p3, r_master, final_char_name),
245
+ build_final(p4, r_master, final_char_name),
246
+ llm_prompt if llm_prompt else "⚠️ API Error / empty response",
247
+ )
_archive/workflows/0705_v32_matrix.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision2 (1).json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision2.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision2.json.bak_20260703_before_previewonly ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision2.json.bak_20260703_before_trimwidgets ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision2.json.bak_20260724_1316_before_perclip_refactor ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision2.json.bak_20260724_before_fixseed ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision3.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision3_svigray_test.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision3_svimotion_test.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/0705_vision4.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/dolphin_lora_classification.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/dolphin_tags_cache.json ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "wan22.r3v3rs3_c0wg1rl-14b-Low-i2v_e70.safetensors": [
3
+ "r3v3rs3_c0wg1rl",
4
+ "c0wg1rl",
5
+ "straddling him in the reverse cowgirl position"
6
+ ],
7
+ "DR34ML4Y_I2V_14B_LOW_V2.safetensors": [
8
+ "m15510n4ry",
9
+ "bl0wj0b",
10
+ "d0gg1e",
11
+ "c0wg1rl",
12
+ "d0ubl3_bj"
13
+ ],
14
+ "wan22.r3v3rs3_c0wg1rl-14b-High-i2v_e70.safetensors": [
15
+ "r3v3rs3_c0wg1rl",
16
+ "c0wg1rl",
17
+ "straddling him in the reverse cowgirl position"
18
+ ],
19
+ "DR34ML4Y_I2V_14B_HIGH_V2.safetensors": [
20
+ "m15510n4ry",
21
+ "bl0wj0b",
22
+ "c0wg1rl",
23
+ "d0gg1e",
24
+ "d0ubl3_bj"
25
+ ],
26
+ "erect_penis_epoch_80.safetensors": [
27
+ "penis"
28
+ ],
29
+ "PENISLORA_22_i2v_HIGH_e320.safetensors": [
30
+ "PENISLORA"
31
+ ],
32
+ "PENISLORA_22_i2v_LOW_e496.safetensors": [
33
+ "PENISLORA"
34
+ ],
35
+ "genitals_helper_v1.0_e219.safetensors": [
36
+ "Innie vagina",
37
+ "Outie vagina"
38
+ ],
39
+ "wan2.1-i2v-480p-penis-arousal-v1.0.safetensors": [
40
+ "penis becomes erect"
41
+ ],
42
+ "pusfix-e5-21.safetensors": [
43
+ "pusfix"
44
+ ],
45
+ "wan22-cunilingus-I2V-72epoc-low.safetensors": [
46
+ "cunn1l1ngu5"
47
+ ],
48
+ "wan22-cunilingus-I2V-106epoc-high.safetensors": [
49
+ "cunn1l1ngu5"
50
+ ],
51
+ "smoll-pp.high.wan2.2.t2v.safetensors": [
52
+ "small penis",
53
+ "she is stroking her small penis between two fingers",
54
+ "flaccid penis"
55
+ ],
56
+ "smoll-pp.low.wan2.2.t2v.safetensors": [
57
+ "small penis",
58
+ "she is stroking her small penis between two fingers",
59
+ "flaccid penis"
60
+ ],
61
+ "masturbation_cumshot_v1.1_e310.safetensors": [
62
+ "which lands on her {thighs|pelvis|stomach|face|breasts}",
63
+ "She moves her hand rapidly up and down the shaft of her penis and she ejaculates white sticky liquid from her penis"
64
+ ],
65
+ "wan2.1-i2v-480p-flaccid-v1.0.safetensors": [
66
+ "penis",
67
+ "bounce",
68
+ "swing"
69
+ ],
70
+ "wan2.2_i2v_highnoise_petplay_v2.0.safetensors": [
71
+ "The woman moves into in a paw pose with her fists raised, looking at the viewer.",
72
+ "She very slowly flicks her wrists, alternating between them back and forth in a cute begging motion. She is acting like a sexy pet.",
73
+ "She begins panting with her tongue out like a dog."
74
+ ],
75
+ "wan2.2_i2v_lownoise_petplay_v2.0.safetensors": [
76
+ "The woman moves into in a paw pose with her fists raised, looking at the viewer.",
77
+ "She very slowly flicks her wrists, alternating between them back and forth in a cute begging motion. She is acting like a sexy pet.",
78
+ "She begins panting with her tongue out like a dog."
79
+ ],
80
+ "wan22_i2v_futa_cowgirl_high_noise.safetensors": [
81
+ "a transgender woman having anal sex in cowgirl position"
82
+ ],
83
+ "wan22_i2v_futa_cowgirl_low_noise.safetensors": [
84
+ "a transgender woman having anal sex in cowgirl position"
85
+ ],
86
+ "anal_insertion_HIGH_V01.safetensors": [
87
+ "Anal insertion.",
88
+ "The video depicts a man slowly inserting his dick into a woman's anus",
89
+ "while she is lying on a couch.",
90
+ "The video depicts a sexual encounter between two animated characters, with one character being penetrated by the other.",
91
+ "The video depicts two animated characters engaging in sexual activity, with one character being penetrated by the other's erect penis.",
92
+ "close-up"
93
+ ],
94
+ "anal_insertion_LOW_V01.safetensors": [
95
+ "Anal insertion.",
96
+ "The video depicts a man slowly inserting his dick into a woman's anus",
97
+ "while she is lying on a couch.",
98
+ "The video depicts a sexual encounter between two animated characters, with one character being penetrated by the other.",
99
+ "The video depicts two animated characters engaging in sexual activity, with one character being penetrated by the other's erect penis."
100
+ ],
101
+ "wan22_t2v_penis_pen15_HIGH.safetensors": [
102
+ "pen15",
103
+ "penis"
104
+ ],
105
+ "wan22_t2v_penis_pen15_LOW.safetensors": [
106
+ "pen15",
107
+ "penis"
108
+ ],
109
+ "wan22_i2v_futa_missionary_low_noise.safetensors": [
110
+ "futa missionary"
111
+ ],
112
+ "wan22_i2v_futa_missionary_high_noise.safetensors": [
113
+ "futa missionary"
114
+ ],
115
+ "shemale anal missionary_high_noise.safetensors": [
116
+ "A transsexual woman is lying on her back while she gets fucked anally by a man's penis. The man's penis is penetrating her anus rapidly."
117
+ ],
118
+ "shemale anal missionary_low_noise.safetensors": [
119
+ "A transsexual woman is lying on her back while she gets fucked anally by a man's penis. The man's penis is penetrating her anus rapidly."
120
+ ],
121
+ "hardcore-gay_high_noise.safetensors": [
122
+ "hardc0re-gay"
123
+ ],
124
+ "hardcore-gay_low_noise.safetensors": [
125
+ "hardc0re-gay"
126
+ ],
127
+ "Blowbang_high_noise.safetensors": [
128
+ "A woman is surrounded by multiple penises, she is in the middle of the frame performing oral sex, men's torsos are around her, they all have huge penises which the woman is grabbing and putting inside her mouth."
129
+ ],
130
+ "xPenisGlanx_wan22_050626_low_noise_15k.safetensors": [
131
+ "Penis"
132
+ ],
133
+ "xPenisGlanx_wan22_050626_high_noise_15k.safetensors": [
134
+ "Penis"
135
+ ],
136
+ "wan_dildo_closeup_i2vA14B_HIGHNOISE_v20.safetensors": [
137
+ "piston_dildo_style"
138
+ ],
139
+ "wan_dildo_closeup_i2vA14B_LOWNOISE_v20.safetensors": [
140
+ "piston_dildo_style"
141
+ ],
142
+ "i2v-anus-squirt_high_noise.safetensors": [
143
+ "returnthis4nussqu1rt"
144
+ ],
145
+ "i2v-anus-squirt_low_noise.safetensors": [
146
+ "returnthis4nussqu1rt"
147
+ ],
148
+ "wan_2.2_i2v_slap_low_v2.0.safetensors": [
149
+ "she slaps herself in the face",
150
+ "she is slapped in the face"
151
+ ],
152
+ "wan_2.2_i2v_slap_high_v2.0.safetensors": [
153
+ "she slaps herself in the face",
154
+ "she is slapped in the face"
155
+ ],
156
+ "Twerking_I2V_high_noise.safetensors": [
157
+ "A woman is twerking and shaking her ass."
158
+ ],
159
+ "Twerking_I2V_low_noise.safetensors": [
160
+ "A woman is twerking and shaking her ass."
161
+ ],
162
+ "FistingHigh.safetensors": [
163
+ "Male fist is moving in and out of female vagina.",
164
+ "vaginal fisting",
165
+ "anal fisting"
166
+ ],
167
+ "FistingLow.safetensors": [
168
+ "Male fist is moving in and out of female vagina",
169
+ "vaginal fisting",
170
+ "anal fisting"
171
+ ],
172
+ "all-tied-up-wan22-HN-e50-az420.safetensors": [
173
+ "tied pose",
174
+ "rope",
175
+ "cuffs",
176
+ "spread-eagle"
177
+ ],
178
+ "all-tied-up-wan22-LN-e128-az420.safetensors": [
179
+ "tied",
180
+ "cuffs",
181
+ "rope",
182
+ "spread-eagle"
183
+ ],
184
+ "wan22_penis_coachbate_v2_high_noise.safetensors": [
185
+ "p3n15"
186
+ ],
187
+ "wan22_penis_coachbate_v2_low_noise.safetensors": [
188
+ "p3n15"
189
+ ],
190
+ "P00TSARA_v1_low_noise.safetensors": [
191
+ "uncut flaccid penis",
192
+ "P00TSARA"
193
+ ],
194
+ "P00TSARA_v1_high_noise.safetensors": [
195
+ "Uncut flaccid penis",
196
+ "P00TSARA"
197
+ ],
198
+ "wan2.1-i2v-480p-flaccid-v1.0.safetensors::359305272::1753134483": [
199
+ "penis",
200
+ "bounce",
201
+ "swing"
202
+ ],
203
+ "PENISLORA_22_i2v_HIGH_e320.safetensors::306807976::1759901296": [
204
+ "PENISLORA"
205
+ ],
206
+ "PENISLORA_22_i2v_LOW_e496.safetensors::306807976::1759901312": [
207
+ "PENISLORA"
208
+ ],
209
+ "masturbation_cumshot_v1.1_e310.safetensors::359257680::1747718457": [
210
+ "which lands on her {thighs|pelvis|stomach|face|breasts}",
211
+ "She moves her hand rapidly up and down the shaft of her penis and she ejaculates white sticky liquid from her penis"
212
+ ],
213
+ "DR34ML4Y_I2V_14B_HIGH_V2.safetensors::306807976::1767355819": [
214
+ "m15510n4ry",
215
+ "bl0wj0b",
216
+ "c0wg1rl",
217
+ "d0gg1e",
218
+ "d0ubl3_bj"
219
+ ],
220
+ "DR34ML4Y_I2V_14B_LOW_V2.safetensors::306807976::1767355809": [
221
+ "m15510n4ry",
222
+ "bl0wj0b",
223
+ "d0gg1e",
224
+ "c0wg1rl",
225
+ "d0ubl3_bj"
226
+ ],
227
+ "flaccid_penis_epoch_100.safetensors::306807976::1745233662": [
228
+ "penis"
229
+ ],
230
+ "erect_penis_epoch_80.safetensors::306807976::1744443760": [
231
+ "penis"
232
+ ],
233
+ "shemale anal missionary_high_noise.safetensors::306808400::1757087872": [
234
+ "A transsexual woman is lying on her back while she gets fucked anally by a man's penis. The man's penis is penetrating her anus rapidly."
235
+ ],
236
+ "shemale anal missionary_low_noise.safetensors::306808384::1757087881": [
237
+ "A transsexual woman is lying on her back while she gets fucked anally by a man's penis. The man's penis is penetrating her anus rapidly."
238
+ ],
239
+ "shemale doggy from behind_high_noise.safetensors::306808392::1757027152": [
240
+ "A transsexual woman is getting fucked doggystyle from behind by a man, the man is penetrating her anally as his penis is going inside her asshole, the man is moving his hips as he penetrates her and the woman is moving her body, she is looking back at the camera and at the man."
241
+ ],
242
+ "shemale doggy from behind_low_noise.safetensors::306808392::1766479090": [
243
+ "A transsexual woman is getting fucked doggystyle from behind by a man, the man is penetrating her anally as his penis is going inside her asshole, the man is moving his hips as he penetrates her and the woman is moving her body, she is looking back at the camera and at the man."
244
+ ],
245
+ "wan2.1-i2v-480p-penis-arousal-v1.0.safetensors::359305272::1754578081": [
246
+ "penis becomes erect"
247
+ ],
248
+ "TwerkReverse_WAN2.2_I2V_000002000_high_noise.safetensors::306808456::1764647417": [
249
+ "Twerk Reverse. A woman twerking moving her hips with a dynamic camera, she have an orgasm"
250
+ ],
251
+ "TwerkReverse_WAN2.2_I2V_000002000_low_noise.safetensors::306808456::1764647428": [
252
+ "Twerk Reverse. A nude woman twerking moving her hips with a dynamic camera, she have an orgasm"
253
+ ]
254
+ }
_archive/workflows/i2vt2v.json ADDED
@@ -0,0 +1,2074 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "7cfb83ab-3b5c-4b57-8d01-aaf470a0dc04",
3
+ "revision": 0,
4
+ "last_node_id": 48,
5
+ "last_link_id": 170,
6
+ "nodes": [
7
+ {
8
+ "id": 19,
9
+ "type": "VAEDecode",
10
+ "pos": [
11
+ 29307.375,
12
+ 2498.96484375
13
+ ],
14
+ "size": [
15
+ 210,
16
+ 46
17
+ ],
18
+ "flags": {},
19
+ "order": 19,
20
+ "mode": 0,
21
+ "inputs": [
22
+ {
23
+ "name": "samples",
24
+ "type": "LATENT",
25
+ "link": 28
26
+ },
27
+ {
28
+ "name": "vae",
29
+ "type": "VAE",
30
+ "link": 29
31
+ }
32
+ ],
33
+ "outputs": [
34
+ {
35
+ "name": "IMAGE",
36
+ "type": "IMAGE",
37
+ "slot_index": 0,
38
+ "links": [
39
+ 150,
40
+ 168
41
+ ]
42
+ }
43
+ ],
44
+ "properties": {
45
+ "cnr_id": "comfy-core",
46
+ "ver": "0.3.47",
47
+ "Node name for S&R": "VAEDecode",
48
+ "ue_properties": {
49
+ "version": "7.8",
50
+ "widget_ue_connectable": {},
51
+ "input_ue_unconnectable": {}
52
+ }
53
+ },
54
+ "widgets_values": []
55
+ },
56
+ {
57
+ "id": 4,
58
+ "type": "VAELoader",
59
+ "pos": [
60
+ 28419.005859375,
61
+ 1797.971923828125
62
+ ],
63
+ "size": [
64
+ 270,
65
+ 58
66
+ ],
67
+ "flags": {},
68
+ "order": 0,
69
+ "mode": 0,
70
+ "inputs": [],
71
+ "outputs": [
72
+ {
73
+ "name": "VAE",
74
+ "type": "VAE",
75
+ "links": [
76
+ 13,
77
+ 29,
78
+ 154,
79
+ 167
80
+ ]
81
+ }
82
+ ],
83
+ "properties": {
84
+ "cnr_id": "comfy-core",
85
+ "ver": "0.3.48",
86
+ "Node name for S&R": "VAELoader",
87
+ "ue_properties": {
88
+ "widget_ue_connectable": {
89
+ "vae_name": true
90
+ },
91
+ "version": "7.8",
92
+ "input_ue_unconnectable": {}
93
+ }
94
+ },
95
+ "widgets_values": [
96
+ "wan_2.1_vae.safetensors"
97
+ ]
98
+ },
99
+ {
100
+ "id": 9,
101
+ "type": "ModelPatchTorchSettings",
102
+ "pos": [
103
+ 27743.802734375,
104
+ 1795.334716796875
105
+ ],
106
+ "size": [
107
+ 307.443359375,
108
+ 58
109
+ ],
110
+ "flags": {},
111
+ "order": 8,
112
+ "mode": 0,
113
+ "inputs": [
114
+ {
115
+ "name": "model",
116
+ "type": "MODEL",
117
+ "link": 3
118
+ }
119
+ ],
120
+ "outputs": [
121
+ {
122
+ "name": "MODEL",
123
+ "type": "MODEL",
124
+ "links": [
125
+ 42
126
+ ]
127
+ }
128
+ ],
129
+ "properties": {
130
+ "cnr_id": "comfyui-kjnodes",
131
+ "ver": "87d0cf42db7d59992daba4d58a83655b5b359f44",
132
+ "Node name for S&R": "ModelPatchTorchSettings",
133
+ "aux_id": "kijai/ComfyUI-KJNodes",
134
+ "ue_properties": {
135
+ "widget_ue_connectable": {
136
+ "enable_fp16_accumulation": true
137
+ },
138
+ "version": "7.8",
139
+ "input_ue_unconnectable": {}
140
+ }
141
+ },
142
+ "widgets_values": [
143
+ true
144
+ ]
145
+ },
146
+ {
147
+ "id": 10,
148
+ "type": "ModelPatchTorchSettings",
149
+ "pos": [
150
+ 27742.720703125,
151
+ 1903.4395751953125
152
+ ],
153
+ "size": [
154
+ 307.443359375,
155
+ 58
156
+ ],
157
+ "flags": {},
158
+ "order": 9,
159
+ "mode": 0,
160
+ "inputs": [
161
+ {
162
+ "name": "model",
163
+ "type": "MODEL",
164
+ "link": 4
165
+ }
166
+ ],
167
+ "outputs": [
168
+ {
169
+ "name": "MODEL",
170
+ "type": "MODEL",
171
+ "links": [
172
+ 43
173
+ ]
174
+ }
175
+ ],
176
+ "properties": {
177
+ "cnr_id": "comfyui-kjnodes",
178
+ "ver": "87d0cf42db7d59992daba4d58a83655b5b359f44",
179
+ "Node name for S&R": "ModelPatchTorchSettings",
180
+ "aux_id": "kijai/ComfyUI-KJNodes",
181
+ "ue_properties": {
182
+ "widget_ue_connectable": {
183
+ "enable_fp16_accumulation": true
184
+ },
185
+ "version": "7.8",
186
+ "input_ue_unconnectable": {}
187
+ }
188
+ },
189
+ "widgets_values": [
190
+ true
191
+ ]
192
+ },
193
+ {
194
+ "id": 23,
195
+ "type": "ModelSamplingSD3",
196
+ "pos": [
197
+ 28923.224609375,
198
+ 1769.3892822265625
199
+ ],
200
+ "size": [
201
+ 315,
202
+ 58
203
+ ],
204
+ "flags": {},
205
+ "order": 12,
206
+ "mode": 0,
207
+ "inputs": [
208
+ {
209
+ "name": "model",
210
+ "type": "MODEL",
211
+ "link": 44
212
+ }
213
+ ],
214
+ "outputs": [
215
+ {
216
+ "name": "MODEL",
217
+ "type": "MODEL",
218
+ "slot_index": 0,
219
+ "links": [
220
+ 45,
221
+ 158
222
+ ]
223
+ }
224
+ ],
225
+ "properties": {
226
+ "cnr_id": "comfy-core",
227
+ "ver": "0.3.47",
228
+ "Node name for S&R": "ModelSamplingSD3",
229
+ "ue_properties": {
230
+ "version": "7.8",
231
+ "widget_ue_connectable": {},
232
+ "input_ue_unconnectable": {}
233
+ }
234
+ },
235
+ "widgets_values": [
236
+ 5
237
+ ]
238
+ },
239
+ {
240
+ "id": 24,
241
+ "type": "ModelSamplingSD3",
242
+ "pos": [
243
+ 29255.103515625,
244
+ 1772.6326904296875
245
+ ],
246
+ "size": [
247
+ 315,
248
+ 58
249
+ ],
250
+ "flags": {},
251
+ "order": 15,
252
+ "mode": 0,
253
+ "inputs": [
254
+ {
255
+ "name": "model",
256
+ "type": "MODEL",
257
+ "link": 46
258
+ }
259
+ ],
260
+ "outputs": [
261
+ {
262
+ "name": "MODEL",
263
+ "type": "MODEL",
264
+ "slot_index": 0,
265
+ "links": [
266
+ 47,
267
+ 162
268
+ ]
269
+ }
270
+ ],
271
+ "properties": {
272
+ "cnr_id": "comfy-core",
273
+ "ver": "0.3.47",
274
+ "Node name for S&R": "ModelSamplingSD3",
275
+ "ue_properties": {
276
+ "version": "7.8",
277
+ "widget_ue_connectable": {},
278
+ "input_ue_unconnectable": {}
279
+ }
280
+ },
281
+ "widgets_values": [
282
+ 5
283
+ ]
284
+ },
285
+ {
286
+ "id": 7,
287
+ "type": "PathchSageAttentionKJ",
288
+ "pos": [
289
+ 27451.584398913365,
290
+ 1777.1170290388295
291
+ ],
292
+ "size": [
293
+ 270,
294
+ 82
295
+ ],
296
+ "flags": {},
297
+ "order": 5,
298
+ "mode": 0,
299
+ "inputs": [
300
+ {
301
+ "name": "model",
302
+ "type": "MODEL",
303
+ "link": 144
304
+ }
305
+ ],
306
+ "outputs": [
307
+ {
308
+ "name": "MODEL",
309
+ "type": "MODEL",
310
+ "links": [
311
+ 3
312
+ ]
313
+ }
314
+ ],
315
+ "properties": {
316
+ "cnr_id": "comfyui-kjnodes",
317
+ "ver": "87d0cf42db7d59992daba4d58a83655b5b359f44",
318
+ "Node name for S&R": "PathchSageAttentionKJ",
319
+ "aux_id": "kijai/ComfyUI-KJNodes",
320
+ "ue_properties": {
321
+ "widget_ue_connectable": {
322
+ "sage_attention": true,
323
+ "allow_compile": true
324
+ },
325
+ "version": "7.8",
326
+ "input_ue_unconnectable": {}
327
+ }
328
+ },
329
+ "widgets_values": [
330
+ "auto",
331
+ false
332
+ ]
333
+ },
334
+ {
335
+ "id": 8,
336
+ "type": "PathchSageAttentionKJ",
337
+ "pos": [
338
+ 27455.173828125,
339
+ 1905.6007080078125
340
+ ],
341
+ "size": [
342
+ 270,
343
+ 82
344
+ ],
345
+ "flags": {},
346
+ "order": 6,
347
+ "mode": 0,
348
+ "inputs": [
349
+ {
350
+ "name": "model",
351
+ "type": "MODEL",
352
+ "link": 145
353
+ }
354
+ ],
355
+ "outputs": [
356
+ {
357
+ "name": "MODEL",
358
+ "type": "MODEL",
359
+ "links": [
360
+ 4
361
+ ]
362
+ }
363
+ ],
364
+ "properties": {
365
+ "cnr_id": "comfyui-kjnodes",
366
+ "ver": "87d0cf42db7d59992daba4d58a83655b5b359f44",
367
+ "Node name for S&R": "PathchSageAttentionKJ",
368
+ "aux_id": "kijai/ComfyUI-KJNodes",
369
+ "ue_properties": {
370
+ "widget_ue_connectable": {
371
+ "sage_attention": true,
372
+ "allow_compile": true
373
+ },
374
+ "version": "7.8",
375
+ "input_ue_unconnectable": {}
376
+ }
377
+ },
378
+ "widgets_values": [
379
+ "auto",
380
+ false
381
+ ]
382
+ },
383
+ {
384
+ "id": 15,
385
+ "type": "KSamplerAdvanced",
386
+ "pos": [
387
+ 28958.892578125,
388
+ 1905.600830078125
389
+ ],
390
+ "size": [
391
+ 252.353515625,
392
+ 546
393
+ ],
394
+ "flags": {},
395
+ "order": 17,
396
+ "mode": 0,
397
+ "inputs": [
398
+ {
399
+ "name": "model",
400
+ "type": "MODEL",
401
+ "link": 45
402
+ },
403
+ {
404
+ "name": "positive",
405
+ "type": "CONDITIONING",
406
+ "link": 16
407
+ },
408
+ {
409
+ "name": "negative",
410
+ "type": "CONDITIONING",
411
+ "link": 17
412
+ },
413
+ {
414
+ "name": "latent_image",
415
+ "type": "LATENT",
416
+ "link": 18
417
+ }
418
+ ],
419
+ "outputs": [
420
+ {
421
+ "name": "LATENT",
422
+ "type": "LATENT",
423
+ "links": [
424
+ 52
425
+ ]
426
+ }
427
+ ],
428
+ "properties": {
429
+ "cnr_id": "comfy-core",
430
+ "ver": "0.3.47",
431
+ "Node name for S&R": "KSamplerAdvanced",
432
+ "ue_properties": {
433
+ "version": "7.8",
434
+ "widget_ue_connectable": {},
435
+ "input_ue_unconnectable": {}
436
+ }
437
+ },
438
+ "widgets_values": [
439
+ "enable",
440
+ 241347010900230,
441
+ "randomize",
442
+ 8,
443
+ 1,
444
+ "euler",
445
+ "simple",
446
+ 0,
447
+ 4,
448
+ "enable"
449
+ ]
450
+ },
451
+ {
452
+ "id": 18,
453
+ "type": "KSamplerAdvanced",
454
+ "pos": [
455
+ 29270.234375,
456
+ 1901.2767333984375
457
+ ],
458
+ "size": [
459
+ 252.353515625,
460
+ 546
461
+ ],
462
+ "flags": {
463
+ "collapsed": false
464
+ },
465
+ "order": 18,
466
+ "mode": 0,
467
+ "inputs": [
468
+ {
469
+ "name": "model",
470
+ "type": "MODEL",
471
+ "link": 47
472
+ },
473
+ {
474
+ "name": "positive",
475
+ "type": "CONDITIONING",
476
+ "link": 26
477
+ },
478
+ {
479
+ "name": "negative",
480
+ "type": "CONDITIONING",
481
+ "link": 27
482
+ },
483
+ {
484
+ "name": "latent_image",
485
+ "type": "LATENT",
486
+ "link": 52
487
+ }
488
+ ],
489
+ "outputs": [
490
+ {
491
+ "name": "LATENT",
492
+ "type": "LATENT",
493
+ "links": [
494
+ 28
495
+ ]
496
+ }
497
+ ],
498
+ "properties": {
499
+ "cnr_id": "comfy-core",
500
+ "ver": "0.3.47",
501
+ "Node name for S&R": "KSamplerAdvanced",
502
+ "ue_properties": {
503
+ "version": "7.8",
504
+ "widget_ue_connectable": {},
505
+ "input_ue_unconnectable": {}
506
+ }
507
+ },
508
+ "widgets_values": [
509
+ "disable",
510
+ 298951344552271,
511
+ "randomize",
512
+ 8,
513
+ 1,
514
+ "euler",
515
+ "simple",
516
+ 4,
517
+ 8,
518
+ "disable"
519
+ ]
520
+ },
521
+ {
522
+ "id": 20,
523
+ "type": "VHS_VideoCombine",
524
+ "pos": [
525
+ 29575.575678414556,
526
+ 1907.1888648963077
527
+ ],
528
+ "size": [
529
+ 705.6669921875,
530
+ 1383.497090242347
531
+ ],
532
+ "flags": {},
533
+ "order": 28,
534
+ "mode": 0,
535
+ "inputs": [
536
+ {
537
+ "name": "images",
538
+ "type": "IMAGE",
539
+ "link": 170
540
+ },
541
+ {
542
+ "name": "audio",
543
+ "shape": 7,
544
+ "type": "AUDIO",
545
+ "link": null
546
+ },
547
+ {
548
+ "name": "meta_batch",
549
+ "shape": 7,
550
+ "type": "VHS_BatchManager",
551
+ "link": null
552
+ },
553
+ {
554
+ "name": "vae",
555
+ "shape": 7,
556
+ "type": "VAE",
557
+ "link": null
558
+ }
559
+ ],
560
+ "outputs": [
561
+ {
562
+ "name": "Filenames",
563
+ "type": "VHS_FILENAMES",
564
+ "links": null
565
+ }
566
+ ],
567
+ "properties": {
568
+ "cnr_id": "comfyui-videohelpersuite",
569
+ "ver": "1.7.2",
570
+ "Node name for S&R": "VHS_VideoCombine",
571
+ "aux_id": "Kosinkadink/ComfyUI-VideoHelperSuite",
572
+ "ue_properties": {
573
+ "version": "7.8",
574
+ "widget_ue_connectable": {},
575
+ "input_ue_unconnectable": {}
576
+ }
577
+ },
578
+ "widgets_values": {
579
+ "frame_rate": 16,
580
+ "loop_count": 0,
581
+ "filename_prefix": "%date:yyyy-MM-dd%/wan2.2/video",
582
+ "format": "video/h264-mp4",
583
+ "pix_fmt": "yuv420p",
584
+ "crf": 19,
585
+ "save_metadata": true,
586
+ "trim_to_audio": false,
587
+ "pingpong": false,
588
+ "save_output": true,
589
+ "videopreview": {
590
+ "hidden": false,
591
+ "paused": false,
592
+ "params": {
593
+ "filename": "video_00001.mp4",
594
+ "subfolder": "2026-07-09\\wan2.2",
595
+ "type": "output",
596
+ "format": "video/h264-mp4",
597
+ "frame_rate": 16,
598
+ "workflow": "video_00001.png",
599
+ "fullpath": "D:\\sd\\output\\2026-07-09\\wan2.2\\video_00001.mp4"
600
+ },
601
+ "muted": false
602
+ }
603
+ }
604
+ },
605
+ {
606
+ "id": 39,
607
+ "type": "UnetLoaderGGUF",
608
+ "pos": [
609
+ 27085.405708029,
610
+ 1493.6196103755808
611
+ ],
612
+ "size": [
613
+ 270,
614
+ 58
615
+ ],
616
+ "flags": {},
617
+ "order": 1,
618
+ "mode": 0,
619
+ "inputs": [],
620
+ "outputs": [
621
+ {
622
+ "name": "MODEL",
623
+ "type": "MODEL",
624
+ "links": [
625
+ 144
626
+ ]
627
+ }
628
+ ],
629
+ "properties": {
630
+ "cnr_id": "comfyui-gguf",
631
+ "ver": "1.1.10",
632
+ "Node name for S&R": "UnetLoaderGGUF",
633
+ "ue_properties": {
634
+ "widget_ue_connectable": {},
635
+ "input_ue_unconnectable": {},
636
+ "version": "7.8"
637
+ }
638
+ },
639
+ "widgets_values": [
640
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
641
+ ]
642
+ },
643
+ {
644
+ "id": 40,
645
+ "type": "UnetLoaderGGUF",
646
+ "pos": [
647
+ 27085.875545789593,
648
+ 1609.6105258602975
649
+ ],
650
+ "size": [
651
+ 270,
652
+ 58
653
+ ],
654
+ "flags": {},
655
+ "order": 2,
656
+ "mode": 0,
657
+ "inputs": [],
658
+ "outputs": [
659
+ {
660
+ "name": "MODEL",
661
+ "type": "MODEL",
662
+ "links": [
663
+ 145
664
+ ]
665
+ }
666
+ ],
667
+ "properties": {
668
+ "cnr_id": "comfyui-gguf",
669
+ "ver": "1.1.10",
670
+ "Node name for S&R": "UnetLoaderGGUF",
671
+ "ue_properties": {
672
+ "widget_ue_connectable": {},
673
+ "input_ue_unconnectable": {},
674
+ "version": "7.8"
675
+ }
676
+ },
677
+ "widgets_values": [
678
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
679
+ ]
680
+ },
681
+ {
682
+ "id": 25,
683
+ "type": "CLIPLoader",
684
+ "pos": [
685
+ 27085.492869738202,
686
+ 2050.0593519193803
687
+ ],
688
+ "size": [
689
+ 270,
690
+ 106
691
+ ],
692
+ "flags": {},
693
+ "order": 3,
694
+ "mode": 0,
695
+ "inputs": [],
696
+ "outputs": [
697
+ {
698
+ "name": "CLIP",
699
+ "type": "CLIP",
700
+ "links": [
701
+ 48,
702
+ 49
703
+ ]
704
+ }
705
+ ],
706
+ "properties": {
707
+ "cnr_id": "comfy-core",
708
+ "ver": "0.3.48",
709
+ "Node name for S&R": "CLIPLoader",
710
+ "ue_properties": {
711
+ "widget_ue_connectable": {
712
+ "clip_name": true,
713
+ "type": true,
714
+ "device": true
715
+ },
716
+ "version": "7.8",
717
+ "input_ue_unconnectable": {}
718
+ }
719
+ },
720
+ "widgets_values": [
721
+ "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
722
+ "wan",
723
+ "default"
724
+ ]
725
+ },
726
+ {
727
+ "id": 5,
728
+ "type": "Power Lora Loader (rgthree)",
729
+ "pos": [
730
+ 27455.154296875,
731
+ 2013.7054443359375
732
+ ],
733
+ "size": [
734
+ 496.79267373832045,
735
+ 275.3138892438692
736
+ ],
737
+ "flags": {},
738
+ "order": 10,
739
+ "mode": 0,
740
+ "inputs": [
741
+ {
742
+ "dir": 3,
743
+ "name": "model",
744
+ "type": "MODEL",
745
+ "link": 42
746
+ },
747
+ {
748
+ "dir": 3,
749
+ "name": "clip",
750
+ "type": "CLIP",
751
+ "link": 48
752
+ }
753
+ ],
754
+ "outputs": [
755
+ {
756
+ "dir": 4,
757
+ "name": "MODEL",
758
+ "shape": 3,
759
+ "type": "MODEL",
760
+ "links": [
761
+ 44
762
+ ]
763
+ },
764
+ {
765
+ "dir": 4,
766
+ "name": "CLIP",
767
+ "shape": 3,
768
+ "type": "CLIP",
769
+ "links": [
770
+ 10,
771
+ 78,
772
+ 151
773
+ ]
774
+ }
775
+ ],
776
+ "properties": {
777
+ "cnr_id": "rgthree-comfy",
778
+ "ver": "c5ffa43de4ddb17244626a65a30700a05dd6b67d",
779
+ "Show Strengths": "Single Strength",
780
+ "Match": "",
781
+ "aux_id": "rgthree/rgthree-comfy",
782
+ "ue_properties": {
783
+ "widget_ue_connectable": {},
784
+ "version": "7.8",
785
+ "input_ue_unconnectable": {}
786
+ }
787
+ },
788
+ "widgets_values": [
789
+ {},
790
+ {
791
+ "type": "PowerLoraLoaderHeaderWidget"
792
+ },
793
+ {
794
+ "on": true,
795
+ "lora": "Wan_2_2_I2V_A14B_HIGH_lightx2v_4step_lora_v1030_rank_64_bf16.safetensors",
796
+ "strength": 1,
797
+ "strengthTwo": null
798
+ },
799
+ {
800
+ "on": true,
801
+ "lora": "scene_change_nsfw_v2.0_high_noise.safetensors",
802
+ "strength": 0.7,
803
+ "strengthTwo": null
804
+ },
805
+ {
806
+ "on": true,
807
+ "lora": "mql_doggy_b_v1_high_noise.safetensors",
808
+ "strength": 0.7,
809
+ "strengthTwo": null
810
+ },
811
+ {
812
+ "on": false,
813
+ "lora": "wan\\penis_detail_test3_000000300_high_noise.safetensors",
814
+ "strength": 1,
815
+ "strengthTwo": null
816
+ },
817
+ {
818
+ "on": false,
819
+ "lora": "wan\\penis_texture_test_2_000000300_high_noise.safetensors",
820
+ "strength": 2.8,
821
+ "strengthTwo": null
822
+ },
823
+ {
824
+ "on": false,
825
+ "lora": "wan\\penis_test_high_noise.safetensors",
826
+ "strength": 1,
827
+ "strengthTwo": null
828
+ },
829
+ {},
830
+ ""
831
+ ]
832
+ },
833
+ {
834
+ "id": 6,
835
+ "type": "Power Lora Loader (rgthree)",
836
+ "pos": [
837
+ 27450.704005508218,
838
+ 2346.34927377847
839
+ ],
840
+ "size": [
841
+ 585.5521610911674,
842
+ 264.77373397977635
843
+ ],
844
+ "flags": {},
845
+ "order": 11,
846
+ "mode": 0,
847
+ "inputs": [
848
+ {
849
+ "dir": 3,
850
+ "name": "model",
851
+ "type": "MODEL",
852
+ "link": 43
853
+ },
854
+ {
855
+ "dir": 3,
856
+ "name": "clip",
857
+ "type": "CLIP",
858
+ "link": 49
859
+ }
860
+ ],
861
+ "outputs": [
862
+ {
863
+ "dir": 4,
864
+ "name": "MODEL",
865
+ "shape": 3,
866
+ "type": "MODEL",
867
+ "links": [
868
+ 46
869
+ ]
870
+ },
871
+ {
872
+ "dir": 4,
873
+ "name": "CLIP",
874
+ "shape": 3,
875
+ "type": "CLIP",
876
+ "links": null
877
+ }
878
+ ],
879
+ "properties": {
880
+ "cnr_id": "rgthree-comfy",
881
+ "ver": "c5ffa43de4ddb17244626a65a30700a05dd6b67d",
882
+ "Show Strengths": "Single Strength",
883
+ "Match": "",
884
+ "aux_id": "rgthree/rgthree-comfy",
885
+ "ue_properties": {
886
+ "widget_ue_connectable": {},
887
+ "version": "7.8",
888
+ "input_ue_unconnectable": {}
889
+ }
890
+ },
891
+ "widgets_values": [
892
+ {},
893
+ {
894
+ "type": "PowerLoraLoaderHeaderWidget"
895
+ },
896
+ {
897
+ "on": true,
898
+ "lora": "wan2.2_i2v_A14b_low_noise_lora_rank64_lightx2v_4step_1022.safetensors",
899
+ "strength": 1,
900
+ "strengthTwo": null
901
+ },
902
+ {
903
+ "on": false,
904
+ "lora": "wan\\scene_change_v2_mw_low_noise.safetensors",
905
+ "strength": 1,
906
+ "strengthTwo": null
907
+ },
908
+ {
909
+ "on": true,
910
+ "lora": "mql_doggy_a_wan22_t2v_v1_low_noise.safetensors",
911
+ "strength": 0.6,
912
+ "strengthTwo": null
913
+ },
914
+ {
915
+ "on": true,
916
+ "lora": "DR34ML4Y_I2V_14B_LOW_V2.safetensors",
917
+ "strength": 1,
918
+ "strengthTwo": null
919
+ },
920
+ {
921
+ "on": false,
922
+ "lora": "wan\\penis_texture_test_2_000000300_low_noise.safetensors",
923
+ "strength": 1,
924
+ "strengthTwo": null
925
+ },
926
+ {
927
+ "on": false,
928
+ "lora": "wan\\penis_test_low_noise.safetensors",
929
+ "strength": 1,
930
+ "strengthTwo": null
931
+ },
932
+ {},
933
+ ""
934
+ ]
935
+ },
936
+ {
937
+ "id": 12,
938
+ "type": "CLIPTextEncode",
939
+ "pos": [
940
+ 27932.95597879707,
941
+ 2771.850316163266
942
+ ],
943
+ "size": [
944
+ 492.141357421875,
945
+ 127.27951049804688
946
+ ],
947
+ "flags": {},
948
+ "order": 13,
949
+ "mode": 0,
950
+ "inputs": [
951
+ {
952
+ "name": "clip",
953
+ "type": "CLIP",
954
+ "link": 10
955
+ }
956
+ ],
957
+ "outputs": [
958
+ {
959
+ "name": "CONDITIONING",
960
+ "type": "CONDITIONING",
961
+ "slot_index": 0,
962
+ "links": [
963
+ 128,
964
+ 153
965
+ ]
966
+ }
967
+ ],
968
+ "title": "CLIP Text Encode (Negative Prompt)",
969
+ "properties": {
970
+ "cnr_id": "comfy-core",
971
+ "ver": "0.3.47",
972
+ "Node name for S&R": "CLIPTextEncode",
973
+ "ue_properties": {
974
+ "version": "7.8",
975
+ "widget_ue_connectable": {},
976
+ "input_ue_unconnectable": {}
977
+ }
978
+ },
979
+ "widgets_values": [
980
+ ""
981
+ ],
982
+ "color": "#322",
983
+ "bgcolor": "#533"
984
+ },
985
+ {
986
+ "id": 11,
987
+ "type": "CLIPTextEncode",
988
+ "pos": [
989
+ 27921.20592312598,
990
+ 2478.051712480355
991
+ ],
992
+ "size": [
993
+ 489.7083740234375,
994
+ 231.58657836914062
995
+ ],
996
+ "flags": {},
997
+ "order": 14,
998
+ "mode": 0,
999
+ "inputs": [
1000
+ {
1001
+ "name": "clip",
1002
+ "type": "CLIP",
1003
+ "link": 78
1004
+ }
1005
+ ],
1006
+ "outputs": [
1007
+ {
1008
+ "name": "CONDITIONING",
1009
+ "type": "CONDITIONING",
1010
+ "slot_index": 0,
1011
+ "links": [
1012
+ 143
1013
+ ]
1014
+ }
1015
+ ],
1016
+ "title": "CLIP Text Encode (Positive Prompt)",
1017
+ "properties": {
1018
+ "cnr_id": "comfy-core",
1019
+ "ver": "0.3.47",
1020
+ "Node name for S&R": "CLIPTextEncode",
1021
+ "ue_properties": {
1022
+ "version": "7.8",
1023
+ "widget_ue_connectable": {},
1024
+ "input_ue_unconnectable": {}
1025
+ }
1026
+ },
1027
+ "widgets_values": [
1028
+ "mqldgy_b. A woman and a man sex scene. The woman is standing. The woman gets down on all fours on ground. The man kneels behind the woman, thrusts his penis back and forth in her vagina. This video is a first-person perspective video from the man's point of view, from above. The man's body-mounted camera. Viewer's crotch visible. Viewer's face invisible. The woman looks at viewer."
1029
+ ],
1030
+ "color": "#232",
1031
+ "bgcolor": "#353"
1032
+ },
1033
+ {
1034
+ "id": 14,
1035
+ "type": "LoadImage",
1036
+ "pos": [
1037
+ 28453.30464535701,
1038
+ 2423.844580226772
1039
+ ],
1040
+ "size": [
1041
+ 488.76470947265625,
1042
+ 523.6205444335938
1043
+ ],
1044
+ "flags": {},
1045
+ "order": 4,
1046
+ "mode": 0,
1047
+ "inputs": [],
1048
+ "outputs": [
1049
+ {
1050
+ "name": "IMAGE",
1051
+ "type": "IMAGE",
1052
+ "slot_index": 0,
1053
+ "links": [
1054
+ 146
1055
+ ]
1056
+ },
1057
+ {
1058
+ "name": "MASK",
1059
+ "type": "MASK",
1060
+ "slot_index": 1,
1061
+ "links": null
1062
+ }
1063
+ ],
1064
+ "properties": {
1065
+ "cnr_id": "comfy-core",
1066
+ "ver": "0.3.47",
1067
+ "Node name for S&R": "LoadImage",
1068
+ "ue_properties": {
1069
+ "version": "7.8",
1070
+ "widget_ue_connectable": {},
1071
+ "input_ue_unconnectable": {}
1072
+ }
1073
+ },
1074
+ "widgets_values": [
1075
+ "ComfyUI_00993_.png",
1076
+ "image"
1077
+ ]
1078
+ },
1079
+ {
1080
+ "id": 41,
1081
+ "type": "WanNativeResize_Dolphin",
1082
+ "pos": [
1083
+ 28077.456746430405,
1084
+ 2003.5213822441738
1085
+ ],
1086
+ "size": [
1087
+ 310.2759765625,
1088
+ 146
1089
+ ],
1090
+ "flags": {},
1091
+ "order": 7,
1092
+ "mode": 0,
1093
+ "inputs": [
1094
+ {
1095
+ "name": "image",
1096
+ "type": "IMAGE",
1097
+ "link": 146
1098
+ }
1099
+ ],
1100
+ "outputs": [
1101
+ {
1102
+ "name": "IMAGE",
1103
+ "type": "IMAGE",
1104
+ "links": [
1105
+ 147
1106
+ ]
1107
+ },
1108
+ {
1109
+ "name": "width",
1110
+ "type": "INT",
1111
+ "links": [
1112
+ 148,
1113
+ 156
1114
+ ]
1115
+ },
1116
+ {
1117
+ "name": "height",
1118
+ "type": "INT",
1119
+ "links": [
1120
+ 149,
1121
+ 157
1122
+ ]
1123
+ }
1124
+ ],
1125
+ "properties": {
1126
+ "Node name for S&R": "WanNativeResize_Dolphin",
1127
+ "ue_properties": {
1128
+ "widget_ue_connectable": {},
1129
+ "input_ue_unconnectable": {},
1130
+ "version": "7.8"
1131
+ }
1132
+ },
1133
+ "widgets_values": [
1134
+ "720P (1280x720)",
1135
+ "lanczos",
1136
+ 16
1137
+ ]
1138
+ },
1139
+ {
1140
+ "id": 13,
1141
+ "type": "WanImageToVideo",
1142
+ "pos": [
1143
+ 28417.96484375,
1144
+ 1912.74853515625
1145
+ ],
1146
+ "size": [
1147
+ 492.7348327636719,
1148
+ 210
1149
+ ],
1150
+ "flags": {},
1151
+ "order": 16,
1152
+ "mode": 0,
1153
+ "inputs": [
1154
+ {
1155
+ "name": "positive",
1156
+ "type": "CONDITIONING",
1157
+ "link": 143
1158
+ },
1159
+ {
1160
+ "name": "negative",
1161
+ "type": "CONDITIONING",
1162
+ "link": 128
1163
+ },
1164
+ {
1165
+ "name": "vae",
1166
+ "type": "VAE",
1167
+ "link": 13
1168
+ },
1169
+ {
1170
+ "name": "clip_vision_output",
1171
+ "shape": 7,
1172
+ "type": "CLIP_VISION_OUTPUT",
1173
+ "link": null
1174
+ },
1175
+ {
1176
+ "name": "start_image",
1177
+ "shape": 7,
1178
+ "type": "IMAGE",
1179
+ "link": 147
1180
+ },
1181
+ {
1182
+ "name": "width",
1183
+ "type": "INT",
1184
+ "widget": {
1185
+ "name": "width"
1186
+ },
1187
+ "link": 148
1188
+ },
1189
+ {
1190
+ "name": "height",
1191
+ "type": "INT",
1192
+ "widget": {
1193
+ "name": "height"
1194
+ },
1195
+ "link": 149
1196
+ }
1197
+ ],
1198
+ "outputs": [
1199
+ {
1200
+ "name": "positive",
1201
+ "type": "CONDITIONING",
1202
+ "slot_index": 0,
1203
+ "links": [
1204
+ 16,
1205
+ 26
1206
+ ]
1207
+ },
1208
+ {
1209
+ "name": "negative",
1210
+ "type": "CONDITIONING",
1211
+ "slot_index": 1,
1212
+ "links": [
1213
+ 17,
1214
+ 27
1215
+ ]
1216
+ },
1217
+ {
1218
+ "name": "latent",
1219
+ "type": "LATENT",
1220
+ "slot_index": 2,
1221
+ "links": [
1222
+ 18
1223
+ ]
1224
+ }
1225
+ ],
1226
+ "properties": {
1227
+ "cnr_id": "comfy-core",
1228
+ "ver": "0.3.47",
1229
+ "Node name for S&R": "WanImageToVideo",
1230
+ "ue_properties": {
1231
+ "version": "7.8",
1232
+ "widget_ue_connectable": {},
1233
+ "input_ue_unconnectable": {}
1234
+ }
1235
+ },
1236
+ "widgets_values": [
1237
+ 768,
1238
+ 768,
1239
+ 81,
1240
+ 1
1241
+ ]
1242
+ },
1243
+ {
1244
+ "id": 42,
1245
+ "type": "ImageFromBatch",
1246
+ "pos": [
1247
+ 29900,
1248
+ 2500
1249
+ ],
1250
+ "size": [
1251
+ 270,
1252
+ 106
1253
+ ],
1254
+ "flags": {},
1255
+ "order": 21,
1256
+ "mode": 0,
1257
+ "inputs": [
1258
+ {
1259
+ "name": "image",
1260
+ "type": "IMAGE",
1261
+ "link": 150
1262
+ }
1263
+ ],
1264
+ "outputs": [
1265
+ {
1266
+ "name": "IMAGE",
1267
+ "type": "IMAGE",
1268
+ "links": [
1269
+ 155
1270
+ ]
1271
+ }
1272
+ ],
1273
+ "title": "Get Last Frame of Clip A",
1274
+ "properties": {
1275
+ "cnr_id": "comfy-core",
1276
+ "ver": "0.3.47",
1277
+ "Node name for S&R": "ImageFromBatch"
1278
+ },
1279
+ "widgets_values": [
1280
+ -1,
1281
+ 1
1282
+ ]
1283
+ },
1284
+ {
1285
+ "id": 43,
1286
+ "type": "CLIPTextEncode",
1287
+ "pos": [
1288
+ 29900,
1289
+ 2700
1290
+ ],
1291
+ "size": [
1292
+ 489.708,
1293
+ 231.586
1294
+ ],
1295
+ "flags": {},
1296
+ "order": 22,
1297
+ "mode": 0,
1298
+ "inputs": [
1299
+ {
1300
+ "name": "clip",
1301
+ "type": "CLIP",
1302
+ "link": 151
1303
+ }
1304
+ ],
1305
+ "outputs": [
1306
+ {
1307
+ "name": "CONDITIONING",
1308
+ "type": "CONDITIONING",
1309
+ "slot_index": 0,
1310
+ "links": [
1311
+ 152
1312
+ ]
1313
+ }
1314
+ ],
1315
+ "title": "CLIP Text Encode (Scene 2 / Scene Change Prompt)",
1316
+ "properties": {
1317
+ "cnr_id": "comfy-core",
1318
+ "ver": "0.3.47",
1319
+ "Node name for S&R": "CLIPTextEncode"
1320
+ },
1321
+ "widgets_values": [
1322
+ ""
1323
+ ],
1324
+ "color": "#232",
1325
+ "bgcolor": "#353"
1326
+ },
1327
+ {
1328
+ "id": 44,
1329
+ "type": "WanImageToVideo",
1330
+ "pos": [
1331
+ 30300,
1332
+ 2500
1333
+ ],
1334
+ "size": [
1335
+ 492.735,
1336
+ 210
1337
+ ],
1338
+ "flags": {},
1339
+ "order": 23,
1340
+ "mode": 0,
1341
+ "inputs": [
1342
+ {
1343
+ "name": "positive",
1344
+ "type": "CONDITIONING",
1345
+ "link": 152
1346
+ },
1347
+ {
1348
+ "name": "negative",
1349
+ "type": "CONDITIONING",
1350
+ "link": 153
1351
+ },
1352
+ {
1353
+ "name": "vae",
1354
+ "type": "VAE",
1355
+ "link": 154
1356
+ },
1357
+ {
1358
+ "name": "clip_vision_output",
1359
+ "shape": 7,
1360
+ "type": "CLIP_VISION_OUTPUT",
1361
+ "link": null
1362
+ },
1363
+ {
1364
+ "name": "start_image",
1365
+ "shape": 7,
1366
+ "type": "IMAGE",
1367
+ "link": 155
1368
+ },
1369
+ {
1370
+ "name": "width",
1371
+ "type": "INT",
1372
+ "widget": {
1373
+ "name": "width"
1374
+ },
1375
+ "link": 156
1376
+ },
1377
+ {
1378
+ "name": "height",
1379
+ "type": "INT",
1380
+ "widget": {
1381
+ "name": "height"
1382
+ },
1383
+ "link": 157
1384
+ }
1385
+ ],
1386
+ "outputs": [
1387
+ {
1388
+ "name": "positive",
1389
+ "type": "CONDITIONING",
1390
+ "slot_index": 0,
1391
+ "links": [
1392
+ 159,
1393
+ 163
1394
+ ]
1395
+ },
1396
+ {
1397
+ "name": "negative",
1398
+ "type": "CONDITIONING",
1399
+ "slot_index": 1,
1400
+ "links": [
1401
+ 160,
1402
+ 164
1403
+ ]
1404
+ },
1405
+ {
1406
+ "name": "latent",
1407
+ "type": "LATENT",
1408
+ "slot_index": 2,
1409
+ "links": [
1410
+ 161
1411
+ ]
1412
+ }
1413
+ ],
1414
+ "title": "Wan Image To Video (Clip B / Scene Change)",
1415
+ "properties": {
1416
+ "cnr_id": "comfy-core",
1417
+ "ver": "0.3.47",
1418
+ "Node name for S&R": "WanImageToVideo"
1419
+ },
1420
+ "widgets_values": [
1421
+ 768,
1422
+ 768,
1423
+ 81,
1424
+ 1
1425
+ ]
1426
+ },
1427
+ {
1428
+ "id": 45,
1429
+ "type": "KSamplerAdvanced",
1430
+ "pos": [
1431
+ 30850,
1432
+ 1900
1433
+ ],
1434
+ "size": [
1435
+ 252.353,
1436
+ 546
1437
+ ],
1438
+ "flags": {},
1439
+ "order": 24,
1440
+ "mode": 0,
1441
+ "inputs": [
1442
+ {
1443
+ "name": "model",
1444
+ "type": "MODEL",
1445
+ "link": 158
1446
+ },
1447
+ {
1448
+ "name": "positive",
1449
+ "type": "CONDITIONING",
1450
+ "link": 159
1451
+ },
1452
+ {
1453
+ "name": "negative",
1454
+ "type": "CONDITIONING",
1455
+ "link": 160
1456
+ },
1457
+ {
1458
+ "name": "latent_image",
1459
+ "type": "LATENT",
1460
+ "link": 161
1461
+ }
1462
+ ],
1463
+ "outputs": [
1464
+ {
1465
+ "name": "LATENT",
1466
+ "type": "LATENT",
1467
+ "links": [
1468
+ 165
1469
+ ]
1470
+ }
1471
+ ],
1472
+ "title": "KSampler (Clip B, High Noise Model)",
1473
+ "properties": {
1474
+ "cnr_id": "comfy-core",
1475
+ "ver": "0.3.47",
1476
+ "Node name for S&R": "KSamplerAdvanced"
1477
+ },
1478
+ "widgets_values": [
1479
+ "enable",
1480
+ 123456789012345,
1481
+ "randomize",
1482
+ 8,
1483
+ 1,
1484
+ "euler",
1485
+ "simple",
1486
+ 0,
1487
+ 4,
1488
+ "enable"
1489
+ ]
1490
+ },
1491
+ {
1492
+ "id": 46,
1493
+ "type": "KSamplerAdvanced",
1494
+ "pos": [
1495
+ 31150,
1496
+ 1900
1497
+ ],
1498
+ "size": [
1499
+ 252.353,
1500
+ 546
1501
+ ],
1502
+ "flags": {},
1503
+ "order": 25,
1504
+ "mode": 0,
1505
+ "inputs": [
1506
+ {
1507
+ "name": "model",
1508
+ "type": "MODEL",
1509
+ "link": 162
1510
+ },
1511
+ {
1512
+ "name": "positive",
1513
+ "type": "CONDITIONING",
1514
+ "link": 163
1515
+ },
1516
+ {
1517
+ "name": "negative",
1518
+ "type": "CONDITIONING",
1519
+ "link": 164
1520
+ },
1521
+ {
1522
+ "name": "latent_image",
1523
+ "type": "LATENT",
1524
+ "link": 165
1525
+ }
1526
+ ],
1527
+ "outputs": [
1528
+ {
1529
+ "name": "LATENT",
1530
+ "type": "LATENT",
1531
+ "links": [
1532
+ 166
1533
+ ]
1534
+ }
1535
+ ],
1536
+ "title": "KSampler (Clip B, Low Noise Model)",
1537
+ "properties": {
1538
+ "cnr_id": "comfy-core",
1539
+ "ver": "0.3.47",
1540
+ "Node name for S&R": "KSamplerAdvanced"
1541
+ },
1542
+ "widgets_values": [
1543
+ "disable",
1544
+ 298951344552399,
1545
+ "randomize",
1546
+ 8,
1547
+ 1,
1548
+ "euler",
1549
+ "simple",
1550
+ 4,
1551
+ 8,
1552
+ "disable"
1553
+ ]
1554
+ },
1555
+ {
1556
+ "id": 47,
1557
+ "type": "VAEDecode",
1558
+ "pos": [
1559
+ 31450,
1560
+ 2500
1561
+ ],
1562
+ "size": [
1563
+ 210,
1564
+ 46
1565
+ ],
1566
+ "flags": {},
1567
+ "order": 26,
1568
+ "mode": 0,
1569
+ "inputs": [
1570
+ {
1571
+ "name": "samples",
1572
+ "type": "LATENT",
1573
+ "link": 166
1574
+ },
1575
+ {
1576
+ "name": "vae",
1577
+ "type": "VAE",
1578
+ "link": 167
1579
+ }
1580
+ ],
1581
+ "outputs": [
1582
+ {
1583
+ "name": "IMAGE",
1584
+ "type": "IMAGE",
1585
+ "slot_index": 0,
1586
+ "links": [
1587
+ 169
1588
+ ]
1589
+ }
1590
+ ],
1591
+ "title": "VAE Decode (Clip B)",
1592
+ "properties": {
1593
+ "cnr_id": "comfy-core",
1594
+ "ver": "0.3.47",
1595
+ "Node name for S&R": "VAEDecode"
1596
+ },
1597
+ "widgets_values": []
1598
+ },
1599
+ {
1600
+ "id": 48,
1601
+ "type": "ImageBatch",
1602
+ "pos": [
1603
+ 31450,
1604
+ 2700
1605
+ ],
1606
+ "size": [
1607
+ 210,
1608
+ 80
1609
+ ],
1610
+ "flags": {},
1611
+ "order": 27,
1612
+ "mode": 0,
1613
+ "inputs": [
1614
+ {
1615
+ "name": "image1",
1616
+ "type": "IMAGE",
1617
+ "link": 168
1618
+ },
1619
+ {
1620
+ "name": "image2",
1621
+ "type": "IMAGE",
1622
+ "link": 169
1623
+ }
1624
+ ],
1625
+ "outputs": [
1626
+ {
1627
+ "name": "IMAGE",
1628
+ "type": "IMAGE",
1629
+ "links": [
1630
+ 170
1631
+ ]
1632
+ }
1633
+ ],
1634
+ "title": "Concatenate Clip A + Clip B Frames",
1635
+ "properties": {
1636
+ "cnr_id": "comfy-core",
1637
+ "ver": "0.3.47",
1638
+ "Node name for S&R": "ImageBatch"
1639
+ },
1640
+ "widgets_values": []
1641
+ }
1642
+ ],
1643
+ "links": [
1644
+ [
1645
+ 3,
1646
+ 7,
1647
+ 0,
1648
+ 9,
1649
+ 0,
1650
+ "MODEL"
1651
+ ],
1652
+ [
1653
+ 4,
1654
+ 8,
1655
+ 0,
1656
+ 10,
1657
+ 0,
1658
+ "MODEL"
1659
+ ],
1660
+ [
1661
+ 10,
1662
+ 5,
1663
+ 1,
1664
+ 12,
1665
+ 0,
1666
+ "CLIP"
1667
+ ],
1668
+ [
1669
+ 13,
1670
+ 4,
1671
+ 0,
1672
+ 13,
1673
+ 2,
1674
+ "VAE"
1675
+ ],
1676
+ [
1677
+ 16,
1678
+ 13,
1679
+ 0,
1680
+ 15,
1681
+ 1,
1682
+ "CONDITIONING"
1683
+ ],
1684
+ [
1685
+ 17,
1686
+ 13,
1687
+ 1,
1688
+ 15,
1689
+ 2,
1690
+ "CONDITIONING"
1691
+ ],
1692
+ [
1693
+ 18,
1694
+ 13,
1695
+ 2,
1696
+ 15,
1697
+ 3,
1698
+ "LATENT"
1699
+ ],
1700
+ [
1701
+ 26,
1702
+ 13,
1703
+ 0,
1704
+ 18,
1705
+ 1,
1706
+ "CONDITIONING"
1707
+ ],
1708
+ [
1709
+ 27,
1710
+ 13,
1711
+ 1,
1712
+ 18,
1713
+ 2,
1714
+ "CONDITIONING"
1715
+ ],
1716
+ [
1717
+ 28,
1718
+ 18,
1719
+ 0,
1720
+ 19,
1721
+ 0,
1722
+ "LATENT"
1723
+ ],
1724
+ [
1725
+ 29,
1726
+ 4,
1727
+ 0,
1728
+ 19,
1729
+ 1,
1730
+ "VAE"
1731
+ ],
1732
+ [
1733
+ 42,
1734
+ 9,
1735
+ 0,
1736
+ 5,
1737
+ 0,
1738
+ "MODEL"
1739
+ ],
1740
+ [
1741
+ 43,
1742
+ 10,
1743
+ 0,
1744
+ 6,
1745
+ 0,
1746
+ "MODEL"
1747
+ ],
1748
+ [
1749
+ 44,
1750
+ 5,
1751
+ 0,
1752
+ 23,
1753
+ 0,
1754
+ "MODEL"
1755
+ ],
1756
+ [
1757
+ 45,
1758
+ 23,
1759
+ 0,
1760
+ 15,
1761
+ 0,
1762
+ "MODEL"
1763
+ ],
1764
+ [
1765
+ 46,
1766
+ 6,
1767
+ 0,
1768
+ 24,
1769
+ 0,
1770
+ "MODEL"
1771
+ ],
1772
+ [
1773
+ 47,
1774
+ 24,
1775
+ 0,
1776
+ 18,
1777
+ 0,
1778
+ "MODEL"
1779
+ ],
1780
+ [
1781
+ 48,
1782
+ 25,
1783
+ 0,
1784
+ 5,
1785
+ 1,
1786
+ "CLIP"
1787
+ ],
1788
+ [
1789
+ 49,
1790
+ 25,
1791
+ 0,
1792
+ 6,
1793
+ 1,
1794
+ "CLIP"
1795
+ ],
1796
+ [
1797
+ 51,
1798
+ 19,
1799
+ 0,
1800
+ 20,
1801
+ 0,
1802
+ "IMAGE"
1803
+ ],
1804
+ [
1805
+ 52,
1806
+ 15,
1807
+ 0,
1808
+ 18,
1809
+ 3,
1810
+ "LATENT"
1811
+ ],
1812
+ [
1813
+ 78,
1814
+ 5,
1815
+ 1,
1816
+ 11,
1817
+ 0,
1818
+ "CLIP"
1819
+ ],
1820
+ [
1821
+ 128,
1822
+ 12,
1823
+ 0,
1824
+ 13,
1825
+ 1,
1826
+ "CONDITIONING"
1827
+ ],
1828
+ [
1829
+ 143,
1830
+ 11,
1831
+ 0,
1832
+ 13,
1833
+ 0,
1834
+ "CONDITIONING"
1835
+ ],
1836
+ [
1837
+ 144,
1838
+ 39,
1839
+ 0,
1840
+ 7,
1841
+ 0,
1842
+ "MODEL"
1843
+ ],
1844
+ [
1845
+ 145,
1846
+ 40,
1847
+ 0,
1848
+ 8,
1849
+ 0,
1850
+ "MODEL"
1851
+ ],
1852
+ [
1853
+ 146,
1854
+ 14,
1855
+ 0,
1856
+ 41,
1857
+ 0,
1858
+ "IMAGE"
1859
+ ],
1860
+ [
1861
+ 147,
1862
+ 41,
1863
+ 0,
1864
+ 13,
1865
+ 4,
1866
+ "IMAGE"
1867
+ ],
1868
+ [
1869
+ 148,
1870
+ 41,
1871
+ 1,
1872
+ 13,
1873
+ 5,
1874
+ "INT"
1875
+ ],
1876
+ [
1877
+ 149,
1878
+ 41,
1879
+ 2,
1880
+ 13,
1881
+ 6,
1882
+ "INT"
1883
+ ],
1884
+ [
1885
+ 150,
1886
+ 19,
1887
+ 0,
1888
+ 42,
1889
+ 0,
1890
+ "IMAGE"
1891
+ ],
1892
+ [
1893
+ 151,
1894
+ 5,
1895
+ 1,
1896
+ 43,
1897
+ 0,
1898
+ "CLIP"
1899
+ ],
1900
+ [
1901
+ 152,
1902
+ 43,
1903
+ 0,
1904
+ 44,
1905
+ 0,
1906
+ "CONDITIONING"
1907
+ ],
1908
+ [
1909
+ 153,
1910
+ 12,
1911
+ 0,
1912
+ 44,
1913
+ 1,
1914
+ "CONDITIONING"
1915
+ ],
1916
+ [
1917
+ 154,
1918
+ 4,
1919
+ 0,
1920
+ 44,
1921
+ 2,
1922
+ "VAE"
1923
+ ],
1924
+ [
1925
+ 155,
1926
+ 42,
1927
+ 0,
1928
+ 44,
1929
+ 4,
1930
+ "IMAGE"
1931
+ ],
1932
+ [
1933
+ 156,
1934
+ 41,
1935
+ 1,
1936
+ 44,
1937
+ 5,
1938
+ "INT"
1939
+ ],
1940
+ [
1941
+ 157,
1942
+ 41,
1943
+ 2,
1944
+ 44,
1945
+ 6,
1946
+ "INT"
1947
+ ],
1948
+ [
1949
+ 158,
1950
+ 23,
1951
+ 0,
1952
+ 45,
1953
+ 0,
1954
+ "MODEL"
1955
+ ],
1956
+ [
1957
+ 159,
1958
+ 44,
1959
+ 0,
1960
+ 45,
1961
+ 1,
1962
+ "CONDITIONING"
1963
+ ],
1964
+ [
1965
+ 160,
1966
+ 44,
1967
+ 1,
1968
+ 45,
1969
+ 2,
1970
+ "CONDITIONING"
1971
+ ],
1972
+ [
1973
+ 161,
1974
+ 44,
1975
+ 2,
1976
+ 45,
1977
+ 3,
1978
+ "LATENT"
1979
+ ],
1980
+ [
1981
+ 162,
1982
+ 24,
1983
+ 0,
1984
+ 46,
1985
+ 0,
1986
+ "MODEL"
1987
+ ],
1988
+ [
1989
+ 163,
1990
+ 44,
1991
+ 0,
1992
+ 46,
1993
+ 1,
1994
+ "CONDITIONING"
1995
+ ],
1996
+ [
1997
+ 164,
1998
+ 44,
1999
+ 1,
2000
+ 46,
2001
+ 2,
2002
+ "CONDITIONING"
2003
+ ],
2004
+ [
2005
+ 165,
2006
+ 45,
2007
+ 0,
2008
+ 46,
2009
+ 3,
2010
+ "LATENT"
2011
+ ],
2012
+ [
2013
+ 166,
2014
+ 46,
2015
+ 0,
2016
+ 47,
2017
+ 0,
2018
+ "LATENT"
2019
+ ],
2020
+ [
2021
+ 167,
2022
+ 4,
2023
+ 0,
2024
+ 47,
2025
+ 1,
2026
+ "VAE"
2027
+ ],
2028
+ [
2029
+ 168,
2030
+ 19,
2031
+ 0,
2032
+ 48,
2033
+ 0,
2034
+ "IMAGE"
2035
+ ],
2036
+ [
2037
+ 169,
2038
+ 47,
2039
+ 0,
2040
+ 48,
2041
+ 1,
2042
+ "IMAGE"
2043
+ ],
2044
+ [
2045
+ 170,
2046
+ 48,
2047
+ 0,
2048
+ 20,
2049
+ 0,
2050
+ "IMAGE"
2051
+ ]
2052
+ ],
2053
+ "groups": [],
2054
+ "config": {},
2055
+ "extra": {
2056
+ "ds": {
2057
+ "scale": 0.5559917313492576,
2058
+ "offset": [
2059
+ -26186.496366607487,
2060
+ -1194.6780790078822
2061
+ ]
2062
+ },
2063
+ "workflowRendererVersion": "LG",
2064
+ "ue_links": [],
2065
+ "links_added_by_ue": [],
2066
+ "frontendVersion": "1.45.20",
2067
+ "VHS_latentpreview": false,
2068
+ "VHS_latentpreviewrate": 0,
2069
+ "VHS_MetadataImage": false,
2070
+ "VHS_KeepIntermediate": true,
2071
+ "anomalous_hashes": {}
2072
+ },
2073
+ "version": 0.4
2074
+ }
_archive/workflows/krea2 inpaint.json ADDED
@@ -0,0 +1,1510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "94d9ce78-c6d4-45b5-b65f-7f7cdda98616",
3
+ "revision": 0,
4
+ "last_node_id": 49,
5
+ "last_link_id": 87,
6
+ "nodes": [
7
+ {
8
+ "id": 25,
9
+ "type": "Label (rgthree)",
10
+ "pos": [
11
+ 502.89126314973754,
12
+ 568.6440437433788
13
+ ],
14
+ "size": [
15
+ 547.05810546875,
16
+ 25
17
+ ],
18
+ "flags": {
19
+ "allow_interaction": true
20
+ },
21
+ "order": 0,
22
+ "mode": 0,
23
+ "inputs": [],
24
+ "outputs": [],
25
+ "title": "And choose Open in Mask Editor | Image Canvas",
26
+ "properties": {
27
+ "fontSize": 25,
28
+ "fontFamily": "Arial",
29
+ "fontColor": "#ffffff",
30
+ "textAlign": "left",
31
+ "backgroundColor": "transparent",
32
+ "padding": 0,
33
+ "borderRadius": 0,
34
+ "angle": 0,
35
+ "ue_properties": {
36
+ "widget_ue_connectable": {},
37
+ "input_ue_unconnectable": {},
38
+ "version": "7.5.2"
39
+ }
40
+ },
41
+ "color": "#fff0",
42
+ "bgcolor": "#fff0"
43
+ },
44
+ {
45
+ "id": 26,
46
+ "type": "Label (rgthree)",
47
+ "pos": [
48
+ 557.4292270417669,
49
+ 521.8881876346229
50
+ ],
51
+ "size": [
52
+ 408.50830078125,
53
+ 25
54
+ ],
55
+ "flags": {
56
+ "allow_interaction": true
57
+ },
58
+ "order": 1,
59
+ "mode": 0,
60
+ "inputs": [],
61
+ "outputs": [],
62
+ "title": "To inpaint mask, right click on mouse",
63
+ "properties": {
64
+ "fontSize": 25,
65
+ "fontFamily": "Arial",
66
+ "fontColor": "#ffffff",
67
+ "textAlign": "left",
68
+ "backgroundColor": "transparent",
69
+ "padding": 0,
70
+ "borderRadius": 0,
71
+ "angle": 0,
72
+ "ue_properties": {
73
+ "widget_ue_connectable": {},
74
+ "input_ue_unconnectable": {},
75
+ "version": "7.5.2"
76
+ }
77
+ },
78
+ "color": "#fff0",
79
+ "bgcolor": "#fff0"
80
+ },
81
+ {
82
+ "id": 32,
83
+ "type": "Label (rgthree)",
84
+ "pos": [
85
+ -1027.309220513278,
86
+ -587.2079374535729
87
+ ],
88
+ "size": [
89
+ 730.810546875,
90
+ 50
91
+ ],
92
+ "flags": {
93
+ "allow_interaction": true
94
+ },
95
+ "order": 2,
96
+ "mode": 0,
97
+ "inputs": [],
98
+ "outputs": [],
99
+ "title": "Notes (Recomend it for a review)",
100
+ "properties": {
101
+ "fontSize": 50,
102
+ "fontFamily": "Arial",
103
+ "fontColor": "#ffffff",
104
+ "textAlign": "left",
105
+ "backgroundColor": "transparent",
106
+ "padding": 0,
107
+ "borderRadius": 0,
108
+ "angle": 0,
109
+ "ue_properties": {
110
+ "widget_ue_connectable": {},
111
+ "version": "7.1",
112
+ "input_ue_unconnectable": {}
113
+ }
114
+ },
115
+ "color": "#fff0",
116
+ "bgcolor": "#fff0"
117
+ },
118
+ {
119
+ "id": 35,
120
+ "type": "LoraLoaderModelOnly",
121
+ "pos": [
122
+ 228.5066115955086,
123
+ -565.064232779069
124
+ ],
125
+ "size": [
126
+ 270,
127
+ 82
128
+ ],
129
+ "flags": {},
130
+ "order": 11,
131
+ "mode": 4,
132
+ "inputs": [
133
+ {
134
+ "name": "model",
135
+ "type": "MODEL",
136
+ "link": 40
137
+ }
138
+ ],
139
+ "outputs": [
140
+ {
141
+ "name": "MODEL",
142
+ "type": "MODEL",
143
+ "links": [
144
+ 41
145
+ ]
146
+ }
147
+ ],
148
+ "title": "Turbo Lora",
149
+ "properties": {
150
+ "cnr_id": "comfy-core",
151
+ "ver": "0.26.0",
152
+ "Node name for S&R": "LoraLoaderModelOnly",
153
+ "ue_properties": {
154
+ "widget_ue_connectable": {},
155
+ "input_ue_unconnectable": {},
156
+ "version": "7.8"
157
+ }
158
+ },
159
+ "widgets_values": [
160
+ "krea2\\krea2_turbo_lora_rank_64_bf16.safetensors",
161
+ 1
162
+ ]
163
+ },
164
+ {
165
+ "id": 36,
166
+ "type": "PathchSageAttentionKJ",
167
+ "pos": [
168
+ 1340.6725366844717,
169
+ 356.52514598362905
170
+ ],
171
+ "size": [
172
+ 270,
173
+ 82
174
+ ],
175
+ "flags": {},
176
+ "order": 15,
177
+ "mode": 0,
178
+ "inputs": [
179
+ {
180
+ "name": "model",
181
+ "type": "MODEL",
182
+ "link": 42
183
+ }
184
+ ],
185
+ "outputs": [
186
+ {
187
+ "name": "MODEL",
188
+ "type": "MODEL",
189
+ "links": [
190
+ 44
191
+ ]
192
+ }
193
+ ],
194
+ "properties": {
195
+ "cnr_id": "comfyui-kjnodes",
196
+ "ver": "1.4.7",
197
+ "Node name for S&R": "PathchSageAttentionKJ",
198
+ "ue_properties": {
199
+ "widget_ue_connectable": {},
200
+ "input_ue_unconnectable": {},
201
+ "version": "7.8"
202
+ }
203
+ },
204
+ "widgets_values": [
205
+ "auto",
206
+ false
207
+ ]
208
+ },
209
+ {
210
+ "id": 37,
211
+ "type": "Fast Groups Bypasser (rgthree)",
212
+ "pos": [
213
+ 223.1305491471883,
214
+ -314.79010528541266
215
+ ],
216
+ "size": [
217
+ 397.4609463381595,
218
+ 100.1651829564488
219
+ ],
220
+ "flags": {},
221
+ "order": 3,
222
+ "mode": 0,
223
+ "inputs": [],
224
+ "outputs": [
225
+ {
226
+ "name": "OPT_CONNECTION",
227
+ "type": "*",
228
+ "links": null
229
+ }
230
+ ],
231
+ "properties": {
232
+ "matchColors": "",
233
+ "matchTitle": "",
234
+ "showNav": true,
235
+ "showAllGraphs": true,
236
+ "sort": "position",
237
+ "customSortAlphabet": "",
238
+ "toggleRestriction": "default",
239
+ "ue_properties": {
240
+ "widget_ue_connectable": {},
241
+ "input_ue_unconnectable": {},
242
+ "version": "7.8"
243
+ }
244
+ }
245
+ },
246
+ {
247
+ "id": 24,
248
+ "type": "Label (rgthree)",
249
+ "pos": [
250
+ 1013.1815196156713,
251
+ -901.8562984252738
252
+ ],
253
+ "size": [
254
+ 867.2607421875,
255
+ 150
256
+ ],
257
+ "flags": {
258
+ "allow_interaction": true
259
+ },
260
+ "order": 4,
261
+ "mode": 0,
262
+ "inputs": [],
263
+ "outputs": [],
264
+ "title": "krea2 Inpaint",
265
+ "properties": {
266
+ "fontSize": 150,
267
+ "fontFamily": "Arial",
268
+ "fontColor": "#ffd000",
269
+ "textAlign": "left",
270
+ "backgroundColor": "transparent",
271
+ "padding": 0,
272
+ "borderRadius": 0,
273
+ "angle": 0,
274
+ "ue_properties": {
275
+ "widget_ue_connectable": {},
276
+ "version": "7.1",
277
+ "input_ue_unconnectable": {}
278
+ }
279
+ },
280
+ "color": "#fff0",
281
+ "bgcolor": "#fff0"
282
+ },
283
+ {
284
+ "id": 33,
285
+ "type": "Note",
286
+ "pos": [
287
+ -1146.177246566986,
288
+ -451.25718147677554
289
+ ],
290
+ "size": [
291
+ 885.1822916666667,
292
+ 509.9479166666667
293
+ ],
294
+ "flags": {},
295
+ "order": 5,
296
+ "mode": 0,
297
+ "inputs": [],
298
+ "outputs": [],
299
+ "title": "Models and .whl files",
300
+ "properties": {
301
+ "ue_properties": {
302
+ "widget_ue_connectable": {},
303
+ "version": "7.1",
304
+ "input_ue_unconnectable": {}
305
+ }
306
+ },
307
+ "widgets_values": [
308
+ "Required models for Krea2 Workflow:\n\nKrea2 (Raw or Turbo): https://huggingface.co/Comfy-Org/Krea-2/tree/main/diffusion_models\nText Encoder: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders\nVAE: https://huggingface.co/Comfy-Org/Krea-2/tree/main/vae\n\nAlso I recommend download uncendored encoder for nsfw and danbooru prompting https://huggingface.co/ahmed22xa/Huihui-Qwen3-VL-4B-Instruct-abliterated-comfy/resolve/main/Huihui-Qwen3-VL-4B-Instruct-abliterated.safetensors\n\nAlso recomend using VAE Wan 2.1 for better result https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors\n\nTurbo Lora (if you use Raw model): https://huggingface.co/Comfy-Org/Krea-2/blob/main/loras/krea2_turbo_lora_rank_64_bf16.safetensors\n\n\nFile save location\n\nComfyUI/\n├───📂 models/\n│ ├───📂 diffusion_models/\n│ │ ├─── krea2_turbo_bf16.safetensors\n│ ├───📂 loras/\n│ │ ├─── krea2_turbo_lora_rank_64_bf16.safetensors\n│ │ ├─── Any loras if you want\n│ ├───📂 text_encoders/\n│ │ └─── qwen3vl_4b_bf16.safetensors\n│ └───📂 vae/\n│ └── qwen_image_vae.safetensors\n\n\n\n\n\n\n\n"
309
+ ],
310
+ "color": "#432",
311
+ "bgcolor": "#653"
312
+ },
313
+ {
314
+ "id": 7,
315
+ "type": "VAELoader",
316
+ "pos": [
317
+ -101.17303068778611,
318
+ -161.27559798417792
319
+ ],
320
+ "size": [
321
+ 270,
322
+ 58
323
+ ],
324
+ "flags": {},
325
+ "order": 6,
326
+ "mode": 0,
327
+ "inputs": [],
328
+ "outputs": [
329
+ {
330
+ "name": "VAE",
331
+ "type": "VAE",
332
+ "links": [
333
+ 17,
334
+ 53
335
+ ]
336
+ }
337
+ ],
338
+ "properties": {
339
+ "cnr_id": "comfy-core",
340
+ "ver": "0.16.4",
341
+ "Node name for S&R": "VAELoader",
342
+ "ue_properties": {
343
+ "widget_ue_connectable": {},
344
+ "input_ue_unconnectable": {},
345
+ "version": "7.7"
346
+ }
347
+ },
348
+ "widgets_values": [
349
+ "krea2RealVae_v10.safetensors"
350
+ ]
351
+ },
352
+ {
353
+ "id": 31,
354
+ "type": "CLIPLoader",
355
+ "pos": [
356
+ -114.65834305333588,
357
+ -343.0687626475736
358
+ ],
359
+ "size": [
360
+ 270,
361
+ 106
362
+ ],
363
+ "flags": {},
364
+ "order": 7,
365
+ "mode": 0,
366
+ "inputs": [],
367
+ "outputs": [
368
+ {
369
+ "name": "CLIP",
370
+ "type": "CLIP",
371
+ "links": [
372
+ 39
373
+ ]
374
+ }
375
+ ],
376
+ "properties": {
377
+ "cnr_id": "comfy-core",
378
+ "ver": "0.18.1",
379
+ "Node name for S&R": "CLIPLoader",
380
+ "ue_properties": {
381
+ "widget_ue_connectable": {},
382
+ "input_ue_unconnectable": {},
383
+ "version": "7.8"
384
+ }
385
+ },
386
+ "widgets_values": [
387
+ "qwen3vl_4b_bf16.safetensors",
388
+ "krea2",
389
+ "default"
390
+ ]
391
+ },
392
+ {
393
+ "id": 28,
394
+ "type": "Lora Loader (LoraManager)",
395
+ "pos": [
396
+ 670.7284687869626,
397
+ -573.0369690519098
398
+ ],
399
+ "size": [
400
+ 568.1487855001747,
401
+ 366.0932833710507
402
+ ],
403
+ "flags": {},
404
+ "order": 13,
405
+ "mode": 0,
406
+ "inputs": [
407
+ {
408
+ "name": "model",
409
+ "type": "MODEL",
410
+ "link": 41
411
+ },
412
+ {
413
+ "name": "clip",
414
+ "shape": 7,
415
+ "type": "CLIP",
416
+ "link": 39
417
+ },
418
+ {
419
+ "name": "lora_stack",
420
+ "shape": 7,
421
+ "type": "LORA_STACK",
422
+ "link": null
423
+ }
424
+ ],
425
+ "outputs": [
426
+ {
427
+ "name": "MODEL",
428
+ "type": "MODEL",
429
+ "links": [
430
+ 42
431
+ ]
432
+ },
433
+ {
434
+ "name": "CLIP",
435
+ "type": "CLIP",
436
+ "links": [
437
+ 31,
438
+ 37
439
+ ]
440
+ },
441
+ {
442
+ "name": "trigger_words",
443
+ "type": "STRING",
444
+ "links": [
445
+ 30
446
+ ]
447
+ },
448
+ {
449
+ "name": "loaded_loras",
450
+ "type": "STRING",
451
+ "links": null
452
+ }
453
+ ],
454
+ "properties": {
455
+ "cnr_id": "comfyui-lora-manager",
456
+ "ver": "0.9.11",
457
+ "Node name for S&R": "Lora Loader (LoraManager)",
458
+ "ue_properties": {
459
+ "widget_ue_connectable": {},
460
+ "input_ue_unconnectable": {},
461
+ "version": "7.5.2"
462
+ },
463
+ "__lm_widget_ids": [
464
+ "__lm_autocomplete_meta_text",
465
+ "text",
466
+ "loras"
467
+ ]
468
+ },
469
+ "widgets_values": [
470
+ {
471
+ "version": 1,
472
+ "textWidgetName": "text"
473
+ },
474
+ "",
475
+ []
476
+ ],
477
+ "color": "#223",
478
+ "bgcolor": "#335"
479
+ },
480
+ {
481
+ "id": 27,
482
+ "type": "TriggerWord Toggle (LoraManager)",
483
+ "pos": [
484
+ 1369.622929842321,
485
+ -802.2472095519896
486
+ ],
487
+ "size": [
488
+ 333.8873046875,
489
+ 284
490
+ ],
491
+ "flags": {},
492
+ "order": 17,
493
+ "mode": 0,
494
+ "inputs": [
495
+ {
496
+ "name": "trigger_words",
497
+ "shape": 7,
498
+ "type": "string",
499
+ "link": 30
500
+ }
501
+ ],
502
+ "outputs": [
503
+ {
504
+ "name": "filtered_trigger_words",
505
+ "type": "STRING",
506
+ "links": [
507
+ 32
508
+ ]
509
+ }
510
+ ],
511
+ "properties": {
512
+ "cnr_id": "comfyui-lora-manager",
513
+ "ver": "0.9.11",
514
+ "Node name for S&R": "TriggerWord Toggle (LoraManager)",
515
+ "ue_properties": {
516
+ "widget_ue_connectable": {},
517
+ "input_ue_unconnectable": {},
518
+ "version": "7.5.2"
519
+ }
520
+ },
521
+ "widgets_values": [
522
+ true,
523
+ true,
524
+ false,
525
+ [],
526
+ ""
527
+ ],
528
+ "color": "#223",
529
+ "bgcolor": "#335"
530
+ },
531
+ {
532
+ "id": 9,
533
+ "type": "CLIPTextEncode",
534
+ "pos": [
535
+ 1372.03106655687,
536
+ -102.39676636158624
537
+ ],
538
+ "size": [
539
+ 400,
540
+ 200
541
+ ],
542
+ "flags": {},
543
+ "order": 16,
544
+ "mode": 0,
545
+ "inputs": [
546
+ {
547
+ "name": "clip",
548
+ "type": "CLIP",
549
+ "link": 37
550
+ }
551
+ ],
552
+ "outputs": [
553
+ {
554
+ "name": "CONDITIONING",
555
+ "type": "CONDITIONING",
556
+ "links": [
557
+ 46
558
+ ]
559
+ }
560
+ ],
561
+ "title": "Negative Prompt",
562
+ "properties": {
563
+ "cnr_id": "comfy-core",
564
+ "ver": "0.3.44",
565
+ "Node name for S&R": "CLIPTextEncode",
566
+ "ue_properties": {
567
+ "widget_ue_connectable": {},
568
+ "version": "7.5.2",
569
+ "input_ue_unconnectable": {}
570
+ }
571
+ },
572
+ "widgets_values": [
573
+ "pale tones, blurry, flat colors, poor anatomy, dull lighting, text, watermark, messy composition, low contrast, white background, black background, skirt, pose, wrong proportion, black hand, suite, bag, breast"
574
+ ],
575
+ "color": "#322",
576
+ "bgcolor": "#533"
577
+ },
578
+ {
579
+ "id": 39,
580
+ "type": "JWImageResizeByLongerSide",
581
+ "pos": [
582
+ 817.3239958429691,
583
+ -110.63714541080098
584
+ ],
585
+ "size": [
586
+ 291.593359375,
587
+ 82
588
+ ],
589
+ "flags": {},
590
+ "order": 10,
591
+ "mode": 0,
592
+ "inputs": [
593
+ {
594
+ "name": "image",
595
+ "type": "IMAGE",
596
+ "link": 50
597
+ }
598
+ ],
599
+ "outputs": [
600
+ {
601
+ "name": "IMAGE",
602
+ "type": "IMAGE",
603
+ "links": [
604
+ 52,
605
+ 78
606
+ ]
607
+ }
608
+ ],
609
+ "properties": {
610
+ "cnr_id": "comfyui-various",
611
+ "ver": "5bd85aaf7616878471469c4ec7e11bbd0cef3bf2",
612
+ "Node name for S&R": "JWImageResizeByLongerSide",
613
+ "ue_properties": {
614
+ "widget_ue_connectable": {},
615
+ "input_ue_unconnectable": {},
616
+ "version": "7.8"
617
+ }
618
+ },
619
+ "widgets_values": [
620
+ 1472,
621
+ "bicubic"
622
+ ]
623
+ },
624
+ {
625
+ "id": 1,
626
+ "type": "LoadImage",
627
+ "pos": [
628
+ -90.56216073008177,
629
+ 46.14239606345017
630
+ ],
631
+ "size": [
632
+ 282.798828125,
633
+ 314
634
+ ],
635
+ "flags": {},
636
+ "order": 8,
637
+ "mode": 0,
638
+ "inputs": [],
639
+ "outputs": [
640
+ {
641
+ "name": "IMAGE",
642
+ "type": "IMAGE",
643
+ "links": [
644
+ 50
645
+ ]
646
+ },
647
+ {
648
+ "name": "MASK",
649
+ "type": "MASK",
650
+ "links": [
651
+ 55,
652
+ 79
653
+ ]
654
+ }
655
+ ],
656
+ "properties": {
657
+ "cnr_id": "comfy-core",
658
+ "ver": "0.16.4",
659
+ "Node name for S&R": "LoadImage",
660
+ "ue_properties": {
661
+ "widget_ue_connectable": {},
662
+ "input_ue_unconnectable": {},
663
+ "version": "7.7"
664
+ },
665
+ "image": "clipspace/clipspace-painted-masked-1784191942701.png [input]"
666
+ },
667
+ "widgets_values": [
668
+ "clipspace/clipspace-painted-masked-1784191942701.png [input]",
669
+ "image"
670
+ ]
671
+ },
672
+ {
673
+ "id": 30,
674
+ "type": "UNETLoader",
675
+ "pos": [
676
+ -121.66298929390322,
677
+ -630.6289599251239
678
+ ],
679
+ "size": [
680
+ 270,
681
+ 82
682
+ ],
683
+ "flags": {},
684
+ "order": 9,
685
+ "mode": 0,
686
+ "inputs": [],
687
+ "outputs": [
688
+ {
689
+ "name": "MODEL",
690
+ "type": "MODEL",
691
+ "links": [
692
+ 40
693
+ ]
694
+ }
695
+ ],
696
+ "properties": {
697
+ "cnr_id": "comfy-core",
698
+ "ver": "0.18.1",
699
+ "Node name for S&R": "UNETLoader",
700
+ "ue_properties": {
701
+ "widget_ue_connectable": {},
702
+ "input_ue_unconnectable": {},
703
+ "version": "7.8"
704
+ }
705
+ },
706
+ "widgets_values": [
707
+ "krea2TurboOfficialComfy_krea2TurboNvfp4.safetensors",
708
+ "default"
709
+ ]
710
+ },
711
+ {
712
+ "id": 29,
713
+ "type": "Prompt (LoraManager)",
714
+ "pos": [
715
+ 1383.7331523126124,
716
+ -405.5066756515228
717
+ ],
718
+ "size": [
719
+ 407.86960005322544,
720
+ 233.17855783027926
721
+ ],
722
+ "flags": {},
723
+ "order": 18,
724
+ "mode": 0,
725
+ "inputs": [
726
+ {
727
+ "name": "clip",
728
+ "type": "CLIP",
729
+ "link": 31
730
+ },
731
+ {
732
+ "name": "seed",
733
+ "shape": 7,
734
+ "type": "INT",
735
+ "link": null
736
+ },
737
+ {
738
+ "name": "trigger_words1",
739
+ "shape": 7,
740
+ "type": "STRING",
741
+ "link": 32
742
+ },
743
+ {
744
+ "name": "trigger_words2",
745
+ "type": "STRING",
746
+ "link": null
747
+ }
748
+ ],
749
+ "outputs": [
750
+ {
751
+ "name": "CONDITIONING",
752
+ "type": "CONDITIONING",
753
+ "links": [
754
+ 45
755
+ ]
756
+ },
757
+ {
758
+ "name": "PROMPT",
759
+ "type": "STRING",
760
+ "links": null
761
+ }
762
+ ],
763
+ "properties": {
764
+ "cnr_id": "comfyui-lora-manager",
765
+ "ver": "4fcf641d5780e5cf140a54f1c23fd31117ddb7e2",
766
+ "Node name for S&R": "Prompt (LoraManager)",
767
+ "ue_properties": {
768
+ "widget_ue_connectable": {},
769
+ "input_ue_unconnectable": {},
770
+ "version": "7.7"
771
+ },
772
+ "__lm_widget_ids": [
773
+ "__lm_autocomplete_meta_text",
774
+ "text"
775
+ ]
776
+ },
777
+ "widgets_values": [
778
+ {
779
+ "version": 1,
780
+ "textWidgetName": "text",
781
+ "lastAccepted": {
782
+ "start": 0,
783
+ "end": 9,
784
+ "insertedText": "12beat13,",
785
+ "textSnapshot": "12beat13,"
786
+ }
787
+ },
788
+ "korean girl"
789
+ ],
790
+ "color": "#232",
791
+ "bgcolor": "#353"
792
+ },
793
+ {
794
+ "id": 14,
795
+ "type": "SaveImage",
796
+ "pos": [
797
+ 321.27544781116023,
798
+ 211.9154535777168
799
+ ],
800
+ "size": [
801
+ 805.0496647217428,
802
+ 1203.507734814826
803
+ ],
804
+ "flags": {},
805
+ "order": 24,
806
+ "mode": 0,
807
+ "inputs": [
808
+ {
809
+ "name": "images",
810
+ "type": "IMAGE",
811
+ "link": 62
812
+ }
813
+ ],
814
+ "outputs": [
815
+ {
816
+ "name": "images",
817
+ "type": "IMAGE",
818
+ "links": null
819
+ }
820
+ ],
821
+ "properties": {
822
+ "cnr_id": "comfy-core",
823
+ "ver": "0.16.4",
824
+ "Node name for S&R": "SaveImage",
825
+ "ue_properties": {
826
+ "widget_ue_connectable": {},
827
+ "input_ue_unconnectable": {},
828
+ "version": "7.7"
829
+ }
830
+ },
831
+ "widgets_values": [
832
+ "ComfyUI"
833
+ ]
834
+ },
835
+ {
836
+ "id": 12,
837
+ "type": "VAEDecode",
838
+ "pos": [
839
+ 1854.727483188264,
840
+ -298.49849528544695
841
+ ],
842
+ "size": [
843
+ 140,
844
+ 46
845
+ ],
846
+ "flags": {},
847
+ "order": 20,
848
+ "mode": 0,
849
+ "inputs": [
850
+ {
851
+ "name": "samples",
852
+ "type": "LATENT",
853
+ "link": 48
854
+ },
855
+ {
856
+ "name": "vae",
857
+ "type": "VAE",
858
+ "link": 17
859
+ }
860
+ ],
861
+ "outputs": [
862
+ {
863
+ "name": "IMAGE",
864
+ "type": "IMAGE",
865
+ "links": [
866
+ 80,
867
+ 85
868
+ ]
869
+ }
870
+ ],
871
+ "properties": {
872
+ "cnr_id": "comfy-core",
873
+ "ver": "0.16.4",
874
+ "Node name for S&R": "VAEDecode",
875
+ "ue_properties": {
876
+ "widget_ue_connectable": {},
877
+ "input_ue_unconnectable": {},
878
+ "version": "7.7"
879
+ }
880
+ },
881
+ "widgets_values": []
882
+ },
883
+ {
884
+ "id": 40,
885
+ "type": "VAEEncode",
886
+ "pos": [
887
+ 1286.2014157680758,
888
+ 31.86479772775502
889
+ ],
890
+ "size": [
891
+ 210,
892
+ 46
893
+ ],
894
+ "flags": {},
895
+ "order": 12,
896
+ "mode": 0,
897
+ "inputs": [
898
+ {
899
+ "name": "pixels",
900
+ "type": "IMAGE",
901
+ "link": 52
902
+ },
903
+ {
904
+ "name": "vae",
905
+ "type": "VAE",
906
+ "link": 53
907
+ }
908
+ ],
909
+ "outputs": [
910
+ {
911
+ "name": "LATENT",
912
+ "type": "LATENT",
913
+ "links": [
914
+ 54
915
+ ]
916
+ }
917
+ ],
918
+ "properties": {
919
+ "cnr_id": "comfy-core",
920
+ "ver": "0.16.4",
921
+ "Node name for S&R": "VAEEncode",
922
+ "ue_properties": {
923
+ "widget_ue_connectable": {},
924
+ "version": "7.8",
925
+ "input_ue_unconnectable": {}
926
+ }
927
+ },
928
+ "widgets_values": []
929
+ },
930
+ {
931
+ "id": 41,
932
+ "type": "SetLatentNoiseMask",
933
+ "pos": [
934
+ 1546.2014157680758,
935
+ 31.86479772775502
936
+ ],
937
+ "size": [
938
+ 210,
939
+ 46
940
+ ],
941
+ "flags": {},
942
+ "order": 14,
943
+ "mode": 0,
944
+ "inputs": [
945
+ {
946
+ "name": "samples",
947
+ "type": "LATENT",
948
+ "link": 54
949
+ },
950
+ {
951
+ "name": "mask",
952
+ "type": "MASK",
953
+ "link": 55
954
+ }
955
+ ],
956
+ "outputs": [
957
+ {
958
+ "name": "LATENT",
959
+ "type": "LATENT",
960
+ "links": [
961
+ 56
962
+ ]
963
+ }
964
+ ],
965
+ "properties": {
966
+ "cnr_id": "comfy-core",
967
+ "ver": "0.16.4",
968
+ "Node name for S&R": "SetLatentNoiseMask",
969
+ "ue_properties": {
970
+ "widget_ue_connectable": {},
971
+ "version": "7.8",
972
+ "input_ue_unconnectable": {}
973
+ }
974
+ },
975
+ "widgets_values": []
976
+ },
977
+ {
978
+ "id": 43,
979
+ "type": "LanPaint_MaskBlend",
980
+ "pos": [
981
+ 2388.816702727679,
982
+ -146.92603208283066
983
+ ],
984
+ "size": [
985
+ 270,
986
+ 110
987
+ ],
988
+ "flags": {},
989
+ "order": 23,
990
+ "mode": 0,
991
+ "inputs": [
992
+ {
993
+ "name": "image1",
994
+ "type": "IMAGE",
995
+ "link": 82
996
+ },
997
+ {
998
+ "name": "image2",
999
+ "type": "IMAGE",
1000
+ "link": 87
1001
+ },
1002
+ {
1003
+ "name": "mask",
1004
+ "type": "MASK",
1005
+ "link": 83
1006
+ }
1007
+ ],
1008
+ "outputs": [
1009
+ {
1010
+ "name": "IMAGE",
1011
+ "type": "IMAGE",
1012
+ "links": [
1013
+ 62
1014
+ ]
1015
+ }
1016
+ ],
1017
+ "title": "Blend Seam with Original",
1018
+ "properties": {
1019
+ "cnr_id": "lanpaint",
1020
+ "ver": "1.5.5",
1021
+ "Node name for S&R": "LanPaint_MaskBlend",
1022
+ "ue_properties": {
1023
+ "widget_ue_connectable": {},
1024
+ "version": "7.8",
1025
+ "input_ue_unconnectable": {}
1026
+ }
1027
+ },
1028
+ "widgets_values": [
1029
+ 51
1030
+ ]
1031
+ },
1032
+ {
1033
+ "id": 48,
1034
+ "type": "MatchImageAndMaskSize_Dolphin",
1035
+ "pos": [
1036
+ 1286.2014157680758,
1037
+ 151.86479772775502
1038
+ ],
1039
+ "size": [
1040
+ 270,
1041
+ 130
1042
+ ],
1043
+ "flags": {},
1044
+ "order": 21,
1045
+ "mode": 0,
1046
+ "inputs": [
1047
+ {
1048
+ "name": "image_to_resize",
1049
+ "type": "IMAGE",
1050
+ "link": 78
1051
+ },
1052
+ {
1053
+ "name": "mask_to_resize",
1054
+ "type": "MASK",
1055
+ "link": 79
1056
+ },
1057
+ {
1058
+ "name": "reference_image",
1059
+ "type": "IMAGE",
1060
+ "link": 80
1061
+ }
1062
+ ],
1063
+ "outputs": [
1064
+ {
1065
+ "name": "image",
1066
+ "type": "IMAGE",
1067
+ "links": [
1068
+ 82,
1069
+ 84
1070
+ ]
1071
+ },
1072
+ {
1073
+ "name": "mask",
1074
+ "type": "MASK",
1075
+ "links": [
1076
+ 83,
1077
+ 86
1078
+ ]
1079
+ }
1080
+ ],
1081
+ "title": "Match Original to Output Size",
1082
+ "properties": {
1083
+ "Node name for S&R": "MatchImageAndMaskSize_Dolphin",
1084
+ "cnr_id": "dolphin_nodes",
1085
+ "ue_properties": {
1086
+ "widget_ue_connectable": {},
1087
+ "version": "7.8",
1088
+ "input_ue_unconnectable": {}
1089
+ }
1090
+ },
1091
+ "widgets_values": []
1092
+ },
1093
+ {
1094
+ "id": 49,
1095
+ "type": "MaskRegionColorMatch_Dolphin",
1096
+ "pos": [
1097
+ 2165.4644495383623,
1098
+ -332.50977690725466
1099
+ ],
1100
+ "size": [
1101
+ 270,
1102
+ 170
1103
+ ],
1104
+ "flags": {},
1105
+ "order": 22,
1106
+ "mode": 0,
1107
+ "inputs": [
1108
+ {
1109
+ "name": "original_image",
1110
+ "type": "IMAGE",
1111
+ "link": 84
1112
+ },
1113
+ {
1114
+ "name": "generated_image",
1115
+ "type": "IMAGE",
1116
+ "link": 85
1117
+ },
1118
+ {
1119
+ "name": "mask",
1120
+ "type": "MASK",
1121
+ "link": 86
1122
+ }
1123
+ ],
1124
+ "outputs": [
1125
+ {
1126
+ "name": "image",
1127
+ "type": "IMAGE",
1128
+ "links": [
1129
+ 87
1130
+ ]
1131
+ }
1132
+ ],
1133
+ "title": "Color Match (Mask Region Only)",
1134
+ "properties": {
1135
+ "Node name for S&R": "MaskRegionColorMatch_Dolphin",
1136
+ "cnr_id": "dolphin_nodes",
1137
+ "ue_properties": {
1138
+ "widget_ue_connectable": {},
1139
+ "version": "7.8",
1140
+ "input_ue_unconnectable": {}
1141
+ }
1142
+ },
1143
+ "widgets_values": [
1144
+ "reinhard",
1145
+ 0.5,
1146
+ 150,
1147
+ 40
1148
+ ]
1149
+ },
1150
+ {
1151
+ "id": 38,
1152
+ "type": "LanPaint_KSamplerAdvanced",
1153
+ "pos": [
1154
+ 1920.0939764613263,
1155
+ -141.16323281521963
1156
+ ],
1157
+ "size": [
1158
+ 383.1676476648677,
1159
+ 950
1160
+ ],
1161
+ "flags": {},
1162
+ "order": 19,
1163
+ "mode": 0,
1164
+ "inputs": [
1165
+ {
1166
+ "name": "model",
1167
+ "type": "MODEL",
1168
+ "link": 44
1169
+ },
1170
+ {
1171
+ "name": "positive",
1172
+ "type": "CONDITIONING",
1173
+ "link": 45
1174
+ },
1175
+ {
1176
+ "name": "negative",
1177
+ "type": "CONDITIONING",
1178
+ "link": 46
1179
+ },
1180
+ {
1181
+ "name": "latent_image",
1182
+ "type": "LATENT",
1183
+ "link": 56
1184
+ }
1185
+ ],
1186
+ "outputs": [
1187
+ {
1188
+ "name": "LATENT",
1189
+ "type": "LATENT",
1190
+ "links": [
1191
+ 48
1192
+ ]
1193
+ }
1194
+ ],
1195
+ "properties": {
1196
+ "cnr_id": "lanpaint",
1197
+ "ver": "1.5.5",
1198
+ "Node name for S&R": "LanPaint_KSamplerAdvanced",
1199
+ "ue_properties": {
1200
+ "widget_ue_connectable": {},
1201
+ "input_ue_unconnectable": {},
1202
+ "version": "7.8"
1203
+ }
1204
+ },
1205
+ "widgets_values": [
1206
+ "enable",
1207
+ 198527596812840,
1208
+ "randomize",
1209
+ 8,
1210
+ 1,
1211
+ "euler",
1212
+ "simple",
1213
+ 0,
1214
+ 10000,
1215
+ "disable",
1216
+ 3,
1217
+ 10.0,
1218
+ 0.1,
1219
+ 0.5,
1220
+ 10.0,
1221
+ "Image First",
1222
+ 2,
1223
+ "LanPaint KSampler Adv.",
1224
+ "🖼️ Image Inpainting",
1225
+ 0.0,
1226
+ 1,
1227
+ "lanpaint_star_button"
1228
+ ]
1229
+ }
1230
+ ],
1231
+ "links": [
1232
+ [
1233
+ 17,
1234
+ 7,
1235
+ 0,
1236
+ 12,
1237
+ 1,
1238
+ "VAE"
1239
+ ],
1240
+ [
1241
+ 30,
1242
+ 28,
1243
+ 2,
1244
+ 27,
1245
+ 0,
1246
+ "string"
1247
+ ],
1248
+ [
1249
+ 31,
1250
+ 28,
1251
+ 1,
1252
+ 29,
1253
+ 0,
1254
+ "CLIP"
1255
+ ],
1256
+ [
1257
+ 32,
1258
+ 27,
1259
+ 0,
1260
+ 29,
1261
+ 2,
1262
+ "STRING"
1263
+ ],
1264
+ [
1265
+ 37,
1266
+ 28,
1267
+ 1,
1268
+ 9,
1269
+ 0,
1270
+ "CLIP"
1271
+ ],
1272
+ [
1273
+ 39,
1274
+ 31,
1275
+ 0,
1276
+ 28,
1277
+ 1,
1278
+ "CLIP"
1279
+ ],
1280
+ [
1281
+ 40,
1282
+ 30,
1283
+ 0,
1284
+ 35,
1285
+ 0,
1286
+ "MODEL"
1287
+ ],
1288
+ [
1289
+ 41,
1290
+ 35,
1291
+ 0,
1292
+ 28,
1293
+ 0,
1294
+ "MODEL"
1295
+ ],
1296
+ [
1297
+ 42,
1298
+ 28,
1299
+ 0,
1300
+ 36,
1301
+ 0,
1302
+ "MODEL"
1303
+ ],
1304
+ [
1305
+ 44,
1306
+ 36,
1307
+ 0,
1308
+ 38,
1309
+ 0,
1310
+ "MODEL"
1311
+ ],
1312
+ [
1313
+ 45,
1314
+ 29,
1315
+ 0,
1316
+ 38,
1317
+ 1,
1318
+ "CONDITIONING"
1319
+ ],
1320
+ [
1321
+ 46,
1322
+ 9,
1323
+ 0,
1324
+ 38,
1325
+ 2,
1326
+ "CONDITIONING"
1327
+ ],
1328
+ [
1329
+ 48,
1330
+ 38,
1331
+ 0,
1332
+ 12,
1333
+ 0,
1334
+ "LATENT"
1335
+ ],
1336
+ [
1337
+ 50,
1338
+ 1,
1339
+ 0,
1340
+ 39,
1341
+ 0,
1342
+ "IMAGE"
1343
+ ],
1344
+ [
1345
+ 52,
1346
+ 39,
1347
+ 0,
1348
+ 40,
1349
+ 0,
1350
+ "IMAGE"
1351
+ ],
1352
+ [
1353
+ 53,
1354
+ 7,
1355
+ 0,
1356
+ 40,
1357
+ 1,
1358
+ "VAE"
1359
+ ],
1360
+ [
1361
+ 54,
1362
+ 40,
1363
+ 0,
1364
+ 41,
1365
+ 0,
1366
+ "LATENT"
1367
+ ],
1368
+ [
1369
+ 55,
1370
+ 1,
1371
+ 1,
1372
+ 41,
1373
+ 1,
1374
+ "MASK"
1375
+ ],
1376
+ [
1377
+ 56,
1378
+ 41,
1379
+ 0,
1380
+ 38,
1381
+ 3,
1382
+ "LATENT"
1383
+ ],
1384
+ [
1385
+ 62,
1386
+ 43,
1387
+ 0,
1388
+ 14,
1389
+ 0,
1390
+ "IMAGE"
1391
+ ],
1392
+ [
1393
+ 78,
1394
+ 39,
1395
+ 0,
1396
+ 48,
1397
+ 0,
1398
+ "IMAGE"
1399
+ ],
1400
+ [
1401
+ 79,
1402
+ 1,
1403
+ 1,
1404
+ 48,
1405
+ 1,
1406
+ "MASK"
1407
+ ],
1408
+ [
1409
+ 80,
1410
+ 12,
1411
+ 0,
1412
+ 48,
1413
+ 2,
1414
+ "IMAGE"
1415
+ ],
1416
+ [
1417
+ 82,
1418
+ 48,
1419
+ 0,
1420
+ 43,
1421
+ 0,
1422
+ "IMAGE"
1423
+ ],
1424
+ [
1425
+ 83,
1426
+ 48,
1427
+ 1,
1428
+ 43,
1429
+ 2,
1430
+ "MASK"
1431
+ ],
1432
+ [
1433
+ 84,
1434
+ 48,
1435
+ 0,
1436
+ 49,
1437
+ 0,
1438
+ "IMAGE"
1439
+ ],
1440
+ [
1441
+ 85,
1442
+ 12,
1443
+ 0,
1444
+ 49,
1445
+ 1,
1446
+ "IMAGE"
1447
+ ],
1448
+ [
1449
+ 86,
1450
+ 48,
1451
+ 1,
1452
+ 49,
1453
+ 2,
1454
+ "MASK"
1455
+ ],
1456
+ [
1457
+ 87,
1458
+ 49,
1459
+ 0,
1460
+ 43,
1461
+ 1,
1462
+ "IMAGE"
1463
+ ]
1464
+ ],
1465
+ "groups": [
1466
+ {
1467
+ "id": 1,
1468
+ "title": "Turbo Lora",
1469
+ "bounding": [
1470
+ 217.02048263467339,
1471
+ -648.7621951869007,
1472
+ 299.2239373235054,
1473
+ 191.911637393992
1474
+ ],
1475
+ "color": "#3f789e",
1476
+ "flags": {}
1477
+ },
1478
+ {
1479
+ "id": 2,
1480
+ "title": "Sageattention",
1481
+ "bounding": [
1482
+ 1316.448516061584,
1483
+ 300.1227252083272,
1484
+ 317.4210206114476,
1485
+ 152.78808325362013
1486
+ ],
1487
+ "color": "#3f789e",
1488
+ "flags": {}
1489
+ }
1490
+ ],
1491
+ "config": {},
1492
+ "extra": {
1493
+ "workflowRendererVersion": "LG",
1494
+ "ue_links": [],
1495
+ "ds": {
1496
+ "scale": 0.6934334949441378,
1497
+ "offset": [
1498
+ -328.05025247996747,
1499
+ 878.3799578784656
1500
+ ]
1501
+ },
1502
+ "links_added_by_ue": [],
1503
+ "frontendVersion": "1.45.20",
1504
+ "VHS_latentpreview": false,
1505
+ "VHS_latentpreviewrate": 0,
1506
+ "VHS_MetadataImage": false,
1507
+ "VHS_KeepIntermediate": true
1508
+ },
1509
+ "version": 0.4
1510
+ }
_archive/workflows/krea2 inpaint_2.json ADDED
@@ -0,0 +1,1500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "94d9ce78-c6d4-45b5-b65f-7f7cdda98616",
3
+ "revision": 0,
4
+ "last_node_id": 49,
5
+ "last_link_id": 87,
6
+ "nodes": [
7
+ {
8
+ "id": 25,
9
+ "type": "Label (rgthree)",
10
+ "pos": [
11
+ 502.89126314973754,
12
+ 568.6440437433788
13
+ ],
14
+ "size": [
15
+ 547.05810546875,
16
+ 25
17
+ ],
18
+ "flags": {
19
+ "allow_interaction": true
20
+ },
21
+ "order": 0,
22
+ "mode": 0,
23
+ "inputs": [],
24
+ "outputs": [],
25
+ "title": "And choose Open in Mask Editor | Image Canvas",
26
+ "properties": {
27
+ "fontSize": 25,
28
+ "fontFamily": "Arial",
29
+ "fontColor": "#ffffff",
30
+ "textAlign": "left",
31
+ "backgroundColor": "transparent",
32
+ "padding": 0,
33
+ "borderRadius": 0,
34
+ "angle": 0,
35
+ "ue_properties": {
36
+ "widget_ue_connectable": {},
37
+ "input_ue_unconnectable": {},
38
+ "version": "7.5.2"
39
+ }
40
+ },
41
+ "color": "#fff0",
42
+ "bgcolor": "#fff0"
43
+ },
44
+ {
45
+ "id": 26,
46
+ "type": "Label (rgthree)",
47
+ "pos": [
48
+ 557.4292270417669,
49
+ 521.8881876346229
50
+ ],
51
+ "size": [
52
+ 408.50830078125,
53
+ 25
54
+ ],
55
+ "flags": {
56
+ "allow_interaction": true
57
+ },
58
+ "order": 1,
59
+ "mode": 0,
60
+ "inputs": [],
61
+ "outputs": [],
62
+ "title": "To inpaint mask, right click on mouse",
63
+ "properties": {
64
+ "fontSize": 25,
65
+ "fontFamily": "Arial",
66
+ "fontColor": "#ffffff",
67
+ "textAlign": "left",
68
+ "backgroundColor": "transparent",
69
+ "padding": 0,
70
+ "borderRadius": 0,
71
+ "angle": 0,
72
+ "ue_properties": {
73
+ "widget_ue_connectable": {},
74
+ "input_ue_unconnectable": {},
75
+ "version": "7.5.2"
76
+ }
77
+ },
78
+ "color": "#fff0",
79
+ "bgcolor": "#fff0"
80
+ },
81
+ {
82
+ "id": 32,
83
+ "type": "Label (rgthree)",
84
+ "pos": [
85
+ -1027.309220513278,
86
+ -587.2079374535729
87
+ ],
88
+ "size": [
89
+ 730.810546875,
90
+ 50
91
+ ],
92
+ "flags": {
93
+ "allow_interaction": true
94
+ },
95
+ "order": 2,
96
+ "mode": 0,
97
+ "inputs": [],
98
+ "outputs": [],
99
+ "title": "Notes (Recomend it for a review)",
100
+ "properties": {
101
+ "fontSize": 50,
102
+ "fontFamily": "Arial",
103
+ "fontColor": "#ffffff",
104
+ "textAlign": "left",
105
+ "backgroundColor": "transparent",
106
+ "padding": 0,
107
+ "borderRadius": 0,
108
+ "angle": 0,
109
+ "ue_properties": {
110
+ "widget_ue_connectable": {},
111
+ "version": "7.1",
112
+ "input_ue_unconnectable": {}
113
+ }
114
+ },
115
+ "color": "#fff0",
116
+ "bgcolor": "#fff0"
117
+ },
118
+ {
119
+ "id": 35,
120
+ "type": "LoraLoaderModelOnly",
121
+ "pos": [
122
+ 228.5066115955086,
123
+ -565.064232779069
124
+ ],
125
+ "size": [
126
+ 270,
127
+ 82
128
+ ],
129
+ "flags": {},
130
+ "order": 11,
131
+ "mode": 4,
132
+ "inputs": [
133
+ {
134
+ "name": "model",
135
+ "type": "MODEL",
136
+ "link": 40
137
+ }
138
+ ],
139
+ "outputs": [
140
+ {
141
+ "name": "MODEL",
142
+ "type": "MODEL",
143
+ "links": [
144
+ 41
145
+ ]
146
+ }
147
+ ],
148
+ "title": "Turbo Lora",
149
+ "properties": {
150
+ "cnr_id": "comfy-core",
151
+ "ver": "0.26.0",
152
+ "Node name for S&R": "LoraLoaderModelOnly",
153
+ "ue_properties": {
154
+ "widget_ue_connectable": {},
155
+ "input_ue_unconnectable": {},
156
+ "version": "7.8"
157
+ }
158
+ },
159
+ "widgets_values": [
160
+ "krea2\\krea2_turbo_lora_rank_64_bf16.safetensors",
161
+ 1
162
+ ]
163
+ },
164
+ {
165
+ "id": 36,
166
+ "type": "PathchSageAttentionKJ",
167
+ "pos": [
168
+ 1340.6725366844717,
169
+ 356.52514598362905
170
+ ],
171
+ "size": [
172
+ 270,
173
+ 82
174
+ ],
175
+ "flags": {},
176
+ "order": 15,
177
+ "mode": 0,
178
+ "inputs": [
179
+ {
180
+ "name": "model",
181
+ "type": "MODEL",
182
+ "link": 42
183
+ }
184
+ ],
185
+ "outputs": [
186
+ {
187
+ "name": "MODEL",
188
+ "type": "MODEL",
189
+ "links": [
190
+ 44
191
+ ]
192
+ }
193
+ ],
194
+ "properties": {
195
+ "cnr_id": "comfyui-kjnodes",
196
+ "ver": "1.4.7",
197
+ "Node name for S&R": "PathchSageAttentionKJ",
198
+ "ue_properties": {
199
+ "widget_ue_connectable": {},
200
+ "input_ue_unconnectable": {},
201
+ "version": "7.8"
202
+ }
203
+ },
204
+ "widgets_values": [
205
+ "auto",
206
+ false
207
+ ]
208
+ },
209
+ {
210
+ "id": 37,
211
+ "type": "Fast Groups Bypasser (rgthree)",
212
+ "pos": [
213
+ 223.1305491471883,
214
+ -314.79010528541266
215
+ ],
216
+ "size": [
217
+ 397.4609463381595,
218
+ 100.1651829564488
219
+ ],
220
+ "flags": {},
221
+ "order": 3,
222
+ "mode": 0,
223
+ "inputs": [],
224
+ "outputs": [
225
+ {
226
+ "name": "OPT_CONNECTION",
227
+ "type": "*",
228
+ "links": null
229
+ }
230
+ ],
231
+ "properties": {
232
+ "matchColors": "",
233
+ "matchTitle": "",
234
+ "showNav": true,
235
+ "showAllGraphs": true,
236
+ "sort": "position",
237
+ "customSortAlphabet": "",
238
+ "toggleRestriction": "default",
239
+ "ue_properties": {
240
+ "widget_ue_connectable": {},
241
+ "input_ue_unconnectable": {},
242
+ "version": "7.8"
243
+ }
244
+ }
245
+ },
246
+ {
247
+ "id": 24,
248
+ "type": "Label (rgthree)",
249
+ "pos": [
250
+ 1013.1815196156713,
251
+ -901.8562984252738
252
+ ],
253
+ "size": [
254
+ 867.2607421875,
255
+ 150
256
+ ],
257
+ "flags": {
258
+ "allow_interaction": true
259
+ },
260
+ "order": 4,
261
+ "mode": 0,
262
+ "inputs": [],
263
+ "outputs": [],
264
+ "title": "krea2 Inpaint",
265
+ "properties": {
266
+ "fontSize": 150,
267
+ "fontFamily": "Arial",
268
+ "fontColor": "#ffd000",
269
+ "textAlign": "left",
270
+ "backgroundColor": "transparent",
271
+ "padding": 0,
272
+ "borderRadius": 0,
273
+ "angle": 0,
274
+ "ue_properties": {
275
+ "widget_ue_connectable": {},
276
+ "version": "7.1",
277
+ "input_ue_unconnectable": {}
278
+ }
279
+ },
280
+ "color": "#fff0",
281
+ "bgcolor": "#fff0"
282
+ },
283
+ {
284
+ "id": 33,
285
+ "type": "Note",
286
+ "pos": [
287
+ -1146.177246566986,
288
+ -451.25718147677554
289
+ ],
290
+ "size": [
291
+ 885.1822916666667,
292
+ 509.9479166666667
293
+ ],
294
+ "flags": {},
295
+ "order": 5,
296
+ "mode": 0,
297
+ "inputs": [],
298
+ "outputs": [],
299
+ "title": "Models and .whl files",
300
+ "properties": {
301
+ "ue_properties": {
302
+ "widget_ue_connectable": {},
303
+ "version": "7.1",
304
+ "input_ue_unconnectable": {}
305
+ }
306
+ },
307
+ "widgets_values": [
308
+ "Required models for Krea2 Workflow:\n\nKrea2 (Raw or Turbo): https://huggingface.co/Comfy-Org/Krea-2/tree/main/diffusion_models\nText Encoder: https://huggingface.co/Comfy-Org/Krea-2/tree/main/text_encoders\nVAE: https://huggingface.co/Comfy-Org/Krea-2/tree/main/vae\n\nAlso I recommend download uncendored encoder for nsfw and danbooru prompting https://huggingface.co/ahmed22xa/Huihui-Qwen3-VL-4B-Instruct-abliterated-comfy/resolve/main/Huihui-Qwen3-VL-4B-Instruct-abliterated.safetensors\n\nAlso recomend using VAE Wan 2.1 for better result https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors\n\nTurbo Lora (if you use Raw model): https://huggingface.co/Comfy-Org/Krea-2/blob/main/loras/krea2_turbo_lora_rank_64_bf16.safetensors\n\n\nFile save location\n\nComfyUI/\n├───📂 models/\n│ ├───📂 diffusion_models/\n│ │ ├─── krea2_turbo_bf16.safetensors\n│ ├───📂 loras/\n│ │ ├─── krea2_turbo_lora_rank_64_bf16.safetensors\n│ │ ├─── Any loras if you want\n│ ├───📂 text_encoders/\n│ │ └─── qwen3vl_4b_bf16.safetensors\n│ └───📂 vae/\n│ └── qwen_image_vae.safetensors\n\n\n\n\n\n\n\n"
309
+ ],
310
+ "color": "#432",
311
+ "bgcolor": "#653"
312
+ },
313
+ {
314
+ "id": 7,
315
+ "type": "VAELoader",
316
+ "pos": [
317
+ -101.17303068778611,
318
+ -161.27559798417792
319
+ ],
320
+ "size": [
321
+ 270,
322
+ 58
323
+ ],
324
+ "flags": {},
325
+ "order": 6,
326
+ "mode": 0,
327
+ "inputs": [],
328
+ "outputs": [
329
+ {
330
+ "name": "VAE",
331
+ "type": "VAE",
332
+ "links": [
333
+ 17,
334
+ 53
335
+ ]
336
+ }
337
+ ],
338
+ "properties": {
339
+ "cnr_id": "comfy-core",
340
+ "ver": "0.16.4",
341
+ "Node name for S&R": "VAELoader",
342
+ "ue_properties": {
343
+ "widget_ue_connectable": {},
344
+ "input_ue_unconnectable": {},
345
+ "version": "7.7"
346
+ }
347
+ },
348
+ "widgets_values": [
349
+ "krea2RealVae_v10.safetensors"
350
+ ]
351
+ },
352
+ {
353
+ "id": 31,
354
+ "type": "CLIPLoader",
355
+ "pos": [
356
+ -114.65834305333588,
357
+ -343.0687626475736
358
+ ],
359
+ "size": [
360
+ 270,
361
+ 106
362
+ ],
363
+ "flags": {},
364
+ "order": 7,
365
+ "mode": 0,
366
+ "inputs": [],
367
+ "outputs": [
368
+ {
369
+ "name": "CLIP",
370
+ "type": "CLIP",
371
+ "links": [
372
+ 39
373
+ ]
374
+ }
375
+ ],
376
+ "properties": {
377
+ "cnr_id": "comfy-core",
378
+ "ver": "0.18.1",
379
+ "Node name for S&R": "CLIPLoader",
380
+ "ue_properties": {
381
+ "widget_ue_connectable": {},
382
+ "input_ue_unconnectable": {},
383
+ "version": "7.8"
384
+ }
385
+ },
386
+ "widgets_values": [
387
+ "qwen3vl_4b_bf16.safetensors",
388
+ "krea2",
389
+ "default"
390
+ ]
391
+ },
392
+ {
393
+ "id": 28,
394
+ "type": "Lora Loader (LoraManager)",
395
+ "pos": [
396
+ 670.7284687869626,
397
+ -573.0369690519098
398
+ ],
399
+ "size": [
400
+ 568.1487855001747,
401
+ 366.0932833710507
402
+ ],
403
+ "flags": {},
404
+ "order": 13,
405
+ "mode": 0,
406
+ "inputs": [
407
+ {
408
+ "name": "model",
409
+ "type": "MODEL",
410
+ "link": 41
411
+ },
412
+ {
413
+ "name": "clip",
414
+ "shape": 7,
415
+ "type": "CLIP",
416
+ "link": 39
417
+ },
418
+ {
419
+ "name": "lora_stack",
420
+ "shape": 7,
421
+ "type": "LORA_STACK",
422
+ "link": null
423
+ }
424
+ ],
425
+ "outputs": [
426
+ {
427
+ "name": "MODEL",
428
+ "type": "MODEL",
429
+ "links": [
430
+ 42
431
+ ]
432
+ },
433
+ {
434
+ "name": "CLIP",
435
+ "type": "CLIP",
436
+ "links": [
437
+ 31,
438
+ 37
439
+ ]
440
+ },
441
+ {
442
+ "name": "trigger_words",
443
+ "type": "STRING",
444
+ "links": [
445
+ 30
446
+ ]
447
+ },
448
+ {
449
+ "name": "loaded_loras",
450
+ "type": "STRING",
451
+ "links": null
452
+ }
453
+ ],
454
+ "properties": {
455
+ "cnr_id": "comfyui-lora-manager",
456
+ "ver": "0.9.11",
457
+ "Node name for S&R": "Lora Loader (LoraManager)",
458
+ "ue_properties": {
459
+ "widget_ue_connectable": {},
460
+ "input_ue_unconnectable": {},
461
+ "version": "7.5.2"
462
+ },
463
+ "__lm_widget_ids": [
464
+ "__lm_autocomplete_meta_text",
465
+ "text",
466
+ "loras"
467
+ ]
468
+ },
469
+ "widgets_values": [
470
+ {
471
+ "version": 1,
472
+ "textWidgetName": "text"
473
+ },
474
+ "",
475
+ []
476
+ ],
477
+ "color": "#223",
478
+ "bgcolor": "#335"
479
+ },
480
+ {
481
+ "id": 27,
482
+ "type": "TriggerWord Toggle (LoraManager)",
483
+ "pos": [
484
+ 1369.622929842321,
485
+ -802.2472095519896
486
+ ],
487
+ "size": [
488
+ 333.8873046875,
489
+ 284
490
+ ],
491
+ "flags": {},
492
+ "order": 17,
493
+ "mode": 0,
494
+ "inputs": [
495
+ {
496
+ "name": "trigger_words",
497
+ "shape": 7,
498
+ "type": "string",
499
+ "link": 30
500
+ }
501
+ ],
502
+ "outputs": [
503
+ {
504
+ "name": "filtered_trigger_words",
505
+ "type": "STRING",
506
+ "links": [
507
+ 32
508
+ ]
509
+ }
510
+ ],
511
+ "properties": {
512
+ "cnr_id": "comfyui-lora-manager",
513
+ "ver": "0.9.11",
514
+ "Node name for S&R": "TriggerWord Toggle (LoraManager)",
515
+ "ue_properties": {
516
+ "widget_ue_connectable": {},
517
+ "input_ue_unconnectable": {},
518
+ "version": "7.5.2"
519
+ }
520
+ },
521
+ "widgets_values": [
522
+ true,
523
+ true,
524
+ false,
525
+ [],
526
+ ""
527
+ ],
528
+ "color": "#223",
529
+ "bgcolor": "#335"
530
+ },
531
+ {
532
+ "id": 9,
533
+ "type": "CLIPTextEncode",
534
+ "pos": [
535
+ 1372.03106655687,
536
+ -102.39676636158624
537
+ ],
538
+ "size": [
539
+ 400,
540
+ 200
541
+ ],
542
+ "flags": {},
543
+ "order": 16,
544
+ "mode": 0,
545
+ "inputs": [
546
+ {
547
+ "name": "clip",
548
+ "type": "CLIP",
549
+ "link": 37
550
+ }
551
+ ],
552
+ "outputs": [
553
+ {
554
+ "name": "CONDITIONING",
555
+ "type": "CONDITIONING",
556
+ "links": [
557
+ 46
558
+ ]
559
+ }
560
+ ],
561
+ "title": "Negative Prompt",
562
+ "properties": {
563
+ "cnr_id": "comfy-core",
564
+ "ver": "0.3.44",
565
+ "Node name for S&R": "CLIPTextEncode",
566
+ "ue_properties": {
567
+ "widget_ue_connectable": {},
568
+ "version": "7.5.2",
569
+ "input_ue_unconnectable": {}
570
+ }
571
+ },
572
+ "widgets_values": [
573
+ "pale tones, blurry, flat colors, poor anatomy, dull lighting, text, watermark, messy composition, low contrast, white background, black background, skirt, pose, wrong proportion, black hand, suite, bag, breast"
574
+ ],
575
+ "color": "#322",
576
+ "bgcolor": "#533"
577
+ },
578
+ {
579
+ "id": 39,
580
+ "type": "JWImageResizeByLongerSide",
581
+ "pos": [
582
+ 817.3239958429691,
583
+ -110.63714541080098
584
+ ],
585
+ "size": [
586
+ 291.593359375,
587
+ 82
588
+ ],
589
+ "flags": {},
590
+ "order": 10,
591
+ "mode": 0,
592
+ "inputs": [
593
+ {
594
+ "name": "image",
595
+ "type": "IMAGE",
596
+ "link": 50
597
+ }
598
+ ],
599
+ "outputs": [
600
+ {
601
+ "name": "IMAGE",
602
+ "type": "IMAGE",
603
+ "links": [
604
+ 52,
605
+ 78
606
+ ]
607
+ }
608
+ ],
609
+ "properties": {
610
+ "cnr_id": "comfyui-various",
611
+ "ver": "5bd85aaf7616878471469c4ec7e11bbd0cef3bf2",
612
+ "Node name for S&R": "JWImageResizeByLongerSide",
613
+ "ue_properties": {
614
+ "widget_ue_connectable": {},
615
+ "input_ue_unconnectable": {},
616
+ "version": "7.8"
617
+ }
618
+ },
619
+ "widgets_values": [
620
+ 1472,
621
+ "bicubic"
622
+ ]
623
+ },
624
+ {
625
+ "id": 1,
626
+ "type": "LoadImage",
627
+ "pos": [
628
+ -90.56216073008177,
629
+ 46.14239606345017
630
+ ],
631
+ "size": [
632
+ 282.798828125,
633
+ 314
634
+ ],
635
+ "flags": {},
636
+ "order": 8,
637
+ "mode": 0,
638
+ "inputs": [],
639
+ "outputs": [
640
+ {
641
+ "name": "IMAGE",
642
+ "type": "IMAGE",
643
+ "links": [
644
+ 50
645
+ ]
646
+ },
647
+ {
648
+ "name": "MASK",
649
+ "type": "MASK",
650
+ "links": [
651
+ 55,
652
+ 79
653
+ ]
654
+ }
655
+ ],
656
+ "properties": {
657
+ "cnr_id": "comfy-core",
658
+ "ver": "0.16.4",
659
+ "Node name for S&R": "LoadImage",
660
+ "ue_properties": {
661
+ "widget_ue_connectable": {},
662
+ "input_ue_unconnectable": {},
663
+ "version": "7.7"
664
+ },
665
+ "image": "clipspace/clipspace-painted-masked-1784191942701.png [input]"
666
+ },
667
+ "widgets_values": [
668
+ "clipspace/clipspace-painted-masked-1784191942701.png [input]",
669
+ "image"
670
+ ]
671
+ },
672
+ {
673
+ "id": 30,
674
+ "type": "UNETLoader",
675
+ "pos": [
676
+ -121.66298929390322,
677
+ -630.6289599251239
678
+ ],
679
+ "size": [
680
+ 270,
681
+ 82
682
+ ],
683
+ "flags": {},
684
+ "order": 9,
685
+ "mode": 0,
686
+ "inputs": [],
687
+ "outputs": [
688
+ {
689
+ "name": "MODEL",
690
+ "type": "MODEL",
691
+ "links": [
692
+ 40
693
+ ]
694
+ }
695
+ ],
696
+ "properties": {
697
+ "cnr_id": "comfy-core",
698
+ "ver": "0.18.1",
699
+ "Node name for S&R": "UNETLoader",
700
+ "ue_properties": {
701
+ "widget_ue_connectable": {},
702
+ "input_ue_unconnectable": {},
703
+ "version": "7.8"
704
+ }
705
+ },
706
+ "widgets_values": [
707
+ "krea2TurboOfficialComfy_krea2TurboNvfp4.safetensors",
708
+ "default"
709
+ ]
710
+ },
711
+ {
712
+ "id": 29,
713
+ "type": "Prompt (LoraManager)",
714
+ "pos": [
715
+ 1383.7331523126124,
716
+ -405.5066756515228
717
+ ],
718
+ "size": [
719
+ 407.86960005322544,
720
+ 233.17855783027926
721
+ ],
722
+ "flags": {},
723
+ "order": 18,
724
+ "mode": 0,
725
+ "inputs": [
726
+ {
727
+ "name": "clip",
728
+ "type": "CLIP",
729
+ "link": 31
730
+ },
731
+ {
732
+ "name": "seed",
733
+ "shape": 7,
734
+ "type": "INT",
735
+ "link": null
736
+ },
737
+ {
738
+ "name": "trigger_words1",
739
+ "shape": 7,
740
+ "type": "STRING",
741
+ "link": 32
742
+ },
743
+ {
744
+ "name": "trigger_words2",
745
+ "type": "STRING",
746
+ "link": null
747
+ }
748
+ ],
749
+ "outputs": [
750
+ {
751
+ "name": "CONDITIONING",
752
+ "type": "CONDITIONING",
753
+ "links": [
754
+ 45
755
+ ]
756
+ },
757
+ {
758
+ "name": "PROMPT",
759
+ "type": "STRING",
760
+ "links": null
761
+ }
762
+ ],
763
+ "properties": {
764
+ "cnr_id": "comfyui-lora-manager",
765
+ "ver": "4fcf641d5780e5cf140a54f1c23fd31117ddb7e2",
766
+ "Node name for S&R": "Prompt (LoraManager)",
767
+ "ue_properties": {
768
+ "widget_ue_connectable": {},
769
+ "input_ue_unconnectable": {},
770
+ "version": "7.7"
771
+ },
772
+ "__lm_widget_ids": [
773
+ "__lm_autocomplete_meta_text",
774
+ "text"
775
+ ]
776
+ },
777
+ "widgets_values": [
778
+ {
779
+ "version": 1,
780
+ "textWidgetName": "text",
781
+ "lastAccepted": {
782
+ "start": 0,
783
+ "end": 9,
784
+ "insertedText": "12beat13,",
785
+ "textSnapshot": "12beat13,"
786
+ }
787
+ },
788
+ "korean girl"
789
+ ],
790
+ "color": "#232",
791
+ "bgcolor": "#353"
792
+ },
793
+ {
794
+ "id": 14,
795
+ "type": "SaveImage",
796
+ "pos": [
797
+ 321.27544781116023,
798
+ 211.9154535777168
799
+ ],
800
+ "size": [
801
+ 805.0496647217428,
802
+ 1203.507734814826
803
+ ],
804
+ "flags": {},
805
+ "order": 26,
806
+ "mode": 0,
807
+ "inputs": [
808
+ {
809
+ "name": "images",
810
+ "type": "IMAGE",
811
+ "link": 62
812
+ }
813
+ ],
814
+ "outputs": [
815
+ {
816
+ "name": "images",
817
+ "type": "IMAGE",
818
+ "links": null
819
+ }
820
+ ],
821
+ "properties": {
822
+ "cnr_id": "comfy-core",
823
+ "ver": "0.16.4",
824
+ "Node name for S&R": "SaveImage",
825
+ "ue_properties": {
826
+ "widget_ue_connectable": {},
827
+ "input_ue_unconnectable": {},
828
+ "version": "7.7"
829
+ }
830
+ },
831
+ "widgets_values": [
832
+ "ComfyUI"
833
+ ]
834
+ },
835
+ {
836
+ "id": 12,
837
+ "type": "VAEDecode",
838
+ "pos": [
839
+ 1854.727483188264,
840
+ -298.49849528544695
841
+ ],
842
+ "size": [
843
+ 140,
844
+ 46
845
+ ],
846
+ "flags": {},
847
+ "order": 20,
848
+ "mode": 0,
849
+ "inputs": [
850
+ {
851
+ "name": "samples",
852
+ "type": "LATENT",
853
+ "link": 48
854
+ },
855
+ {
856
+ "name": "vae",
857
+ "type": "VAE",
858
+ "link": 17
859
+ }
860
+ ],
861
+ "outputs": [
862
+ {
863
+ "name": "IMAGE",
864
+ "type": "IMAGE",
865
+ "links": [
866
+ 80,
867
+ 85
868
+ ]
869
+ }
870
+ ],
871
+ "properties": {
872
+ "cnr_id": "comfy-core",
873
+ "ver": "0.16.4",
874
+ "Node name for S&R": "VAEDecode",
875
+ "ue_properties": {
876
+ "widget_ue_connectable": {},
877
+ "input_ue_unconnectable": {},
878
+ "version": "7.7"
879
+ }
880
+ },
881
+ "widgets_values": []
882
+ },
883
+ {
884
+ "id": 40,
885
+ "type": "VAEEncode",
886
+ "pos": [
887
+ 1286.2014157680758,
888
+ 31.86479772775502
889
+ ],
890
+ "size": [
891
+ 210,
892
+ 46
893
+ ],
894
+ "flags": {},
895
+ "order": 12,
896
+ "mode": 0,
897
+ "inputs": [
898
+ {
899
+ "name": "pixels",
900
+ "type": "IMAGE",
901
+ "link": 52
902
+ },
903
+ {
904
+ "name": "vae",
905
+ "type": "VAE",
906
+ "link": 53
907
+ }
908
+ ],
909
+ "outputs": [
910
+ {
911
+ "name": "LATENT",
912
+ "type": "LATENT",
913
+ "links": [
914
+ 54
915
+ ]
916
+ }
917
+ ],
918
+ "properties": {
919
+ "cnr_id": "comfy-core",
920
+ "ver": "0.16.4",
921
+ "Node name for S&R": "VAEEncode",
922
+ "ue_properties": {
923
+ "widget_ue_connectable": {},
924
+ "version": "7.8",
925
+ "input_ue_unconnectable": {}
926
+ }
927
+ },
928
+ "widgets_values": []
929
+ },
930
+ {
931
+ "id": 41,
932
+ "type": "SetLatentNoiseMask",
933
+ "pos": [
934
+ 1546.2014157680758,
935
+ 31.86479772775502
936
+ ],
937
+ "size": [
938
+ 210,
939
+ 46
940
+ ],
941
+ "flags": {},
942
+ "order": 14,
943
+ "mode": 0,
944
+ "inputs": [
945
+ {
946
+ "name": "samples",
947
+ "type": "LATENT",
948
+ "link": 54
949
+ },
950
+ {
951
+ "name": "mask",
952
+ "type": "MASK",
953
+ "link": 55
954
+ }
955
+ ],
956
+ "outputs": [
957
+ {
958
+ "name": "LATENT",
959
+ "type": "LATENT",
960
+ "links": [
961
+ 56
962
+ ]
963
+ }
964
+ ],
965
+ "properties": {
966
+ "cnr_id": "comfy-core",
967
+ "ver": "0.16.4",
968
+ "Node name for S&R": "SetLatentNoiseMask",
969
+ "ue_properties": {
970
+ "widget_ue_connectable": {},
971
+ "version": "7.8",
972
+ "input_ue_unconnectable": {}
973
+ }
974
+ },
975
+ "widgets_values": []
976
+ },
977
+ {
978
+ "id": 43,
979
+ "type": "LanPaint_MaskBlend",
980
+ "pos": [
981
+ 2388.816702727679,
982
+ -146.92603208283066
983
+ ],
984
+ "size": [
985
+ 270,
986
+ 110
987
+ ],
988
+ "flags": {},
989
+ "order": 25,
990
+ "mode": 0,
991
+ "inputs": [
992
+ {
993
+ "name": "image1",
994
+ "type": "IMAGE",
995
+ "link": 82
996
+ },
997
+ {
998
+ "name": "image2",
999
+ "type": "IMAGE",
1000
+ "link": 87
1001
+ },
1002
+ {
1003
+ "name": "mask",
1004
+ "type": "MASK",
1005
+ "link": 83
1006
+ }
1007
+ ],
1008
+ "outputs": [
1009
+ {
1010
+ "name": "IMAGE",
1011
+ "type": "IMAGE",
1012
+ "links": [
1013
+ 62
1014
+ ]
1015
+ }
1016
+ ],
1017
+ "title": "Blend Seam with Original",
1018
+ "properties": {
1019
+ "cnr_id": "lanpaint",
1020
+ "ver": "1.5.5",
1021
+ "Node name for S&R": "LanPaint_MaskBlend",
1022
+ "ue_properties": {
1023
+ "widget_ue_connectable": {},
1024
+ "version": "7.8",
1025
+ "input_ue_unconnectable": {}
1026
+ }
1027
+ },
1028
+ "widgets_values": [
1029
+ 51
1030
+ ]
1031
+ },
1032
+ {
1033
+ "id": 38,
1034
+ "type": "LanPaint_KSamplerAdvanced",
1035
+ "pos": [
1036
+ 1920.0939764613263,
1037
+ -141.16323281521963
1038
+ ],
1039
+ "size": [
1040
+ 383.1676476648677,
1041
+ 950
1042
+ ],
1043
+ "flags": {},
1044
+ "order": 19,
1045
+ "mode": 0,
1046
+ "inputs": [
1047
+ {
1048
+ "name": "model",
1049
+ "type": "MODEL",
1050
+ "link": 44
1051
+ },
1052
+ {
1053
+ "name": "positive",
1054
+ "type": "CONDITIONING",
1055
+ "link": 45
1056
+ },
1057
+ {
1058
+ "name": "negative",
1059
+ "type": "CONDITIONING",
1060
+ "link": 46
1061
+ },
1062
+ {
1063
+ "name": "latent_image",
1064
+ "type": "LATENT",
1065
+ "link": 56
1066
+ }
1067
+ ],
1068
+ "outputs": [
1069
+ {
1070
+ "name": "LATENT",
1071
+ "type": "LATENT",
1072
+ "links": [
1073
+ 48
1074
+ ]
1075
+ }
1076
+ ],
1077
+ "properties": {
1078
+ "cnr_id": "lanpaint",
1079
+ "ver": "1.5.5",
1080
+ "Node name for S&R": "LanPaint_KSamplerAdvanced",
1081
+ "ue_properties": {
1082
+ "widget_ue_connectable": {},
1083
+ "input_ue_unconnectable": {},
1084
+ "version": "7.8"
1085
+ }
1086
+ },
1087
+ "widgets_values": [
1088
+ "enable",
1089
+ 198527596812840,
1090
+ "randomize",
1091
+ 8,
1092
+ 1,
1093
+ "euler",
1094
+ "beta57",
1095
+ 0,
1096
+ 10000,
1097
+ "disable",
1098
+ 3,
1099
+ 10.0,
1100
+ 0.1,
1101
+ 0.5,
1102
+ 10.0,
1103
+ "Image First",
1104
+ 2,
1105
+ "LanPaint KSampler Adv.",
1106
+ "🖼️ Image Inpainting",
1107
+ 0.0,
1108
+ 1,
1109
+ "lanpaint_star_button"
1110
+ ]
1111
+ },
1112
+ {
1113
+ "id": 48,
1114
+ "type": "MatchImageAndMaskSize_Dolphin",
1115
+ "pos": [
1116
+ 1286.2014157680758,
1117
+ 151.86479772775502
1118
+ ],
1119
+ "size": [
1120
+ 270,
1121
+ 130
1122
+ ],
1123
+ "flags": {},
1124
+ "order": 21,
1125
+ "mode": 0,
1126
+ "inputs": [
1127
+ {
1128
+ "name": "image_to_resize",
1129
+ "type": "IMAGE",
1130
+ "link": 78
1131
+ },
1132
+ {
1133
+ "name": "mask_to_resize",
1134
+ "type": "MASK",
1135
+ "link": 79
1136
+ },
1137
+ {
1138
+ "name": "reference_image",
1139
+ "type": "IMAGE",
1140
+ "link": 80
1141
+ }
1142
+ ],
1143
+ "outputs": [
1144
+ {
1145
+ "name": "image",
1146
+ "type": "IMAGE",
1147
+ "links": [
1148
+ 82,
1149
+ 84
1150
+ ]
1151
+ },
1152
+ {
1153
+ "name": "mask",
1154
+ "type": "MASK",
1155
+ "links": [
1156
+ 83,
1157
+ 86
1158
+ ]
1159
+ }
1160
+ ],
1161
+ "title": "Match Original to Output Size",
1162
+ "properties": {
1163
+ "cnr_id": "dolphin_nodes",
1164
+ "Node name for S&R": "MatchImageAndMaskSize_Dolphin"
1165
+ },
1166
+ "widgets_values": []
1167
+ },
1168
+ {
1169
+ "id": 49,
1170
+ "type": "MaskRegionColorMatch_Dolphin",
1171
+ "pos": [
1172
+ 2165.4644495383623,
1173
+ -332.50977690725466
1174
+ ],
1175
+ "size": [
1176
+ 270,
1177
+ 170
1178
+ ],
1179
+ "flags": {},
1180
+ "order": 24,
1181
+ "mode": 0,
1182
+ "inputs": [
1183
+ {
1184
+ "name": "original_image",
1185
+ "type": "IMAGE",
1186
+ "link": 84
1187
+ },
1188
+ {
1189
+ "name": "generated_image",
1190
+ "type": "IMAGE",
1191
+ "link": 85
1192
+ },
1193
+ {
1194
+ "name": "mask",
1195
+ "type": "MASK",
1196
+ "link": 86
1197
+ }
1198
+ ],
1199
+ "outputs": [
1200
+ {
1201
+ "name": "image",
1202
+ "type": "IMAGE",
1203
+ "links": [
1204
+ 87
1205
+ ]
1206
+ }
1207
+ ],
1208
+ "title": "Color Match (Mask Region Only)",
1209
+ "properties": {
1210
+ "cnr_id": "dolphin_nodes",
1211
+ "Node name for S&R": "MaskRegionColorMatch_Dolphin"
1212
+ },
1213
+ "widgets_values": [
1214
+ "reinhard",
1215
+ 0.5,
1216
+ 150,
1217
+ 40
1218
+ ]
1219
+ }
1220
+ ],
1221
+ "links": [
1222
+ [
1223
+ 17,
1224
+ 7,
1225
+ 0,
1226
+ 12,
1227
+ 1,
1228
+ "VAE"
1229
+ ],
1230
+ [
1231
+ 30,
1232
+ 28,
1233
+ 2,
1234
+ 27,
1235
+ 0,
1236
+ "string"
1237
+ ],
1238
+ [
1239
+ 31,
1240
+ 28,
1241
+ 1,
1242
+ 29,
1243
+ 0,
1244
+ "CLIP"
1245
+ ],
1246
+ [
1247
+ 32,
1248
+ 27,
1249
+ 0,
1250
+ 29,
1251
+ 2,
1252
+ "STRING"
1253
+ ],
1254
+ [
1255
+ 37,
1256
+ 28,
1257
+ 1,
1258
+ 9,
1259
+ 0,
1260
+ "CLIP"
1261
+ ],
1262
+ [
1263
+ 39,
1264
+ 31,
1265
+ 0,
1266
+ 28,
1267
+ 1,
1268
+ "CLIP"
1269
+ ],
1270
+ [
1271
+ 40,
1272
+ 30,
1273
+ 0,
1274
+ 35,
1275
+ 0,
1276
+ "MODEL"
1277
+ ],
1278
+ [
1279
+ 41,
1280
+ 35,
1281
+ 0,
1282
+ 28,
1283
+ 0,
1284
+ "MODEL"
1285
+ ],
1286
+ [
1287
+ 42,
1288
+ 28,
1289
+ 0,
1290
+ 36,
1291
+ 0,
1292
+ "MODEL"
1293
+ ],
1294
+ [
1295
+ 44,
1296
+ 36,
1297
+ 0,
1298
+ 38,
1299
+ 0,
1300
+ "MODEL"
1301
+ ],
1302
+ [
1303
+ 45,
1304
+ 29,
1305
+ 0,
1306
+ 38,
1307
+ 1,
1308
+ "CONDITIONING"
1309
+ ],
1310
+ [
1311
+ 46,
1312
+ 9,
1313
+ 0,
1314
+ 38,
1315
+ 2,
1316
+ "CONDITIONING"
1317
+ ],
1318
+ [
1319
+ 48,
1320
+ 38,
1321
+ 0,
1322
+ 12,
1323
+ 0,
1324
+ "LATENT"
1325
+ ],
1326
+ [
1327
+ 50,
1328
+ 1,
1329
+ 0,
1330
+ 39,
1331
+ 0,
1332
+ "IMAGE"
1333
+ ],
1334
+ [
1335
+ 52,
1336
+ 39,
1337
+ 0,
1338
+ 40,
1339
+ 0,
1340
+ "IMAGE"
1341
+ ],
1342
+ [
1343
+ 53,
1344
+ 7,
1345
+ 0,
1346
+ 40,
1347
+ 1,
1348
+ "VAE"
1349
+ ],
1350
+ [
1351
+ 54,
1352
+ 40,
1353
+ 0,
1354
+ 41,
1355
+ 0,
1356
+ "LATENT"
1357
+ ],
1358
+ [
1359
+ 55,
1360
+ 1,
1361
+ 1,
1362
+ 41,
1363
+ 1,
1364
+ "MASK"
1365
+ ],
1366
+ [
1367
+ 56,
1368
+ 41,
1369
+ 0,
1370
+ 38,
1371
+ 3,
1372
+ "LATENT"
1373
+ ],
1374
+ [
1375
+ 62,
1376
+ 43,
1377
+ 0,
1378
+ 14,
1379
+ 0,
1380
+ "IMAGE"
1381
+ ],
1382
+ [
1383
+ 78,
1384
+ 39,
1385
+ 0,
1386
+ 48,
1387
+ 0,
1388
+ "IMAGE"
1389
+ ],
1390
+ [
1391
+ 79,
1392
+ 1,
1393
+ 1,
1394
+ 48,
1395
+ 1,
1396
+ "MASK"
1397
+ ],
1398
+ [
1399
+ 80,
1400
+ 12,
1401
+ 0,
1402
+ 48,
1403
+ 2,
1404
+ "IMAGE"
1405
+ ],
1406
+ [
1407
+ 82,
1408
+ 48,
1409
+ 0,
1410
+ 43,
1411
+ 0,
1412
+ "IMAGE"
1413
+ ],
1414
+ [
1415
+ 83,
1416
+ 48,
1417
+ 1,
1418
+ 43,
1419
+ 2,
1420
+ "MASK"
1421
+ ],
1422
+ [
1423
+ 84,
1424
+ 48,
1425
+ 0,
1426
+ 49,
1427
+ 0,
1428
+ "IMAGE"
1429
+ ],
1430
+ [
1431
+ 85,
1432
+ 12,
1433
+ 0,
1434
+ 49,
1435
+ 1,
1436
+ "IMAGE"
1437
+ ],
1438
+ [
1439
+ 86,
1440
+ 48,
1441
+ 1,
1442
+ 49,
1443
+ 2,
1444
+ "MASK"
1445
+ ],
1446
+ [
1447
+ 87,
1448
+ 49,
1449
+ 0,
1450
+ 43,
1451
+ 1,
1452
+ "IMAGE"
1453
+ ]
1454
+ ],
1455
+ "groups": [
1456
+ {
1457
+ "id": 1,
1458
+ "title": "Turbo Lora",
1459
+ "bounding": [
1460
+ 217.02048263467339,
1461
+ -648.7621951869007,
1462
+ 299.2239373235054,
1463
+ 191.911637393992
1464
+ ],
1465
+ "color": "#3f789e",
1466
+ "flags": {}
1467
+ },
1468
+ {
1469
+ "id": 2,
1470
+ "title": "Sageattention",
1471
+ "bounding": [
1472
+ 1316.448516061584,
1473
+ 300.1227252083272,
1474
+ 317.4210206114476,
1475
+ 152.78808325362013
1476
+ ],
1477
+ "color": "#3f789e",
1478
+ "flags": {}
1479
+ }
1480
+ ],
1481
+ "config": {},
1482
+ "extra": {
1483
+ "workflowRendererVersion": "LG",
1484
+ "ue_links": [],
1485
+ "ds": {
1486
+ "scale": 0.6934334949441378,
1487
+ "offset": [
1488
+ -984.5986415646988,
1489
+ 659.1808549965879
1490
+ ]
1491
+ },
1492
+ "links_added_by_ue": [],
1493
+ "frontendVersion": "1.45.20",
1494
+ "VHS_latentpreview": false,
1495
+ "VHS_latentpreviewrate": 0,
1496
+ "VHS_MetadataImage": false,
1497
+ "VHS_KeepIntermediate": true
1498
+ },
1499
+ "version": 0.4
1500
+ }
_archive/workflows/lora_test.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/slowmo_ab_test_A_core_wanimagetovideo.json ADDED
@@ -0,0 +1,1131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 20,
3
+ "last_link_id": 28,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 120
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "inputs": [],
20
+ "outputs": [
21
+ {
22
+ "name": "CLIP",
23
+ "type": "CLIP",
24
+ "links": [
25
+ 5,
26
+ 6
27
+ ],
28
+ "slot_index": 0
29
+ }
30
+ ],
31
+ "properties": {
32
+ "Node name for S&R": "CLIPLoader"
33
+ },
34
+ "widgets_values": [
35
+ "nsfw_wan_umt5-xxl_fp8_scaled.safetensors",
36
+ "wan",
37
+ "default"
38
+ ]
39
+ },
40
+ {
41
+ "id": 2,
42
+ "type": "VAELoader",
43
+ "pos": [
44
+ 0,
45
+ 160
46
+ ],
47
+ "size": [
48
+ 320,
49
+ 120
50
+ ],
51
+ "flags": {},
52
+ "order": 1,
53
+ "mode": 0,
54
+ "inputs": [],
55
+ "outputs": [
56
+ {
57
+ "name": "VAE",
58
+ "type": "VAE",
59
+ "links": [
60
+ 16,
61
+ 27
62
+ ],
63
+ "slot_index": 0
64
+ }
65
+ ],
66
+ "properties": {
67
+ "Node name for S&R": "VAELoader"
68
+ },
69
+ "widgets_values": [
70
+ "Wan2_1_VAE_bf16.safetensors"
71
+ ]
72
+ },
73
+ {
74
+ "id": 3,
75
+ "type": "UnetLoaderGGUF",
76
+ "pos": [
77
+ 0,
78
+ 320
79
+ ],
80
+ "size": [
81
+ 320,
82
+ 120
83
+ ],
84
+ "flags": {},
85
+ "order": 2,
86
+ "mode": 0,
87
+ "inputs": [],
88
+ "outputs": [
89
+ {
90
+ "name": "MODEL",
91
+ "type": "MODEL",
92
+ "links": [
93
+ 1
94
+ ],
95
+ "slot_index": 0
96
+ }
97
+ ],
98
+ "properties": {
99
+ "Node name for S&R": "UnetLoaderGGUF"
100
+ },
101
+ "widgets_values": [
102
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
103
+ ],
104
+ "title": "High-noise UNet"
105
+ },
106
+ {
107
+ "id": 4,
108
+ "type": "UnetLoaderGGUF",
109
+ "pos": [
110
+ 0,
111
+ 480
112
+ ],
113
+ "size": [
114
+ 320,
115
+ 120
116
+ ],
117
+ "flags": {},
118
+ "order": 3,
119
+ "mode": 0,
120
+ "inputs": [],
121
+ "outputs": [
122
+ {
123
+ "name": "MODEL",
124
+ "type": "MODEL",
125
+ "links": [
126
+ 3
127
+ ],
128
+ "slot_index": 0
129
+ }
130
+ ],
131
+ "properties": {
132
+ "Node name for S&R": "UnetLoaderGGUF"
133
+ },
134
+ "widgets_values": [
135
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
136
+ ],
137
+ "title": "Low-noise UNet"
138
+ },
139
+ {
140
+ "id": 5,
141
+ "type": "LoraLoaderModelOnly",
142
+ "pos": [
143
+ 360,
144
+ 320
145
+ ],
146
+ "size": [
147
+ 320,
148
+ 120
149
+ ],
150
+ "flags": {},
151
+ "order": 4,
152
+ "mode": 0,
153
+ "inputs": [
154
+ {
155
+ "name": "model",
156
+ "type": "MODEL",
157
+ "link": 1
158
+ }
159
+ ],
160
+ "outputs": [
161
+ {
162
+ "name": "MODEL",
163
+ "type": "MODEL",
164
+ "links": [
165
+ 2,
166
+ 7
167
+ ],
168
+ "slot_index": 0
169
+ }
170
+ ],
171
+ "properties": {
172
+ "Node name for S&R": "LoraLoaderModelOnly"
173
+ },
174
+ "widgets_values": [
175
+ "SVI_v2_PRO_Wan2.2-I2V-A14B_HIGH_lora_rank_128_fp16.safetensors",
176
+ 1.0
177
+ ],
178
+ "title": "SVI LoRA (HIGH, 1.0) = base_high"
179
+ },
180
+ {
181
+ "id": 6,
182
+ "type": "LoraLoaderModelOnly",
183
+ "pos": [
184
+ 720,
185
+ 320
186
+ ],
187
+ "size": [
188
+ 320,
189
+ 120
190
+ ],
191
+ "flags": {},
192
+ "order": 5,
193
+ "mode": 0,
194
+ "inputs": [
195
+ {
196
+ "name": "model",
197
+ "type": "MODEL",
198
+ "link": 2
199
+ }
200
+ ],
201
+ "outputs": [
202
+ {
203
+ "name": "MODEL",
204
+ "type": "MODEL",
205
+ "links": [
206
+ 9
207
+ ],
208
+ "slot_index": 0
209
+ }
210
+ ],
211
+ "properties": {
212
+ "Node name for S&R": "LoraLoaderModelOnly"
213
+ },
214
+ "widgets_values": [
215
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
216
+ 1.0
217
+ ],
218
+ "title": "+ lightx2v (1.0) = lightning_high"
219
+ },
220
+ {
221
+ "id": 7,
222
+ "type": "LoraLoaderModelOnly",
223
+ "pos": [
224
+ 360,
225
+ 480
226
+ ],
227
+ "size": [
228
+ 320,
229
+ 120
230
+ ],
231
+ "flags": {},
232
+ "order": 6,
233
+ "mode": 0,
234
+ "inputs": [
235
+ {
236
+ "name": "model",
237
+ "type": "MODEL",
238
+ "link": 3
239
+ }
240
+ ],
241
+ "outputs": [
242
+ {
243
+ "name": "MODEL",
244
+ "type": "MODEL",
245
+ "links": [
246
+ 4
247
+ ],
248
+ "slot_index": 0
249
+ }
250
+ ],
251
+ "properties": {
252
+ "Node name for S&R": "LoraLoaderModelOnly"
253
+ },
254
+ "widgets_values": [
255
+ "SVI_v2_PRO_Wan2.2-I2V-A14B_LOW_lora_rank_128_fp16.safetensors",
256
+ 1.0
257
+ ],
258
+ "title": "SVI LoRA (LOW, 1.0)"
259
+ },
260
+ {
261
+ "id": 8,
262
+ "type": "LoraLoaderModelOnly",
263
+ "pos": [
264
+ 720,
265
+ 480
266
+ ],
267
+ "size": [
268
+ 320,
269
+ 120
270
+ ],
271
+ "flags": {},
272
+ "order": 7,
273
+ "mode": 0,
274
+ "inputs": [
275
+ {
276
+ "name": "model",
277
+ "type": "MODEL",
278
+ "link": 4
279
+ }
280
+ ],
281
+ "outputs": [
282
+ {
283
+ "name": "MODEL",
284
+ "type": "MODEL",
285
+ "links": [
286
+ 11
287
+ ],
288
+ "slot_index": 0
289
+ }
290
+ ],
291
+ "properties": {
292
+ "Node name for S&R": "LoraLoaderModelOnly"
293
+ },
294
+ "widgets_values": [
295
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
296
+ 1.5
297
+ ],
298
+ "title": "+ lightx2v (1.5) = lightning_low"
299
+ },
300
+ {
301
+ "id": 9,
302
+ "type": "CLIPTextEncode",
303
+ "pos": [
304
+ 1080,
305
+ 0
306
+ ],
307
+ "size": [
308
+ 320,
309
+ 120
310
+ ],
311
+ "flags": {},
312
+ "order": 8,
313
+ "mode": 0,
314
+ "inputs": [
315
+ {
316
+ "name": "clip",
317
+ "type": "CLIP",
318
+ "link": 5
319
+ }
320
+ ],
321
+ "outputs": [
322
+ {
323
+ "name": "CONDITIONING",
324
+ "type": "CONDITIONING",
325
+ "links": [
326
+ 8,
327
+ 10,
328
+ 12,
329
+ 14
330
+ ],
331
+ "slot_index": 0
332
+ }
333
+ ],
334
+ "properties": {
335
+ "Node name for S&R": "CLIPTextEncode"
336
+ },
337
+ "widgets_values": [
338
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
339
+ ],
340
+ "title": "Positive prompt"
341
+ },
342
+ {
343
+ "id": 10,
344
+ "type": "CLIPTextEncode",
345
+ "pos": [
346
+ 1080,
347
+ 160
348
+ ],
349
+ "size": [
350
+ 320,
351
+ 120
352
+ ],
353
+ "flags": {},
354
+ "order": 9,
355
+ "mode": 0,
356
+ "inputs": [
357
+ {
358
+ "name": "clip",
359
+ "type": "CLIP",
360
+ "link": 6
361
+ }
362
+ ],
363
+ "outputs": [
364
+ {
365
+ "name": "CONDITIONING",
366
+ "type": "CONDITIONING",
367
+ "links": [
368
+ 15
369
+ ],
370
+ "slot_index": 0
371
+ }
372
+ ],
373
+ "properties": {
374
+ "Node name for S&R": "CLIPTextEncode"
375
+ },
376
+ "widgets_values": [
377
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
378
+ ],
379
+ "title": "Negative prompt"
380
+ },
381
+ {
382
+ "id": 11,
383
+ "type": "WanVideoNAG",
384
+ "pos": [
385
+ 1080,
386
+ 320
387
+ ],
388
+ "size": [
389
+ 320,
390
+ 120
391
+ ],
392
+ "flags": {},
393
+ "order": 10,
394
+ "mode": 0,
395
+ "inputs": [
396
+ {
397
+ "name": "model",
398
+ "type": "MODEL",
399
+ "link": 7
400
+ },
401
+ {
402
+ "name": "conditioning",
403
+ "type": "CONDITIONING",
404
+ "link": 8
405
+ }
406
+ ],
407
+ "outputs": [
408
+ {
409
+ "name": "model",
410
+ "type": "MODEL",
411
+ "links": [
412
+ 20
413
+ ],
414
+ "slot_index": 0
415
+ }
416
+ ],
417
+ "properties": {
418
+ "Node name for S&R": "WanVideoNAG"
419
+ },
420
+ "widgets_values": [
421
+ 11.0,
422
+ 0.25,
423
+ 2.5,
424
+ "default",
425
+ false
426
+ ],
427
+ "title": "NAG(base_high)"
428
+ },
429
+ {
430
+ "id": 12,
431
+ "type": "WanVideoNAG",
432
+ "pos": [
433
+ 1080,
434
+ 400
435
+ ],
436
+ "size": [
437
+ 320,
438
+ 120
439
+ ],
440
+ "flags": {},
441
+ "order": 11,
442
+ "mode": 0,
443
+ "inputs": [
444
+ {
445
+ "name": "model",
446
+ "type": "MODEL",
447
+ "link": 9
448
+ },
449
+ {
450
+ "name": "conditioning",
451
+ "type": "CONDITIONING",
452
+ "link": 10
453
+ }
454
+ ],
455
+ "outputs": [
456
+ {
457
+ "name": "model",
458
+ "type": "MODEL",
459
+ "links": [
460
+ 21
461
+ ],
462
+ "slot_index": 0
463
+ }
464
+ ],
465
+ "properties": {
466
+ "Node name for S&R": "WanVideoNAG"
467
+ },
468
+ "widgets_values": [
469
+ 11.0,
470
+ 0.25,
471
+ 2.5,
472
+ "default",
473
+ false
474
+ ],
475
+ "title": "NAG(lightning_high)"
476
+ },
477
+ {
478
+ "id": 13,
479
+ "type": "WanVideoNAG",
480
+ "pos": [
481
+ 1080,
482
+ 480
483
+ ],
484
+ "size": [
485
+ 320,
486
+ 120
487
+ ],
488
+ "flags": {},
489
+ "order": 12,
490
+ "mode": 0,
491
+ "inputs": [
492
+ {
493
+ "name": "model",
494
+ "type": "MODEL",
495
+ "link": 11
496
+ },
497
+ {
498
+ "name": "conditioning",
499
+ "type": "CONDITIONING",
500
+ "link": 12
501
+ }
502
+ ],
503
+ "outputs": [
504
+ {
505
+ "name": "model",
506
+ "type": "MODEL",
507
+ "links": [
508
+ 22
509
+ ],
510
+ "slot_index": 0
511
+ }
512
+ ],
513
+ "properties": {
514
+ "Node name for S&R": "WanVideoNAG"
515
+ },
516
+ "widgets_values": [
517
+ 11.0,
518
+ 0.25,
519
+ 2.5,
520
+ "default",
521
+ false
522
+ ],
523
+ "title": "NAG(lightning_low)"
524
+ },
525
+ {
526
+ "id": 14,
527
+ "type": "LoadImage",
528
+ "pos": [
529
+ 1440,
530
+ 660
531
+ ],
532
+ "size": [
533
+ 320,
534
+ 320
535
+ ],
536
+ "flags": {},
537
+ "order": 13,
538
+ "mode": 0,
539
+ "inputs": [],
540
+ "outputs": [
541
+ {
542
+ "name": "IMAGE",
543
+ "type": "IMAGE",
544
+ "links": [
545
+ 13
546
+ ],
547
+ "slot_index": 0
548
+ },
549
+ {
550
+ "name": "MASK",
551
+ "type": "MASK",
552
+ "links": [],
553
+ "slot_index": 1
554
+ }
555
+ ],
556
+ "properties": {
557
+ "Node name for S&R": "LoadImage"
558
+ },
559
+ "widgets_values": [
560
+ "input.png",
561
+ "image"
562
+ ]
563
+ },
564
+ {
565
+ "id": 15,
566
+ "type": "WanNativeResize_Dolphin",
567
+ "pos": [
568
+ 1800,
569
+ 660
570
+ ],
571
+ "size": [
572
+ 320,
573
+ 120
574
+ ],
575
+ "flags": {},
576
+ "order": 14,
577
+ "mode": 0,
578
+ "inputs": [
579
+ {
580
+ "name": "image",
581
+ "type": "IMAGE",
582
+ "link": 13
583
+ }
584
+ ],
585
+ "outputs": [
586
+ {
587
+ "name": "IMAGE",
588
+ "type": "IMAGE",
589
+ "links": [
590
+ 17
591
+ ],
592
+ "slot_index": 0
593
+ },
594
+ {
595
+ "name": "width",
596
+ "type": "INT",
597
+ "links": [
598
+ 18
599
+ ],
600
+ "slot_index": 1
601
+ },
602
+ {
603
+ "name": "height",
604
+ "type": "INT",
605
+ "links": [
606
+ 19
607
+ ],
608
+ "slot_index": 2
609
+ }
610
+ ],
611
+ "properties": {
612
+ "Node name for S&R": "WanNativeResize_Dolphin"
613
+ },
614
+ "widgets_values": [
615
+ "480P (832x480)",
616
+ "lanczos",
617
+ 16
618
+ ]
619
+ },
620
+ {
621
+ "id": 16,
622
+ "type": "WanImageToVideo",
623
+ "pos": [
624
+ 2160,
625
+ 160
626
+ ],
627
+ "size": [
628
+ 320,
629
+ 220
630
+ ],
631
+ "flags": {},
632
+ "order": 15,
633
+ "mode": 0,
634
+ "inputs": [
635
+ {
636
+ "name": "positive",
637
+ "type": "CONDITIONING",
638
+ "link": 14
639
+ },
640
+ {
641
+ "name": "negative",
642
+ "type": "CONDITIONING",
643
+ "link": 15
644
+ },
645
+ {
646
+ "name": "vae",
647
+ "type": "VAE",
648
+ "link": 16
649
+ },
650
+ {
651
+ "name": "clip_vision_output",
652
+ "type": "CLIP_VISION_OUTPUT",
653
+ "link": null,
654
+ "shape": 7
655
+ },
656
+ {
657
+ "name": "start_image",
658
+ "type": "IMAGE",
659
+ "link": 17,
660
+ "shape": 7
661
+ },
662
+ {
663
+ "name": "width",
664
+ "type": "INT",
665
+ "link": 18,
666
+ "widget": {
667
+ "name": "width"
668
+ }
669
+ },
670
+ {
671
+ "name": "height",
672
+ "type": "INT",
673
+ "link": 19,
674
+ "widget": {
675
+ "name": "height"
676
+ }
677
+ }
678
+ ],
679
+ "outputs": [
680
+ {
681
+ "name": "positive",
682
+ "type": "CONDITIONING",
683
+ "links": [
684
+ 23
685
+ ],
686
+ "slot_index": 0
687
+ },
688
+ {
689
+ "name": "negative",
690
+ "type": "CONDITIONING",
691
+ "links": [
692
+ 24
693
+ ],
694
+ "slot_index": 1
695
+ },
696
+ {
697
+ "name": "latent",
698
+ "type": "LATENT",
699
+ "links": [
700
+ 25
701
+ ],
702
+ "slot_index": 2
703
+ }
704
+ ],
705
+ "properties": {
706
+ "Node name for S&R": "WanImageToVideo"
707
+ },
708
+ "widgets_values": [
709
+ 832,
710
+ 480,
711
+ 81,
712
+ 1
713
+ ],
714
+ "title": "A: core WanImageToVideo"
715
+ },
716
+ {
717
+ "id": 17,
718
+ "type": "TripleKSamplerWan22LightningAdvanced",
719
+ "pos": [
720
+ 2560,
721
+ 160
722
+ ],
723
+ "size": [
724
+ 360,
725
+ 420
726
+ ],
727
+ "flags": {},
728
+ "order": 16,
729
+ "mode": 0,
730
+ "inputs": [
731
+ {
732
+ "name": "base_high",
733
+ "type": "MODEL",
734
+ "link": 20
735
+ },
736
+ {
737
+ "name": "lightning_high",
738
+ "type": "MODEL",
739
+ "link": 21
740
+ },
741
+ {
742
+ "name": "lightning_low",
743
+ "type": "MODEL",
744
+ "link": 22
745
+ },
746
+ {
747
+ "name": "positive",
748
+ "type": "CONDITIONING",
749
+ "link": 23
750
+ },
751
+ {
752
+ "name": "negative",
753
+ "type": "CONDITIONING",
754
+ "link": 24
755
+ },
756
+ {
757
+ "name": "latent_image",
758
+ "type": "LATENT",
759
+ "link": 25
760
+ }
761
+ ],
762
+ "outputs": [
763
+ {
764
+ "name": "LATENT",
765
+ "type": "LATENT",
766
+ "links": [
767
+ 26
768
+ ],
769
+ "slot_index": 0
770
+ }
771
+ ],
772
+ "properties": {
773
+ "Node name for S&R": "TripleKSamplerWan22LightningAdvanced"
774
+ },
775
+ "widgets_values": [
776
+ 909090909090,
777
+ "fixed",
778
+ 5.0,
779
+ 20,
780
+ -1,
781
+ 5.0,
782
+ "euler",
783
+ "simple",
784
+ 1,
785
+ 20,
786
+ 1.0,
787
+ "euler",
788
+ "simple",
789
+ "50% of steps",
790
+ -1,
791
+ 0.875,
792
+ false
793
+ ],
794
+ "title": "TripleKSamplerWan22LightningAdvanced (real prod values: base_cfg=5, lightning_steps=20, boundary=0.875)"
795
+ },
796
+ {
797
+ "id": 18,
798
+ "type": "VAEDecode",
799
+ "pos": [
800
+ 2960,
801
+ 160
802
+ ],
803
+ "size": [
804
+ 320,
805
+ 120
806
+ ],
807
+ "flags": {},
808
+ "order": 17,
809
+ "mode": 0,
810
+ "inputs": [
811
+ {
812
+ "name": "samples",
813
+ "type": "LATENT",
814
+ "link": 26
815
+ },
816
+ {
817
+ "name": "vae",
818
+ "type": "VAE",
819
+ "link": 27
820
+ }
821
+ ],
822
+ "outputs": [
823
+ {
824
+ "name": "IMAGE",
825
+ "type": "IMAGE",
826
+ "links": [
827
+ 28
828
+ ],
829
+ "slot_index": 0
830
+ }
831
+ ],
832
+ "properties": {
833
+ "Node name for S&R": "VAEDecode"
834
+ },
835
+ "widgets_values": []
836
+ },
837
+ {
838
+ "id": 19,
839
+ "type": "VHS_VideoCombine",
840
+ "pos": [
841
+ 3320,
842
+ 160
843
+ ],
844
+ "size": [
845
+ 320,
846
+ 260
847
+ ],
848
+ "flags": {},
849
+ "order": 18,
850
+ "mode": 0,
851
+ "inputs": [
852
+ {
853
+ "name": "images",
854
+ "type": "IMAGE",
855
+ "link": 28
856
+ }
857
+ ],
858
+ "outputs": [],
859
+ "properties": {
860
+ "Node name for S&R": "VHS_VideoCombine"
861
+ },
862
+ "widgets_values": {
863
+ "frame_rate": 16,
864
+ "loop_count": 0,
865
+ "filename_prefix": "wan22_slowmo/ab_test_A_core_wanimagetovideo",
866
+ "format": "video/h264-mp4",
867
+ "pingpong": false,
868
+ "save_image": true,
869
+ "crf": 19,
870
+ "save_metadata": true,
871
+ "audio_file": ""
872
+ }
873
+ },
874
+ {
875
+ "id": 20,
876
+ "type": "Note",
877
+ "pos": [
878
+ 2560,
879
+ 620
880
+ ],
881
+ "size": [
882
+ 1000,
883
+ 220
884
+ ],
885
+ "flags": {},
886
+ "order": 19,
887
+ "mode": 0,
888
+ "inputs": [],
889
+ "outputs": [],
890
+ "properties": {
891
+ "Node name for S&R": "Note"
892
+ },
893
+ "widgets_values": [
894
+ "A/B 테스트 A안: 코어 WanImageToVideo 사용 (SVIProGray 대신).\nB안과 완전히 동일한 모델/LoRA(base_high/lightning_high/lightning_low)/NAG/TripleKSamplerWan22LightningAdvanced 설정, 컨디셔닝 노드만 다름.\n여기서도 후반부 그레이가 나오면, 문제는 wan_svipro_gray.py가 아니라 '앵커 1장 + 81프레임 필러'라는 방식 자체가 이 LoRA/스텝 예산에서 한계에 부딪히는 것."
895
+ ],
896
+ "title": "A/B Test A: core WanImageToVideo",
897
+ "color": "#432",
898
+ "bgcolor": "#653"
899
+ }
900
+ ],
901
+ "links": [
902
+ [
903
+ 1,
904
+ 3,
905
+ 0,
906
+ 5,
907
+ 0,
908
+ "MODEL"
909
+ ],
910
+ [
911
+ 2,
912
+ 5,
913
+ 0,
914
+ 6,
915
+ 0,
916
+ "MODEL"
917
+ ],
918
+ [
919
+ 3,
920
+ 4,
921
+ 0,
922
+ 7,
923
+ 0,
924
+ "MODEL"
925
+ ],
926
+ [
927
+ 4,
928
+ 7,
929
+ 0,
930
+ 8,
931
+ 0,
932
+ "MODEL"
933
+ ],
934
+ [
935
+ 5,
936
+ 1,
937
+ 0,
938
+ 9,
939
+ 0,
940
+ "CLIP"
941
+ ],
942
+ [
943
+ 6,
944
+ 1,
945
+ 0,
946
+ 10,
947
+ 0,
948
+ "CLIP"
949
+ ],
950
+ [
951
+ 7,
952
+ 5,
953
+ 0,
954
+ 11,
955
+ 0,
956
+ "MODEL"
957
+ ],
958
+ [
959
+ 8,
960
+ 9,
961
+ 0,
962
+ 11,
963
+ 1,
964
+ "CONDITIONING"
965
+ ],
966
+ [
967
+ 9,
968
+ 6,
969
+ 0,
970
+ 12,
971
+ 0,
972
+ "MODEL"
973
+ ],
974
+ [
975
+ 10,
976
+ 9,
977
+ 0,
978
+ 12,
979
+ 1,
980
+ "CONDITIONING"
981
+ ],
982
+ [
983
+ 11,
984
+ 8,
985
+ 0,
986
+ 13,
987
+ 0,
988
+ "MODEL"
989
+ ],
990
+ [
991
+ 12,
992
+ 9,
993
+ 0,
994
+ 13,
995
+ 1,
996
+ "CONDITIONING"
997
+ ],
998
+ [
999
+ 13,
1000
+ 14,
1001
+ 0,
1002
+ 15,
1003
+ 0,
1004
+ "IMAGE"
1005
+ ],
1006
+ [
1007
+ 14,
1008
+ 9,
1009
+ 0,
1010
+ 16,
1011
+ 0,
1012
+ "CONDITIONING"
1013
+ ],
1014
+ [
1015
+ 15,
1016
+ 10,
1017
+ 0,
1018
+ 16,
1019
+ 1,
1020
+ "CONDITIONING"
1021
+ ],
1022
+ [
1023
+ 16,
1024
+ 2,
1025
+ 0,
1026
+ 16,
1027
+ 2,
1028
+ "VAE"
1029
+ ],
1030
+ [
1031
+ 17,
1032
+ 15,
1033
+ 0,
1034
+ 16,
1035
+ 4,
1036
+ "IMAGE"
1037
+ ],
1038
+ [
1039
+ 18,
1040
+ 15,
1041
+ 1,
1042
+ 16,
1043
+ 5,
1044
+ "INT"
1045
+ ],
1046
+ [
1047
+ 19,
1048
+ 15,
1049
+ 2,
1050
+ 16,
1051
+ 6,
1052
+ "INT"
1053
+ ],
1054
+ [
1055
+ 20,
1056
+ 11,
1057
+ 0,
1058
+ 17,
1059
+ 0,
1060
+ "MODEL"
1061
+ ],
1062
+ [
1063
+ 21,
1064
+ 12,
1065
+ 0,
1066
+ 17,
1067
+ 1,
1068
+ "MODEL"
1069
+ ],
1070
+ [
1071
+ 22,
1072
+ 13,
1073
+ 0,
1074
+ 17,
1075
+ 2,
1076
+ "MODEL"
1077
+ ],
1078
+ [
1079
+ 23,
1080
+ 16,
1081
+ 0,
1082
+ 17,
1083
+ 3,
1084
+ "CONDITIONING"
1085
+ ],
1086
+ [
1087
+ 24,
1088
+ 16,
1089
+ 1,
1090
+ 17,
1091
+ 4,
1092
+ "CONDITIONING"
1093
+ ],
1094
+ [
1095
+ 25,
1096
+ 16,
1097
+ 2,
1098
+ 17,
1099
+ 5,
1100
+ "LATENT"
1101
+ ],
1102
+ [
1103
+ 26,
1104
+ 17,
1105
+ 0,
1106
+ 18,
1107
+ 0,
1108
+ "LATENT"
1109
+ ],
1110
+ [
1111
+ 27,
1112
+ 2,
1113
+ 0,
1114
+ 18,
1115
+ 1,
1116
+ "VAE"
1117
+ ],
1118
+ [
1119
+ 28,
1120
+ 18,
1121
+ 0,
1122
+ 19,
1123
+ 0,
1124
+ "IMAGE"
1125
+ ]
1126
+ ],
1127
+ "groups": [],
1128
+ "config": {},
1129
+ "extra": {},
1130
+ "version": 0.4
1131
+ }
_archive/workflows/slowmo_ab_test_B_svigray.json ADDED
@@ -0,0 +1,1151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 21,
3
+ "last_link_id": 28,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 120
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "inputs": [],
20
+ "outputs": [
21
+ {
22
+ "name": "CLIP",
23
+ "type": "CLIP",
24
+ "links": [
25
+ 5,
26
+ 6
27
+ ],
28
+ "slot_index": 0
29
+ }
30
+ ],
31
+ "properties": {
32
+ "Node name for S&R": "CLIPLoader"
33
+ },
34
+ "widgets_values": [
35
+ "nsfw_wan_umt5-xxl_fp8_scaled.safetensors",
36
+ "wan",
37
+ "default"
38
+ ]
39
+ },
40
+ {
41
+ "id": 2,
42
+ "type": "VAELoader",
43
+ "pos": [
44
+ 0,
45
+ 160
46
+ ],
47
+ "size": [
48
+ 320,
49
+ 120
50
+ ],
51
+ "flags": {},
52
+ "order": 1,
53
+ "mode": 0,
54
+ "inputs": [],
55
+ "outputs": [
56
+ {
57
+ "name": "VAE",
58
+ "type": "VAE",
59
+ "links": [
60
+ 15,
61
+ 18,
62
+ 27
63
+ ],
64
+ "slot_index": 0
65
+ }
66
+ ],
67
+ "properties": {
68
+ "Node name for S&R": "VAELoader"
69
+ },
70
+ "widgets_values": [
71
+ "Wan2_1_VAE_bf16.safetensors"
72
+ ]
73
+ },
74
+ {
75
+ "id": 3,
76
+ "type": "UnetLoaderGGUF",
77
+ "pos": [
78
+ 0,
79
+ 320
80
+ ],
81
+ "size": [
82
+ 320,
83
+ 120
84
+ ],
85
+ "flags": {},
86
+ "order": 2,
87
+ "mode": 0,
88
+ "inputs": [],
89
+ "outputs": [
90
+ {
91
+ "name": "MODEL",
92
+ "type": "MODEL",
93
+ "links": [
94
+ 1
95
+ ],
96
+ "slot_index": 0
97
+ }
98
+ ],
99
+ "properties": {
100
+ "Node name for S&R": "UnetLoaderGGUF"
101
+ },
102
+ "widgets_values": [
103
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
104
+ ],
105
+ "title": "High-noise UNet"
106
+ },
107
+ {
108
+ "id": 4,
109
+ "type": "UnetLoaderGGUF",
110
+ "pos": [
111
+ 0,
112
+ 480
113
+ ],
114
+ "size": [
115
+ 320,
116
+ 120
117
+ ],
118
+ "flags": {},
119
+ "order": 3,
120
+ "mode": 0,
121
+ "inputs": [],
122
+ "outputs": [
123
+ {
124
+ "name": "MODEL",
125
+ "type": "MODEL",
126
+ "links": [
127
+ 3
128
+ ],
129
+ "slot_index": 0
130
+ }
131
+ ],
132
+ "properties": {
133
+ "Node name for S&R": "UnetLoaderGGUF"
134
+ },
135
+ "widgets_values": [
136
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
137
+ ],
138
+ "title": "Low-noise UNet"
139
+ },
140
+ {
141
+ "id": 5,
142
+ "type": "LoraLoaderModelOnly",
143
+ "pos": [
144
+ 360,
145
+ 320
146
+ ],
147
+ "size": [
148
+ 320,
149
+ 120
150
+ ],
151
+ "flags": {},
152
+ "order": 4,
153
+ "mode": 0,
154
+ "inputs": [
155
+ {
156
+ "name": "model",
157
+ "type": "MODEL",
158
+ "link": 1
159
+ }
160
+ ],
161
+ "outputs": [
162
+ {
163
+ "name": "MODEL",
164
+ "type": "MODEL",
165
+ "links": [
166
+ 2,
167
+ 7
168
+ ],
169
+ "slot_index": 0
170
+ }
171
+ ],
172
+ "properties": {
173
+ "Node name for S&R": "LoraLoaderModelOnly"
174
+ },
175
+ "widgets_values": [
176
+ "SVI_v2_PRO_Wan2.2-I2V-A14B_HIGH_lora_rank_128_fp16.safetensors",
177
+ 1.0
178
+ ],
179
+ "title": "SVI LoRA (HIGH, 1.0) = base_high"
180
+ },
181
+ {
182
+ "id": 6,
183
+ "type": "LoraLoaderModelOnly",
184
+ "pos": [
185
+ 720,
186
+ 320
187
+ ],
188
+ "size": [
189
+ 320,
190
+ 120
191
+ ],
192
+ "flags": {},
193
+ "order": 5,
194
+ "mode": 0,
195
+ "inputs": [
196
+ {
197
+ "name": "model",
198
+ "type": "MODEL",
199
+ "link": 2
200
+ }
201
+ ],
202
+ "outputs": [
203
+ {
204
+ "name": "MODEL",
205
+ "type": "MODEL",
206
+ "links": [
207
+ 9
208
+ ],
209
+ "slot_index": 0
210
+ }
211
+ ],
212
+ "properties": {
213
+ "Node name for S&R": "LoraLoaderModelOnly"
214
+ },
215
+ "widgets_values": [
216
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
217
+ 1.0
218
+ ],
219
+ "title": "+ lightx2v (1.0) = lightning_high"
220
+ },
221
+ {
222
+ "id": 7,
223
+ "type": "LoraLoaderModelOnly",
224
+ "pos": [
225
+ 360,
226
+ 480
227
+ ],
228
+ "size": [
229
+ 320,
230
+ 120
231
+ ],
232
+ "flags": {},
233
+ "order": 6,
234
+ "mode": 0,
235
+ "inputs": [
236
+ {
237
+ "name": "model",
238
+ "type": "MODEL",
239
+ "link": 3
240
+ }
241
+ ],
242
+ "outputs": [
243
+ {
244
+ "name": "MODEL",
245
+ "type": "MODEL",
246
+ "links": [
247
+ 4
248
+ ],
249
+ "slot_index": 0
250
+ }
251
+ ],
252
+ "properties": {
253
+ "Node name for S&R": "LoraLoaderModelOnly"
254
+ },
255
+ "widgets_values": [
256
+ "SVI_v2_PRO_Wan2.2-I2V-A14B_LOW_lora_rank_128_fp16.safetensors",
257
+ 1.0
258
+ ],
259
+ "title": "SVI LoRA (LOW, 1.0)"
260
+ },
261
+ {
262
+ "id": 8,
263
+ "type": "LoraLoaderModelOnly",
264
+ "pos": [
265
+ 720,
266
+ 480
267
+ ],
268
+ "size": [
269
+ 320,
270
+ 120
271
+ ],
272
+ "flags": {},
273
+ "order": 7,
274
+ "mode": 0,
275
+ "inputs": [
276
+ {
277
+ "name": "model",
278
+ "type": "MODEL",
279
+ "link": 4
280
+ }
281
+ ],
282
+ "outputs": [
283
+ {
284
+ "name": "MODEL",
285
+ "type": "MODEL",
286
+ "links": [
287
+ 11
288
+ ],
289
+ "slot_index": 0
290
+ }
291
+ ],
292
+ "properties": {
293
+ "Node name for S&R": "LoraLoaderModelOnly"
294
+ },
295
+ "widgets_values": [
296
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
297
+ 1.5
298
+ ],
299
+ "title": "+ lightx2v (1.5) = lightning_low"
300
+ },
301
+ {
302
+ "id": 9,
303
+ "type": "CLIPTextEncode",
304
+ "pos": [
305
+ 1080,
306
+ 0
307
+ ],
308
+ "size": [
309
+ 320,
310
+ 120
311
+ ],
312
+ "flags": {},
313
+ "order": 8,
314
+ "mode": 0,
315
+ "inputs": [
316
+ {
317
+ "name": "clip",
318
+ "type": "CLIP",
319
+ "link": 5
320
+ }
321
+ ],
322
+ "outputs": [
323
+ {
324
+ "name": "CONDITIONING",
325
+ "type": "CONDITIONING",
326
+ "links": [
327
+ 8,
328
+ 10,
329
+ 12,
330
+ 16
331
+ ],
332
+ "slot_index": 0
333
+ }
334
+ ],
335
+ "properties": {
336
+ "Node name for S&R": "CLIPTextEncode"
337
+ },
338
+ "widgets_values": [
339
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
340
+ ],
341
+ "title": "Positive prompt"
342
+ },
343
+ {
344
+ "id": 10,
345
+ "type": "CLIPTextEncode",
346
+ "pos": [
347
+ 1080,
348
+ 160
349
+ ],
350
+ "size": [
351
+ 320,
352
+ 120
353
+ ],
354
+ "flags": {},
355
+ "order": 9,
356
+ "mode": 0,
357
+ "inputs": [
358
+ {
359
+ "name": "clip",
360
+ "type": "CLIP",
361
+ "link": 6
362
+ }
363
+ ],
364
+ "outputs": [
365
+ {
366
+ "name": "CONDITIONING",
367
+ "type": "CONDITIONING",
368
+ "links": [
369
+ 17
370
+ ],
371
+ "slot_index": 0
372
+ }
373
+ ],
374
+ "properties": {
375
+ "Node name for S&R": "CLIPTextEncode"
376
+ },
377
+ "widgets_values": [
378
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
379
+ ],
380
+ "title": "Negative prompt"
381
+ },
382
+ {
383
+ "id": 11,
384
+ "type": "WanVideoNAG",
385
+ "pos": [
386
+ 1080,
387
+ 320
388
+ ],
389
+ "size": [
390
+ 320,
391
+ 120
392
+ ],
393
+ "flags": {},
394
+ "order": 10,
395
+ "mode": 0,
396
+ "inputs": [
397
+ {
398
+ "name": "model",
399
+ "type": "MODEL",
400
+ "link": 7
401
+ },
402
+ {
403
+ "name": "conditioning",
404
+ "type": "CONDITIONING",
405
+ "link": 8
406
+ }
407
+ ],
408
+ "outputs": [
409
+ {
410
+ "name": "model",
411
+ "type": "MODEL",
412
+ "links": [
413
+ 20
414
+ ],
415
+ "slot_index": 0
416
+ }
417
+ ],
418
+ "properties": {
419
+ "Node name for S&R": "WanVideoNAG"
420
+ },
421
+ "widgets_values": [
422
+ 11.0,
423
+ 0.25,
424
+ 2.5,
425
+ "default",
426
+ false
427
+ ],
428
+ "title": "NAG(base_high)"
429
+ },
430
+ {
431
+ "id": 12,
432
+ "type": "WanVideoNAG",
433
+ "pos": [
434
+ 1080,
435
+ 400
436
+ ],
437
+ "size": [
438
+ 320,
439
+ 120
440
+ ],
441
+ "flags": {},
442
+ "order": 11,
443
+ "mode": 0,
444
+ "inputs": [
445
+ {
446
+ "name": "model",
447
+ "type": "MODEL",
448
+ "link": 9
449
+ },
450
+ {
451
+ "name": "conditioning",
452
+ "type": "CONDITIONING",
453
+ "link": 10
454
+ }
455
+ ],
456
+ "outputs": [
457
+ {
458
+ "name": "model",
459
+ "type": "MODEL",
460
+ "links": [
461
+ 21
462
+ ],
463
+ "slot_index": 0
464
+ }
465
+ ],
466
+ "properties": {
467
+ "Node name for S&R": "WanVideoNAG"
468
+ },
469
+ "widgets_values": [
470
+ 11.0,
471
+ 0.25,
472
+ 2.5,
473
+ "default",
474
+ false
475
+ ],
476
+ "title": "NAG(lightning_high)"
477
+ },
478
+ {
479
+ "id": 13,
480
+ "type": "WanVideoNAG",
481
+ "pos": [
482
+ 1080,
483
+ 480
484
+ ],
485
+ "size": [
486
+ 320,
487
+ 120
488
+ ],
489
+ "flags": {},
490
+ "order": 12,
491
+ "mode": 0,
492
+ "inputs": [
493
+ {
494
+ "name": "model",
495
+ "type": "MODEL",
496
+ "link": 11
497
+ },
498
+ {
499
+ "name": "conditioning",
500
+ "type": "CONDITIONING",
501
+ "link": 12
502
+ }
503
+ ],
504
+ "outputs": [
505
+ {
506
+ "name": "model",
507
+ "type": "MODEL",
508
+ "links": [
509
+ 22
510
+ ],
511
+ "slot_index": 0
512
+ }
513
+ ],
514
+ "properties": {
515
+ "Node name for S&R": "WanVideoNAG"
516
+ },
517
+ "widgets_values": [
518
+ 11.0,
519
+ 0.25,
520
+ 2.5,
521
+ "default",
522
+ false
523
+ ],
524
+ "title": "NAG(lightning_low)"
525
+ },
526
+ {
527
+ "id": 14,
528
+ "type": "LoadImage",
529
+ "pos": [
530
+ 1440,
531
+ 660
532
+ ],
533
+ "size": [
534
+ 320,
535
+ 320
536
+ ],
537
+ "flags": {},
538
+ "order": 13,
539
+ "mode": 0,
540
+ "inputs": [],
541
+ "outputs": [
542
+ {
543
+ "name": "IMAGE",
544
+ "type": "IMAGE",
545
+ "links": [
546
+ 13
547
+ ],
548
+ "slot_index": 0
549
+ },
550
+ {
551
+ "name": "MASK",
552
+ "type": "MASK",
553
+ "links": [],
554
+ "slot_index": 1
555
+ }
556
+ ],
557
+ "properties": {
558
+ "Node name for S&R": "LoadImage"
559
+ },
560
+ "widgets_values": [
561
+ "input.png",
562
+ "image"
563
+ ]
564
+ },
565
+ {
566
+ "id": 15,
567
+ "type": "WanNativeResize_Dolphin",
568
+ "pos": [
569
+ 1800,
570
+ 660
571
+ ],
572
+ "size": [
573
+ 320,
574
+ 120
575
+ ],
576
+ "flags": {},
577
+ "order": 14,
578
+ "mode": 0,
579
+ "inputs": [
580
+ {
581
+ "name": "image",
582
+ "type": "IMAGE",
583
+ "link": 13
584
+ }
585
+ ],
586
+ "outputs": [
587
+ {
588
+ "name": "IMAGE",
589
+ "type": "IMAGE",
590
+ "links": [
591
+ 14
592
+ ],
593
+ "slot_index": 0
594
+ },
595
+ {
596
+ "name": "width",
597
+ "type": "INT",
598
+ "links": [],
599
+ "slot_index": 1
600
+ },
601
+ {
602
+ "name": "height",
603
+ "type": "INT",
604
+ "links": [],
605
+ "slot_index": 2
606
+ }
607
+ ],
608
+ "properties": {
609
+ "Node name for S&R": "WanNativeResize_Dolphin"
610
+ },
611
+ "widgets_values": [
612
+ "480P (832x480)",
613
+ "lanczos",
614
+ 16
615
+ ]
616
+ },
617
+ {
618
+ "id": 16,
619
+ "type": "VAEEncode",
620
+ "pos": [
621
+ 2160,
622
+ 660
623
+ ],
624
+ "size": [
625
+ 320,
626
+ 120
627
+ ],
628
+ "flags": {},
629
+ "order": 15,
630
+ "mode": 0,
631
+ "inputs": [
632
+ {
633
+ "name": "pixels",
634
+ "type": "IMAGE",
635
+ "link": 14
636
+ },
637
+ {
638
+ "name": "vae",
639
+ "type": "VAE",
640
+ "link": 15
641
+ }
642
+ ],
643
+ "outputs": [
644
+ {
645
+ "name": "LATENT",
646
+ "type": "LATENT",
647
+ "links": [
648
+ 19
649
+ ],
650
+ "slot_index": 0
651
+ }
652
+ ],
653
+ "properties": {
654
+ "Node name for S&R": "VAEEncode"
655
+ },
656
+ "widgets_values": [],
657
+ "title": "Anchor encode"
658
+ },
659
+ {
660
+ "id": 17,
661
+ "type": "WanImageToVideoSVIProGray_Dolphin",
662
+ "pos": [
663
+ 2160,
664
+ 160
665
+ ],
666
+ "size": [
667
+ 340,
668
+ 220
669
+ ],
670
+ "flags": {},
671
+ "order": 16,
672
+ "mode": 0,
673
+ "inputs": [
674
+ {
675
+ "name": "positive",
676
+ "type": "CONDITIONING",
677
+ "link": 16
678
+ },
679
+ {
680
+ "name": "negative",
681
+ "type": "CONDITIONING",
682
+ "link": 17
683
+ },
684
+ {
685
+ "name": "vae",
686
+ "type": "VAE",
687
+ "link": 18
688
+ },
689
+ {
690
+ "name": "anchor_samples",
691
+ "type": "LATENT",
692
+ "link": 19
693
+ },
694
+ {
695
+ "name": "prev_samples",
696
+ "type": "LATENT",
697
+ "link": null,
698
+ "shape": 7
699
+ }
700
+ ],
701
+ "outputs": [
702
+ {
703
+ "name": "positive",
704
+ "type": "CONDITIONING",
705
+ "links": [
706
+ 23
707
+ ],
708
+ "slot_index": 0
709
+ },
710
+ {
711
+ "name": "negative",
712
+ "type": "CONDITIONING",
713
+ "links": [
714
+ 24
715
+ ],
716
+ "slot_index": 1
717
+ },
718
+ {
719
+ "name": "latent",
720
+ "type": "LATENT",
721
+ "links": [
722
+ 25
723
+ ],
724
+ "slot_index": 2
725
+ }
726
+ ],
727
+ "properties": {
728
+ "Node name for S&R": "WanImageToVideoSVIProGray_Dolphin"
729
+ },
730
+ "widgets_values": [
731
+ 81,
732
+ 0
733
+ ],
734
+ "title": "B: WanImageToVideoSVIProGray_Dolphin"
735
+ },
736
+ {
737
+ "id": 18,
738
+ "type": "TripleKSamplerWan22LightningAdvanced",
739
+ "pos": [
740
+ 2560,
741
+ 160
742
+ ],
743
+ "size": [
744
+ 360,
745
+ 420
746
+ ],
747
+ "flags": {},
748
+ "order": 17,
749
+ "mode": 0,
750
+ "inputs": [
751
+ {
752
+ "name": "base_high",
753
+ "type": "MODEL",
754
+ "link": 20
755
+ },
756
+ {
757
+ "name": "lightning_high",
758
+ "type": "MODEL",
759
+ "link": 21
760
+ },
761
+ {
762
+ "name": "lightning_low",
763
+ "type": "MODEL",
764
+ "link": 22
765
+ },
766
+ {
767
+ "name": "positive",
768
+ "type": "CONDITIONING",
769
+ "link": 23
770
+ },
771
+ {
772
+ "name": "negative",
773
+ "type": "CONDITIONING",
774
+ "link": 24
775
+ },
776
+ {
777
+ "name": "latent_image",
778
+ "type": "LATENT",
779
+ "link": 25
780
+ }
781
+ ],
782
+ "outputs": [
783
+ {
784
+ "name": "LATENT",
785
+ "type": "LATENT",
786
+ "links": [
787
+ 26
788
+ ],
789
+ "slot_index": 0
790
+ }
791
+ ],
792
+ "properties": {
793
+ "Node name for S&R": "TripleKSamplerWan22LightningAdvanced"
794
+ },
795
+ "widgets_values": [
796
+ 909090909090,
797
+ "fixed",
798
+ 5.0,
799
+ 20,
800
+ -1,
801
+ 5.0,
802
+ "euler",
803
+ "simple",
804
+ 1,
805
+ 20,
806
+ 1.0,
807
+ "euler",
808
+ "simple",
809
+ "50% of steps",
810
+ -1,
811
+ 0.875,
812
+ false
813
+ ],
814
+ "title": "TripleKSamplerWan22LightningAdvanced (real prod values: base_cfg=5, lightning_steps=20, boundary=0.875)"
815
+ },
816
+ {
817
+ "id": 19,
818
+ "type": "VAEDecode",
819
+ "pos": [
820
+ 2960,
821
+ 160
822
+ ],
823
+ "size": [
824
+ 320,
825
+ 120
826
+ ],
827
+ "flags": {},
828
+ "order": 18,
829
+ "mode": 0,
830
+ "inputs": [
831
+ {
832
+ "name": "samples",
833
+ "type": "LATENT",
834
+ "link": 26
835
+ },
836
+ {
837
+ "name": "vae",
838
+ "type": "VAE",
839
+ "link": 27
840
+ }
841
+ ],
842
+ "outputs": [
843
+ {
844
+ "name": "IMAGE",
845
+ "type": "IMAGE",
846
+ "links": [
847
+ 28
848
+ ],
849
+ "slot_index": 0
850
+ }
851
+ ],
852
+ "properties": {
853
+ "Node name for S&R": "VAEDecode"
854
+ },
855
+ "widgets_values": []
856
+ },
857
+ {
858
+ "id": 20,
859
+ "type": "VHS_VideoCombine",
860
+ "pos": [
861
+ 3320,
862
+ 160
863
+ ],
864
+ "size": [
865
+ 320,
866
+ 260
867
+ ],
868
+ "flags": {},
869
+ "order": 19,
870
+ "mode": 0,
871
+ "inputs": [
872
+ {
873
+ "name": "images",
874
+ "type": "IMAGE",
875
+ "link": 28
876
+ }
877
+ ],
878
+ "outputs": [],
879
+ "properties": {
880
+ "Node name for S&R": "VHS_VideoCombine"
881
+ },
882
+ "widgets_values": {
883
+ "frame_rate": 16,
884
+ "loop_count": 0,
885
+ "filename_prefix": "wan22_slowmo/ab_test_B_svigray",
886
+ "format": "video/h264-mp4",
887
+ "pingpong": false,
888
+ "save_image": true,
889
+ "crf": 19,
890
+ "save_metadata": true,
891
+ "audio_file": ""
892
+ }
893
+ },
894
+ {
895
+ "id": 21,
896
+ "type": "Note",
897
+ "pos": [
898
+ 2560,
899
+ 620
900
+ ],
901
+ "size": [
902
+ 1000,
903
+ 260
904
+ ],
905
+ "flags": {},
906
+ "order": 20,
907
+ "mode": 0,
908
+ "inputs": [],
909
+ "outputs": [],
910
+ "properties": {
911
+ "Node name for S&R": "Note"
912
+ },
913
+ "widgets_values": [
914
+ "A/B 테스트 B안: WanImageToVideoSVIProGray_Dolphin 사용.\n실제 프로덕션 값 재현: base_high=GGUF+SVI(1.0), lightning_high=+lightx2v(1.0), lightning_low=GGUF+SVI(1.0)+lightx2v(1.5). NAG(11/0.25/2.5) 세 모델 다 적용. TripleKSamplerWan22LightningAdvanced: base_cfg=5, lightning_steps=20, boundary=0.875, switch_strategy=50% of steps (0705_vision3.json 실측값). 단일 클립, anchor 1장만(콜드스타트).\nA안(core WanImageToVideo)과 완전히 동일한 모델/LoRA/NAG/샘플러 설정, 컨디셔닝 노드만 다름.\n둘 다 후반부 그레이면 '1앵커+80프레임필러' 자체의 한계, B만 그레이면 wan_svipro_gray.py 버그."
915
+ ],
916
+ "title": "A/B Test B: SVIProGray",
917
+ "color": "#432",
918
+ "bgcolor": "#653"
919
+ }
920
+ ],
921
+ "links": [
922
+ [
923
+ 1,
924
+ 3,
925
+ 0,
926
+ 5,
927
+ 0,
928
+ "MODEL"
929
+ ],
930
+ [
931
+ 2,
932
+ 5,
933
+ 0,
934
+ 6,
935
+ 0,
936
+ "MODEL"
937
+ ],
938
+ [
939
+ 3,
940
+ 4,
941
+ 0,
942
+ 7,
943
+ 0,
944
+ "MODEL"
945
+ ],
946
+ [
947
+ 4,
948
+ 7,
949
+ 0,
950
+ 8,
951
+ 0,
952
+ "MODEL"
953
+ ],
954
+ [
955
+ 5,
956
+ 1,
957
+ 0,
958
+ 9,
959
+ 0,
960
+ "CLIP"
961
+ ],
962
+ [
963
+ 6,
964
+ 1,
965
+ 0,
966
+ 10,
967
+ 0,
968
+ "CLIP"
969
+ ],
970
+ [
971
+ 7,
972
+ 5,
973
+ 0,
974
+ 11,
975
+ 0,
976
+ "MODEL"
977
+ ],
978
+ [
979
+ 8,
980
+ 9,
981
+ 0,
982
+ 11,
983
+ 1,
984
+ "CONDITIONING"
985
+ ],
986
+ [
987
+ 9,
988
+ 6,
989
+ 0,
990
+ 12,
991
+ 0,
992
+ "MODEL"
993
+ ],
994
+ [
995
+ 10,
996
+ 9,
997
+ 0,
998
+ 12,
999
+ 1,
1000
+ "CONDITIONING"
1001
+ ],
1002
+ [
1003
+ 11,
1004
+ 8,
1005
+ 0,
1006
+ 13,
1007
+ 0,
1008
+ "MODEL"
1009
+ ],
1010
+ [
1011
+ 12,
1012
+ 9,
1013
+ 0,
1014
+ 13,
1015
+ 1,
1016
+ "CONDITIONING"
1017
+ ],
1018
+ [
1019
+ 13,
1020
+ 14,
1021
+ 0,
1022
+ 15,
1023
+ 0,
1024
+ "IMAGE"
1025
+ ],
1026
+ [
1027
+ 14,
1028
+ 15,
1029
+ 0,
1030
+ 16,
1031
+ 0,
1032
+ "IMAGE"
1033
+ ],
1034
+ [
1035
+ 15,
1036
+ 2,
1037
+ 0,
1038
+ 16,
1039
+ 1,
1040
+ "VAE"
1041
+ ],
1042
+ [
1043
+ 16,
1044
+ 9,
1045
+ 0,
1046
+ 17,
1047
+ 0,
1048
+ "CONDITIONING"
1049
+ ],
1050
+ [
1051
+ 17,
1052
+ 10,
1053
+ 0,
1054
+ 17,
1055
+ 1,
1056
+ "CONDITIONING"
1057
+ ],
1058
+ [
1059
+ 18,
1060
+ 2,
1061
+ 0,
1062
+ 17,
1063
+ 2,
1064
+ "VAE"
1065
+ ],
1066
+ [
1067
+ 19,
1068
+ 16,
1069
+ 0,
1070
+ 17,
1071
+ 3,
1072
+ "LATENT"
1073
+ ],
1074
+ [
1075
+ 20,
1076
+ 11,
1077
+ 0,
1078
+ 18,
1079
+ 0,
1080
+ "MODEL"
1081
+ ],
1082
+ [
1083
+ 21,
1084
+ 12,
1085
+ 0,
1086
+ 18,
1087
+ 1,
1088
+ "MODEL"
1089
+ ],
1090
+ [
1091
+ 22,
1092
+ 13,
1093
+ 0,
1094
+ 18,
1095
+ 2,
1096
+ "MODEL"
1097
+ ],
1098
+ [
1099
+ 23,
1100
+ 17,
1101
+ 0,
1102
+ 18,
1103
+ 3,
1104
+ "CONDITIONING"
1105
+ ],
1106
+ [
1107
+ 24,
1108
+ 17,
1109
+ 1,
1110
+ 18,
1111
+ 4,
1112
+ "CONDITIONING"
1113
+ ],
1114
+ [
1115
+ 25,
1116
+ 17,
1117
+ 2,
1118
+ 18,
1119
+ 5,
1120
+ "LATENT"
1121
+ ],
1122
+ [
1123
+ 26,
1124
+ 18,
1125
+ 0,
1126
+ 19,
1127
+ 0,
1128
+ "LATENT"
1129
+ ],
1130
+ [
1131
+ 27,
1132
+ 2,
1133
+ 0,
1134
+ 19,
1135
+ 1,
1136
+ "VAE"
1137
+ ],
1138
+ [
1139
+ 28,
1140
+ 19,
1141
+ 0,
1142
+ 20,
1143
+ 0,
1144
+ "IMAGE"
1145
+ ]
1146
+ ],
1147
+ "groups": [],
1148
+ "config": {},
1149
+ "extra": {},
1150
+ "version": 0.4
1151
+ }
_archive/workflows/slowmo_scenario1_hi_cfg35_lo_lightx2v.json ADDED
@@ -0,0 +1,948 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 17,
3
+ "last_link_id": 23,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 120
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "inputs": [],
20
+ "outputs": [
21
+ {
22
+ "name": "CLIP",
23
+ "type": "CLIP",
24
+ "links": [
25
+ 4,
26
+ 5
27
+ ],
28
+ "slot_index": 0
29
+ }
30
+ ],
31
+ "properties": {
32
+ "Node name for S&R": "CLIPLoader"
33
+ },
34
+ "widgets_values": [
35
+ "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
36
+ "wan",
37
+ "default"
38
+ ]
39
+ },
40
+ {
41
+ "id": 2,
42
+ "type": "VAELoader",
43
+ "pos": [
44
+ 0,
45
+ 160
46
+ ],
47
+ "size": [
48
+ 320,
49
+ 120
50
+ ],
51
+ "flags": {},
52
+ "order": 1,
53
+ "mode": 0,
54
+ "inputs": [],
55
+ "outputs": [
56
+ {
57
+ "name": "VAE",
58
+ "type": "VAE",
59
+ "links": [
60
+ 9,
61
+ 22
62
+ ],
63
+ "slot_index": 0
64
+ }
65
+ ],
66
+ "properties": {
67
+ "Node name for S&R": "VAELoader"
68
+ },
69
+ "widgets_values": [
70
+ "wan_2.1_vae.safetensors"
71
+ ]
72
+ },
73
+ {
74
+ "id": 3,
75
+ "type": "UnetLoaderGGUF",
76
+ "pos": [
77
+ 0,
78
+ 320
79
+ ],
80
+ "size": [
81
+ 320,
82
+ 120
83
+ ],
84
+ "flags": {},
85
+ "order": 2,
86
+ "mode": 0,
87
+ "inputs": [],
88
+ "outputs": [
89
+ {
90
+ "name": "MODEL",
91
+ "type": "MODEL",
92
+ "links": [
93
+ 1
94
+ ],
95
+ "slot_index": 0
96
+ }
97
+ ],
98
+ "properties": {
99
+ "Node name for S&R": "UnetLoaderGGUF"
100
+ },
101
+ "widgets_values": [
102
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
103
+ ],
104
+ "title": "High-noise UNet"
105
+ },
106
+ {
107
+ "id": 4,
108
+ "type": "UnetLoaderGGUF",
109
+ "pos": [
110
+ 0,
111
+ 480
112
+ ],
113
+ "size": [
114
+ 320,
115
+ 120
116
+ ],
117
+ "flags": {},
118
+ "order": 3,
119
+ "mode": 0,
120
+ "inputs": [],
121
+ "outputs": [
122
+ {
123
+ "name": "MODEL",
124
+ "type": "MODEL",
125
+ "links": [
126
+ 2
127
+ ],
128
+ "slot_index": 0
129
+ }
130
+ ],
131
+ "properties": {
132
+ "Node name for S&R": "UnetLoaderGGUF"
133
+ },
134
+ "widgets_values": [
135
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
136
+ ],
137
+ "title": "Low-noise UNet"
138
+ },
139
+ {
140
+ "id": 5,
141
+ "type": "ModelSamplingSD3",
142
+ "pos": [
143
+ 360,
144
+ 320
145
+ ],
146
+ "size": [
147
+ 320,
148
+ 120
149
+ ],
150
+ "flags": {},
151
+ "order": 4,
152
+ "mode": 0,
153
+ "inputs": [
154
+ {
155
+ "name": "model",
156
+ "type": "MODEL",
157
+ "link": 1
158
+ }
159
+ ],
160
+ "outputs": [
161
+ {
162
+ "name": "MODEL",
163
+ "type": "MODEL",
164
+ "links": [
165
+ 13
166
+ ],
167
+ "slot_index": 0
168
+ }
169
+ ],
170
+ "properties": {
171
+ "Node name for S&R": "ModelSamplingSD3"
172
+ },
173
+ "widgets_values": [
174
+ 5.0
175
+ ],
176
+ "title": "High noise (NO lightx2v, full CFG)"
177
+ },
178
+ {
179
+ "id": 6,
180
+ "type": "LoraLoaderModelOnly",
181
+ "pos": [
182
+ 360,
183
+ 480
184
+ ],
185
+ "size": [
186
+ 320,
187
+ 120
188
+ ],
189
+ "flags": {},
190
+ "order": 5,
191
+ "mode": 0,
192
+ "inputs": [
193
+ {
194
+ "name": "model",
195
+ "type": "MODEL",
196
+ "link": 2
197
+ }
198
+ ],
199
+ "outputs": [
200
+ {
201
+ "name": "MODEL",
202
+ "type": "MODEL",
203
+ "links": [
204
+ 3
205
+ ],
206
+ "slot_index": 0
207
+ }
208
+ ],
209
+ "properties": {
210
+ "Node name for S&R": "LoraLoaderModelOnly"
211
+ },
212
+ "widgets_values": [
213
+ "wan2.2_i2v_A14b_low_noise_lora_rank64_lightx2v_4step_1022.safetensors",
214
+ 1.0
215
+ ],
216
+ "title": "lightx2v LoRA (LOW only)"
217
+ },
218
+ {
219
+ "id": 7,
220
+ "type": "ModelSamplingSD3",
221
+ "pos": [
222
+ 720,
223
+ 480
224
+ ],
225
+ "size": [
226
+ 320,
227
+ 120
228
+ ],
229
+ "flags": {},
230
+ "order": 6,
231
+ "mode": 0,
232
+ "inputs": [
233
+ {
234
+ "name": "model",
235
+ "type": "MODEL",
236
+ "link": 3
237
+ }
238
+ ],
239
+ "outputs": [
240
+ {
241
+ "name": "MODEL",
242
+ "type": "MODEL",
243
+ "links": [
244
+ 17
245
+ ],
246
+ "slot_index": 0
247
+ }
248
+ ],
249
+ "properties": {
250
+ "Node name for S&R": "ModelSamplingSD3"
251
+ },
252
+ "widgets_values": [
253
+ 5.0
254
+ ],
255
+ "title": "Low noise (lightx2v, CFG1)"
256
+ },
257
+ {
258
+ "id": 8,
259
+ "type": "CLIPTextEncode",
260
+ "pos": [
261
+ 720,
262
+ 0
263
+ ],
264
+ "size": [
265
+ 320,
266
+ 120
267
+ ],
268
+ "flags": {},
269
+ "order": 7,
270
+ "mode": 0,
271
+ "inputs": [
272
+ {
273
+ "name": "clip",
274
+ "type": "CLIP",
275
+ "link": 4
276
+ }
277
+ ],
278
+ "outputs": [
279
+ {
280
+ "name": "CONDITIONING",
281
+ "type": "CONDITIONING",
282
+ "links": [
283
+ 7
284
+ ],
285
+ "slot_index": 0
286
+ }
287
+ ],
288
+ "properties": {
289
+ "Node name for S&R": "CLIPTextEncode"
290
+ },
291
+ "widgets_values": [
292
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
293
+ ],
294
+ "title": "Positive prompt"
295
+ },
296
+ {
297
+ "id": 9,
298
+ "type": "CLIPTextEncode",
299
+ "pos": [
300
+ 720,
301
+ 160
302
+ ],
303
+ "size": [
304
+ 320,
305
+ 120
306
+ ],
307
+ "flags": {},
308
+ "order": 8,
309
+ "mode": 0,
310
+ "inputs": [
311
+ {
312
+ "name": "clip",
313
+ "type": "CLIP",
314
+ "link": 5
315
+ }
316
+ ],
317
+ "outputs": [
318
+ {
319
+ "name": "CONDITIONING",
320
+ "type": "CONDITIONING",
321
+ "links": [
322
+ 8
323
+ ],
324
+ "slot_index": 0
325
+ }
326
+ ],
327
+ "properties": {
328
+ "Node name for S&R": "CLIPTextEncode"
329
+ },
330
+ "widgets_values": [
331
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
332
+ ],
333
+ "title": "Negative prompt"
334
+ },
335
+ {
336
+ "id": 10,
337
+ "type": "LoadImage",
338
+ "pos": [
339
+ 720,
340
+ 340
341
+ ],
342
+ "size": [
343
+ 320,
344
+ 320
345
+ ],
346
+ "flags": {},
347
+ "order": 9,
348
+ "mode": 0,
349
+ "inputs": [],
350
+ "outputs": [
351
+ {
352
+ "name": "IMAGE",
353
+ "type": "IMAGE",
354
+ "links": [
355
+ 6
356
+ ],
357
+ "slot_index": 0
358
+ },
359
+ {
360
+ "name": "MASK",
361
+ "type": "MASK",
362
+ "links": [],
363
+ "slot_index": 1
364
+ }
365
+ ],
366
+ "properties": {
367
+ "Node name for S&R": "LoadImage"
368
+ },
369
+ "widgets_values": [
370
+ "input.png",
371
+ "image"
372
+ ]
373
+ },
374
+ {
375
+ "id": 11,
376
+ "type": "WanNativeResize_Dolphin",
377
+ "pos": [
378
+ 1080,
379
+ 340
380
+ ],
381
+ "size": [
382
+ 320,
383
+ 120
384
+ ],
385
+ "flags": {},
386
+ "order": 10,
387
+ "mode": 0,
388
+ "inputs": [
389
+ {
390
+ "name": "image",
391
+ "type": "IMAGE",
392
+ "link": 6
393
+ }
394
+ ],
395
+ "outputs": [
396
+ {
397
+ "name": "IMAGE",
398
+ "type": "IMAGE",
399
+ "links": [
400
+ 10
401
+ ],
402
+ "slot_index": 0
403
+ },
404
+ {
405
+ "name": "width",
406
+ "type": "INT",
407
+ "links": [
408
+ 11
409
+ ],
410
+ "slot_index": 1
411
+ },
412
+ {
413
+ "name": "height",
414
+ "type": "INT",
415
+ "links": [
416
+ 12
417
+ ],
418
+ "slot_index": 2
419
+ }
420
+ ],
421
+ "properties": {
422
+ "Node name for S&R": "WanNativeResize_Dolphin"
423
+ },
424
+ "widgets_values": [
425
+ "720P (1280x720)",
426
+ "lanczos",
427
+ 16
428
+ ]
429
+ },
430
+ {
431
+ "id": 12,
432
+ "type": "WanImageToVideo",
433
+ "pos": [
434
+ 1480,
435
+ 160
436
+ ],
437
+ "size": [
438
+ 320,
439
+ 220
440
+ ],
441
+ "flags": {},
442
+ "order": 11,
443
+ "mode": 0,
444
+ "inputs": [
445
+ {
446
+ "name": "positive",
447
+ "type": "CONDITIONING",
448
+ "link": 7
449
+ },
450
+ {
451
+ "name": "negative",
452
+ "type": "CONDITIONING",
453
+ "link": 8
454
+ },
455
+ {
456
+ "name": "vae",
457
+ "type": "VAE",
458
+ "link": 9
459
+ },
460
+ {
461
+ "name": "clip_vision_output",
462
+ "type": "CLIP_VISION_OUTPUT",
463
+ "link": null,
464
+ "shape": 7
465
+ },
466
+ {
467
+ "name": "start_image",
468
+ "type": "IMAGE",
469
+ "link": 10,
470
+ "shape": 7
471
+ },
472
+ {
473
+ "name": "width",
474
+ "type": "INT",
475
+ "link": 11,
476
+ "widget": {
477
+ "name": "width"
478
+ }
479
+ },
480
+ {
481
+ "name": "height",
482
+ "type": "INT",
483
+ "link": 12,
484
+ "widget": {
485
+ "name": "height"
486
+ }
487
+ }
488
+ ],
489
+ "outputs": [
490
+ {
491
+ "name": "positive",
492
+ "type": "CONDITIONING",
493
+ "links": [
494
+ 14,
495
+ 18
496
+ ],
497
+ "slot_index": 0
498
+ },
499
+ {
500
+ "name": "negative",
501
+ "type": "CONDITIONING",
502
+ "links": [
503
+ 15,
504
+ 19
505
+ ],
506
+ "slot_index": 1
507
+ },
508
+ {
509
+ "name": "latent",
510
+ "type": "LATENT",
511
+ "links": [
512
+ 16
513
+ ],
514
+ "slot_index": 2
515
+ }
516
+ ],
517
+ "properties": {
518
+ "Node name for S&R": "WanImageToVideo"
519
+ },
520
+ "widgets_values": [
521
+ 1280,
522
+ 720,
523
+ 81,
524
+ 1
525
+ ]
526
+ },
527
+ {
528
+ "id": 13,
529
+ "type": "KSamplerAdvanced",
530
+ "pos": [
531
+ 1900,
532
+ 160
533
+ ],
534
+ "size": [
535
+ 320,
536
+ 260
537
+ ],
538
+ "flags": {},
539
+ "order": 12,
540
+ "mode": 0,
541
+ "inputs": [
542
+ {
543
+ "name": "model",
544
+ "type": "MODEL",
545
+ "link": 13
546
+ },
547
+ {
548
+ "name": "positive",
549
+ "type": "CONDITIONING",
550
+ "link": 14
551
+ },
552
+ {
553
+ "name": "negative",
554
+ "type": "CONDITIONING",
555
+ "link": 15
556
+ },
557
+ {
558
+ "name": "latent_image",
559
+ "type": "LATENT",
560
+ "link": 16
561
+ }
562
+ ],
563
+ "outputs": [
564
+ {
565
+ "name": "LATENT",
566
+ "type": "LATENT",
567
+ "links": [
568
+ 20
569
+ ],
570
+ "slot_index": 0
571
+ }
572
+ ],
573
+ "properties": {
574
+ "Node name for S&R": "KSamplerAdvanced"
575
+ },
576
+ "widgets_values": [
577
+ "enable",
578
+ 999111222333,
579
+ "fixed",
580
+ 16,
581
+ 3.5,
582
+ "euler",
583
+ "simple",
584
+ 0,
585
+ 16,
586
+ "disable"
587
+ ],
588
+ "title": "HIGH stage - no lightx2v, CFG 3.5, independent 16-step schedule, finishes fully (0->16 of 16)"
589
+ },
590
+ {
591
+ "id": 14,
592
+ "type": "KSamplerAdvanced",
593
+ "pos": [
594
+ 2260,
595
+ 160
596
+ ],
597
+ "size": [
598
+ 320,
599
+ 260
600
+ ],
601
+ "flags": {},
602
+ "order": 13,
603
+ "mode": 0,
604
+ "inputs": [
605
+ {
606
+ "name": "model",
607
+ "type": "MODEL",
608
+ "link": 17
609
+ },
610
+ {
611
+ "name": "positive",
612
+ "type": "CONDITIONING",
613
+ "link": 18
614
+ },
615
+ {
616
+ "name": "negative",
617
+ "type": "CONDITIONING",
618
+ "link": 19
619
+ },
620
+ {
621
+ "name": "latent_image",
622
+ "type": "LATENT",
623
+ "link": 20
624
+ }
625
+ ],
626
+ "outputs": [
627
+ {
628
+ "name": "LATENT",
629
+ "type": "LATENT",
630
+ "links": [
631
+ 21
632
+ ],
633
+ "slot_index": 0
634
+ }
635
+ ],
636
+ "properties": {
637
+ "Node name for S&R": "KSamplerAdvanced"
638
+ },
639
+ "widgets_values": [
640
+ "enable",
641
+ 999111222333,
642
+ "fixed",
643
+ 4,
644
+ 1.0,
645
+ "euler",
646
+ "simple",
647
+ 2,
648
+ 4,
649
+ "disable"
650
+ ],
651
+ "title": "LOW stage - lightx2v, CFG 1, OWN independent 4-step schedule, re-noise at step 2/4 (denoise~0.5 touch-up)"
652
+ },
653
+ {
654
+ "id": 15,
655
+ "type": "VAEDecode",
656
+ "pos": [
657
+ 2620,
658
+ 160
659
+ ],
660
+ "size": [
661
+ 320,
662
+ 120
663
+ ],
664
+ "flags": {},
665
+ "order": 14,
666
+ "mode": 0,
667
+ "inputs": [
668
+ {
669
+ "name": "samples",
670
+ "type": "LATENT",
671
+ "link": 21
672
+ },
673
+ {
674
+ "name": "vae",
675
+ "type": "VAE",
676
+ "link": 22
677
+ }
678
+ ],
679
+ "outputs": [
680
+ {
681
+ "name": "IMAGE",
682
+ "type": "IMAGE",
683
+ "links": [
684
+ 23
685
+ ],
686
+ "slot_index": 0
687
+ }
688
+ ],
689
+ "properties": {
690
+ "Node name for S&R": "VAEDecode"
691
+ },
692
+ "widgets_values": []
693
+ },
694
+ {
695
+ "id": 16,
696
+ "type": "VHS_VideoCombine",
697
+ "pos": [
698
+ 2980,
699
+ 160
700
+ ],
701
+ "size": [
702
+ 320,
703
+ 260
704
+ ],
705
+ "flags": {},
706
+ "order": 15,
707
+ "mode": 0,
708
+ "inputs": [
709
+ {
710
+ "name": "images",
711
+ "type": "IMAGE",
712
+ "link": 23
713
+ }
714
+ ],
715
+ "outputs": [],
716
+ "properties": {
717
+ "Node name for S&R": "VHS_VideoCombine"
718
+ },
719
+ "widgets_values": {
720
+ "frame_rate": 16,
721
+ "loop_count": 0,
722
+ "filename_prefix": "wan22_slowmo/hi_nolightx2v_cfg35_lo_lightx2v",
723
+ "format": "video/h264-mp4",
724
+ "pingpong": false,
725
+ "save_image": true,
726
+ "crf": 19,
727
+ "save_metadata": true,
728
+ "audio_file": ""
729
+ }
730
+ },
731
+ {
732
+ "id": 17,
733
+ "type": "Note",
734
+ "pos": [
735
+ 2620,
736
+ 480
737
+ ],
738
+ "size": [
739
+ 900,
740
+ 260
741
+ ],
742
+ "flags": {},
743
+ "order": 16,
744
+ "mode": 0,
745
+ "inputs": [],
746
+ "outputs": [],
747
+ "properties": {
748
+ "Node name for S&R": "Note"
749
+ },
750
+ "widgets_values": [
751
+ "시나리오: Hi=lightx2v 없음(CFG3.5) / Lo=lightx2v만(CFG1, 디테일 정제).\n2026-07-29 재설계: High/Low가 steps 총량을 공유하는 슬라이싱 구조를 버리고 완전히 독립된 스케줄로 분리함.\n- 문제 이력: 처음엔 steps=12 공유(0-8-12)로 캐릭터 얼굴/디테일이 흔들림 → High를 8→16, 총steps 12→20으로 늘렸더니 오히려 컬러 노이즈(TV 스노우 같은 발산)로 악화됨. 원인: 공유 스케줄에서 High 비중을 늘릴수록 Low가 넘겨받는 시그마 값이 lightx2v가 실제 학습한 4스텝 노이즈 레벨에서 더 멀어져서, Low가 완전히 낯선 노이즈 레벨을 받아 발산한 것.\n- 수정: High는 자기만의 독립된 16스텝 스케줄로 CFG3.5, 완전히 끝까지(0->16, leftover noise 없이) 디노이징. Low는 그 결과물에 add_noise=enable로 재노이즈를 살짝 태워서(steps=4의 2->4, denoise~0.5 상당) lightx2v 고유 4스텝 스케줄로 마무리. 두 스테이지가 이제 서로의 스텝 수에 영향 안 받음.\n튜닝 포인트: Low의 재노이즈 강도는 start_at_step(2/4)으로 조절 — 낮추면(1) lightx2v 영향/디테일 정제 강해지지만 캐릭터 드리프트 위험 커짐, 높이면(3) High 결과를 더 보존하지만 정제 효과 약해짐. 시드는 두 노드 동일(999111222333)."
752
+ ],
753
+ "title": "Scenario 1: Hi=CFG3.5(no lightx2v) / Lo=lightx2v only",
754
+ "color": "#432",
755
+ "bgcolor": "#653"
756
+ }
757
+ ],
758
+ "links": [
759
+ [
760
+ 1,
761
+ 3,
762
+ 0,
763
+ 5,
764
+ 0,
765
+ "MODEL"
766
+ ],
767
+ [
768
+ 2,
769
+ 4,
770
+ 0,
771
+ 6,
772
+ 0,
773
+ "MODEL"
774
+ ],
775
+ [
776
+ 3,
777
+ 6,
778
+ 0,
779
+ 7,
780
+ 0,
781
+ "MODEL"
782
+ ],
783
+ [
784
+ 4,
785
+ 1,
786
+ 0,
787
+ 8,
788
+ 0,
789
+ "CLIP"
790
+ ],
791
+ [
792
+ 5,
793
+ 1,
794
+ 0,
795
+ 9,
796
+ 0,
797
+ "CLIP"
798
+ ],
799
+ [
800
+ 6,
801
+ 10,
802
+ 0,
803
+ 11,
804
+ 0,
805
+ "IMAGE"
806
+ ],
807
+ [
808
+ 7,
809
+ 8,
810
+ 0,
811
+ 12,
812
+ 0,
813
+ "CONDITIONING"
814
+ ],
815
+ [
816
+ 8,
817
+ 9,
818
+ 0,
819
+ 12,
820
+ 1,
821
+ "CONDITIONING"
822
+ ],
823
+ [
824
+ 9,
825
+ 2,
826
+ 0,
827
+ 12,
828
+ 2,
829
+ "VAE"
830
+ ],
831
+ [
832
+ 10,
833
+ 11,
834
+ 0,
835
+ 12,
836
+ 4,
837
+ "IMAGE"
838
+ ],
839
+ [
840
+ 11,
841
+ 11,
842
+ 1,
843
+ 12,
844
+ 5,
845
+ "INT"
846
+ ],
847
+ [
848
+ 12,
849
+ 11,
850
+ 2,
851
+ 12,
852
+ 6,
853
+ "INT"
854
+ ],
855
+ [
856
+ 13,
857
+ 5,
858
+ 0,
859
+ 13,
860
+ 0,
861
+ "MODEL"
862
+ ],
863
+ [
864
+ 14,
865
+ 12,
866
+ 0,
867
+ 13,
868
+ 1,
869
+ "CONDITIONING"
870
+ ],
871
+ [
872
+ 15,
873
+ 12,
874
+ 1,
875
+ 13,
876
+ 2,
877
+ "CONDITIONING"
878
+ ],
879
+ [
880
+ 16,
881
+ 12,
882
+ 2,
883
+ 13,
884
+ 3,
885
+ "LATENT"
886
+ ],
887
+ [
888
+ 17,
889
+ 7,
890
+ 0,
891
+ 14,
892
+ 0,
893
+ "MODEL"
894
+ ],
895
+ [
896
+ 18,
897
+ 12,
898
+ 0,
899
+ 14,
900
+ 1,
901
+ "CONDITIONING"
902
+ ],
903
+ [
904
+ 19,
905
+ 12,
906
+ 1,
907
+ 14,
908
+ 2,
909
+ "CONDITIONING"
910
+ ],
911
+ [
912
+ 20,
913
+ 13,
914
+ 0,
915
+ 14,
916
+ 3,
917
+ "LATENT"
918
+ ],
919
+ [
920
+ 21,
921
+ 14,
922
+ 0,
923
+ 15,
924
+ 0,
925
+ "LATENT"
926
+ ],
927
+ [
928
+ 22,
929
+ 2,
930
+ 0,
931
+ 15,
932
+ 1,
933
+ "VAE"
934
+ ],
935
+ [
936
+ 23,
937
+ 15,
938
+ 0,
939
+ 16,
940
+ 0,
941
+ "IMAGE"
942
+ ]
943
+ ],
944
+ "groups": [],
945
+ "config": {},
946
+ "extra": {},
947
+ "version": 0.4
948
+ }
_archive/workflows/slowmo_scenario2_3ksampler_warmup.json ADDED
@@ -0,0 +1,1141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 20,
3
+ "last_link_id": 29,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 120
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "inputs": [],
20
+ "outputs": [
21
+ {
22
+ "name": "CLIP",
23
+ "type": "CLIP",
24
+ "links": [
25
+ 6,
26
+ 7
27
+ ],
28
+ "slot_index": 0
29
+ }
30
+ ],
31
+ "properties": {
32
+ "Node name for S&R": "CLIPLoader"
33
+ },
34
+ "widgets_values": [
35
+ "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
36
+ "wan",
37
+ "default"
38
+ ]
39
+ },
40
+ {
41
+ "id": 2,
42
+ "type": "VAELoader",
43
+ "pos": [
44
+ 0,
45
+ 160
46
+ ],
47
+ "size": [
48
+ 320,
49
+ 120
50
+ ],
51
+ "flags": {},
52
+ "order": 1,
53
+ "mode": 0,
54
+ "inputs": [],
55
+ "outputs": [
56
+ {
57
+ "name": "VAE",
58
+ "type": "VAE",
59
+ "links": [
60
+ 11,
61
+ 28
62
+ ],
63
+ "slot_index": 0
64
+ }
65
+ ],
66
+ "properties": {
67
+ "Node name for S&R": "VAELoader"
68
+ },
69
+ "widgets_values": [
70
+ "wan_2.1_vae.safetensors"
71
+ ]
72
+ },
73
+ {
74
+ "id": 3,
75
+ "type": "UnetLoaderGGUF",
76
+ "pos": [
77
+ 0,
78
+ 320
79
+ ],
80
+ "size": [
81
+ 320,
82
+ 120
83
+ ],
84
+ "flags": {},
85
+ "order": 2,
86
+ "mode": 0,
87
+ "inputs": [],
88
+ "outputs": [
89
+ {
90
+ "name": "MODEL",
91
+ "type": "MODEL",
92
+ "links": [
93
+ 1,
94
+ 2
95
+ ],
96
+ "slot_index": 0
97
+ }
98
+ ],
99
+ "properties": {
100
+ "Node name for S&R": "UnetLoaderGGUF"
101
+ },
102
+ "widgets_values": [
103
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
104
+ ],
105
+ "title": "High-noise UNet"
106
+ },
107
+ {
108
+ "id": 4,
109
+ "type": "UnetLoaderGGUF",
110
+ "pos": [
111
+ 0,
112
+ 480
113
+ ],
114
+ "size": [
115
+ 320,
116
+ 120
117
+ ],
118
+ "flags": {},
119
+ "order": 3,
120
+ "mode": 0,
121
+ "inputs": [],
122
+ "outputs": [
123
+ {
124
+ "name": "MODEL",
125
+ "type": "MODEL",
126
+ "links": [
127
+ 4
128
+ ],
129
+ "slot_index": 0
130
+ }
131
+ ],
132
+ "properties": {
133
+ "Node name for S&R": "UnetLoaderGGUF"
134
+ },
135
+ "widgets_values": [
136
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
137
+ ],
138
+ "title": "Low-noise UNet"
139
+ },
140
+ {
141
+ "id": 5,
142
+ "type": "ModelSamplingSD3",
143
+ "pos": [
144
+ 360,
145
+ 320
146
+ ],
147
+ "size": [
148
+ 320,
149
+ 120
150
+ ],
151
+ "flags": {},
152
+ "order": 4,
153
+ "mode": 0,
154
+ "inputs": [
155
+ {
156
+ "name": "model",
157
+ "type": "MODEL",
158
+ "link": 1
159
+ }
160
+ ],
161
+ "outputs": [
162
+ {
163
+ "name": "MODEL",
164
+ "type": "MODEL",
165
+ "links": [
166
+ 15
167
+ ],
168
+ "slot_index": 0
169
+ }
170
+ ],
171
+ "properties": {
172
+ "Node name for S&R": "ModelSamplingSD3"
173
+ },
174
+ "widgets_values": [
175
+ 5.0
176
+ ],
177
+ "title": "Stage1: High, NO LoRA (warmup)"
178
+ },
179
+ {
180
+ "id": 6,
181
+ "type": "LoraLoaderModelOnly",
182
+ "pos": [
183
+ 360,
184
+ 480
185
+ ],
186
+ "size": [
187
+ 320,
188
+ 120
189
+ ],
190
+ "flags": {},
191
+ "order": 5,
192
+ "mode": 0,
193
+ "inputs": [
194
+ {
195
+ "name": "model",
196
+ "type": "MODEL",
197
+ "link": 2
198
+ }
199
+ ],
200
+ "outputs": [
201
+ {
202
+ "name": "MODEL",
203
+ "type": "MODEL",
204
+ "links": [
205
+ 3
206
+ ],
207
+ "slot_index": 0
208
+ }
209
+ ],
210
+ "properties": {
211
+ "Node name for S&R": "LoraLoaderModelOnly"
212
+ },
213
+ "widgets_values": [
214
+ "Wan_2_2_I2V_A14B_HIGH_lightx2v_4step_lora_v1030_rank_64_bf16.safetensors",
215
+ 1.0
216
+ ],
217
+ "title": "lightx2v LoRA (HIGH)"
218
+ },
219
+ {
220
+ "id": 7,
221
+ "type": "ModelSamplingSD3",
222
+ "pos": [
223
+ 720,
224
+ 480
225
+ ],
226
+ "size": [
227
+ 320,
228
+ 120
229
+ ],
230
+ "flags": {},
231
+ "order": 6,
232
+ "mode": 0,
233
+ "inputs": [
234
+ {
235
+ "name": "model",
236
+ "type": "MODEL",
237
+ "link": 3
238
+ }
239
+ ],
240
+ "outputs": [
241
+ {
242
+ "name": "MODEL",
243
+ "type": "MODEL",
244
+ "links": [
245
+ 19
246
+ ],
247
+ "slot_index": 0
248
+ }
249
+ ],
250
+ "properties": {
251
+ "Node name for S&R": "ModelSamplingSD3"
252
+ },
253
+ "widgets_values": [
254
+ 5.0
255
+ ],
256
+ "title": "Stage2: High + lightx2v"
257
+ },
258
+ {
259
+ "id": 8,
260
+ "type": "LoraLoaderModelOnly",
261
+ "pos": [
262
+ 360,
263
+ 640
264
+ ],
265
+ "size": [
266
+ 320,
267
+ 120
268
+ ],
269
+ "flags": {},
270
+ "order": 7,
271
+ "mode": 0,
272
+ "inputs": [
273
+ {
274
+ "name": "model",
275
+ "type": "MODEL",
276
+ "link": 4
277
+ }
278
+ ],
279
+ "outputs": [
280
+ {
281
+ "name": "MODEL",
282
+ "type": "MODEL",
283
+ "links": [
284
+ 5
285
+ ],
286
+ "slot_index": 0
287
+ }
288
+ ],
289
+ "properties": {
290
+ "Node name for S&R": "LoraLoaderModelOnly"
291
+ },
292
+ "widgets_values": [
293
+ "wan2.2_i2v_A14b_low_noise_lora_rank64_lightx2v_4step_1022.safetensors",
294
+ 1.0
295
+ ],
296
+ "title": "lightx2v LoRA (LOW)"
297
+ },
298
+ {
299
+ "id": 9,
300
+ "type": "ModelSamplingSD3",
301
+ "pos": [
302
+ 720,
303
+ 640
304
+ ],
305
+ "size": [
306
+ 320,
307
+ 120
308
+ ],
309
+ "flags": {},
310
+ "order": 8,
311
+ "mode": 0,
312
+ "inputs": [
313
+ {
314
+ "name": "model",
315
+ "type": "MODEL",
316
+ "link": 5
317
+ }
318
+ ],
319
+ "outputs": [
320
+ {
321
+ "name": "MODEL",
322
+ "type": "MODEL",
323
+ "links": [
324
+ 23
325
+ ],
326
+ "slot_index": 0
327
+ }
328
+ ],
329
+ "properties": {
330
+ "Node name for S&R": "ModelSamplingSD3"
331
+ },
332
+ "widgets_values": [
333
+ 5.0
334
+ ],
335
+ "title": "Stage3: Low + lightx2v"
336
+ },
337
+ {
338
+ "id": 10,
339
+ "type": "CLIPTextEncode",
340
+ "pos": [
341
+ 720,
342
+ 0
343
+ ],
344
+ "size": [
345
+ 320,
346
+ 120
347
+ ],
348
+ "flags": {},
349
+ "order": 9,
350
+ "mode": 0,
351
+ "inputs": [
352
+ {
353
+ "name": "clip",
354
+ "type": "CLIP",
355
+ "link": 6
356
+ }
357
+ ],
358
+ "outputs": [
359
+ {
360
+ "name": "CONDITIONING",
361
+ "type": "CONDITIONING",
362
+ "links": [
363
+ 9
364
+ ],
365
+ "slot_index": 0
366
+ }
367
+ ],
368
+ "properties": {
369
+ "Node name for S&R": "CLIPTextEncode"
370
+ },
371
+ "widgets_values": [
372
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
373
+ ],
374
+ "title": "Positive prompt"
375
+ },
376
+ {
377
+ "id": 11,
378
+ "type": "CLIPTextEncode",
379
+ "pos": [
380
+ 720,
381
+ 160
382
+ ],
383
+ "size": [
384
+ 320,
385
+ 120
386
+ ],
387
+ "flags": {},
388
+ "order": 10,
389
+ "mode": 0,
390
+ "inputs": [
391
+ {
392
+ "name": "clip",
393
+ "type": "CLIP",
394
+ "link": 7
395
+ }
396
+ ],
397
+ "outputs": [
398
+ {
399
+ "name": "CONDITIONING",
400
+ "type": "CONDITIONING",
401
+ "links": [
402
+ 10
403
+ ],
404
+ "slot_index": 0
405
+ }
406
+ ],
407
+ "properties": {
408
+ "Node name for S&R": "CLIPTextEncode"
409
+ },
410
+ "widgets_values": [
411
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
412
+ ],
413
+ "title": "Negative prompt"
414
+ },
415
+ {
416
+ "id": 12,
417
+ "type": "LoadImage",
418
+ "pos": [
419
+ 720,
420
+ 340
421
+ ],
422
+ "size": [
423
+ 320,
424
+ 320
425
+ ],
426
+ "flags": {},
427
+ "order": 11,
428
+ "mode": 0,
429
+ "inputs": [],
430
+ "outputs": [
431
+ {
432
+ "name": "IMAGE",
433
+ "type": "IMAGE",
434
+ "links": [
435
+ 8
436
+ ],
437
+ "slot_index": 0
438
+ },
439
+ {
440
+ "name": "MASK",
441
+ "type": "MASK",
442
+ "links": [],
443
+ "slot_index": 1
444
+ }
445
+ ],
446
+ "properties": {
447
+ "Node name for S&R": "LoadImage"
448
+ },
449
+ "widgets_values": [
450
+ "input.png",
451
+ "image"
452
+ ]
453
+ },
454
+ {
455
+ "id": 13,
456
+ "type": "WanNativeResize_Dolphin",
457
+ "pos": [
458
+ 1080,
459
+ 340
460
+ ],
461
+ "size": [
462
+ 320,
463
+ 120
464
+ ],
465
+ "flags": {},
466
+ "order": 12,
467
+ "mode": 0,
468
+ "inputs": [
469
+ {
470
+ "name": "image",
471
+ "type": "IMAGE",
472
+ "link": 8
473
+ }
474
+ ],
475
+ "outputs": [
476
+ {
477
+ "name": "IMAGE",
478
+ "type": "IMAGE",
479
+ "links": [
480
+ 12
481
+ ],
482
+ "slot_index": 0
483
+ },
484
+ {
485
+ "name": "width",
486
+ "type": "INT",
487
+ "links": [
488
+ 13
489
+ ],
490
+ "slot_index": 1
491
+ },
492
+ {
493
+ "name": "height",
494
+ "type": "INT",
495
+ "links": [
496
+ 14
497
+ ],
498
+ "slot_index": 2
499
+ }
500
+ ],
501
+ "properties": {
502
+ "Node name for S&R": "WanNativeResize_Dolphin"
503
+ },
504
+ "widgets_values": [
505
+ "720P (1280x720)",
506
+ "lanczos",
507
+ 16
508
+ ]
509
+ },
510
+ {
511
+ "id": 14,
512
+ "type": "WanImageToVideo",
513
+ "pos": [
514
+ 1480,
515
+ 160
516
+ ],
517
+ "size": [
518
+ 320,
519
+ 220
520
+ ],
521
+ "flags": {},
522
+ "order": 13,
523
+ "mode": 0,
524
+ "inputs": [
525
+ {
526
+ "name": "positive",
527
+ "type": "CONDITIONING",
528
+ "link": 9
529
+ },
530
+ {
531
+ "name": "negative",
532
+ "type": "CONDITIONING",
533
+ "link": 10
534
+ },
535
+ {
536
+ "name": "vae",
537
+ "type": "VAE",
538
+ "link": 11
539
+ },
540
+ {
541
+ "name": "clip_vision_output",
542
+ "type": "CLIP_VISION_OUTPUT",
543
+ "link": null,
544
+ "shape": 7
545
+ },
546
+ {
547
+ "name": "start_image",
548
+ "type": "IMAGE",
549
+ "link": 12,
550
+ "shape": 7
551
+ },
552
+ {
553
+ "name": "width",
554
+ "type": "INT",
555
+ "link": 13,
556
+ "widget": {
557
+ "name": "width"
558
+ }
559
+ },
560
+ {
561
+ "name": "height",
562
+ "type": "INT",
563
+ "link": 14,
564
+ "widget": {
565
+ "name": "height"
566
+ }
567
+ }
568
+ ],
569
+ "outputs": [
570
+ {
571
+ "name": "positive",
572
+ "type": "CONDITIONING",
573
+ "links": [
574
+ 16,
575
+ 20,
576
+ 24
577
+ ],
578
+ "slot_index": 0
579
+ },
580
+ {
581
+ "name": "negative",
582
+ "type": "CONDITIONING",
583
+ "links": [
584
+ 17,
585
+ 21,
586
+ 25
587
+ ],
588
+ "slot_index": 1
589
+ },
590
+ {
591
+ "name": "latent",
592
+ "type": "LATENT",
593
+ "links": [
594
+ 18
595
+ ],
596
+ "slot_index": 2
597
+ }
598
+ ],
599
+ "properties": {
600
+ "Node name for S&R": "WanImageToVideo"
601
+ },
602
+ "widgets_values": [
603
+ 1280,
604
+ 720,
605
+ 81,
606
+ 1
607
+ ]
608
+ },
609
+ {
610
+ "id": 15,
611
+ "type": "KSamplerAdvanced",
612
+ "pos": [
613
+ 1900,
614
+ 120
615
+ ],
616
+ "size": [
617
+ 320,
618
+ 260
619
+ ],
620
+ "flags": {},
621
+ "order": 14,
622
+ "mode": 0,
623
+ "inputs": [
624
+ {
625
+ "name": "model",
626
+ "type": "MODEL",
627
+ "link": 15
628
+ },
629
+ {
630
+ "name": "positive",
631
+ "type": "CONDITIONING",
632
+ "link": 16
633
+ },
634
+ {
635
+ "name": "negative",
636
+ "type": "CONDITIONING",
637
+ "link": 17
638
+ },
639
+ {
640
+ "name": "latent_image",
641
+ "type": "LATENT",
642
+ "link": 18
643
+ }
644
+ ],
645
+ "outputs": [
646
+ {
647
+ "name": "LATENT",
648
+ "type": "LATENT",
649
+ "links": [
650
+ 22
651
+ ],
652
+ "slot_index": 0
653
+ }
654
+ ],
655
+ "properties": {
656
+ "Node name for S&R": "KSamplerAdvanced"
657
+ },
658
+ "widgets_values": [
659
+ "enable",
660
+ 555222333111,
661
+ "fixed",
662
+ 9,
663
+ 3.5,
664
+ "euler",
665
+ "simple",
666
+ 0,
667
+ 1,
668
+ "enable"
669
+ ],
670
+ "title": "Stage1 - no LoRA, CFG 3.5, 1/9 step (motion warmup)"
671
+ },
672
+ {
673
+ "id": 16,
674
+ "type": "KSamplerAdvanced",
675
+ "pos": [
676
+ 2260,
677
+ 120
678
+ ],
679
+ "size": [
680
+ 320,
681
+ 260
682
+ ],
683
+ "flags": {},
684
+ "order": 15,
685
+ "mode": 0,
686
+ "inputs": [
687
+ {
688
+ "name": "model",
689
+ "type": "MODEL",
690
+ "link": 19
691
+ },
692
+ {
693
+ "name": "positive",
694
+ "type": "CONDITIONING",
695
+ "link": 20
696
+ },
697
+ {
698
+ "name": "negative",
699
+ "type": "CONDITIONING",
700
+ "link": 21
701
+ },
702
+ {
703
+ "name": "latent_image",
704
+ "type": "LATENT",
705
+ "link": 22
706
+ }
707
+ ],
708
+ "outputs": [
709
+ {
710
+ "name": "LATENT",
711
+ "type": "LATENT",
712
+ "links": [
713
+ 26
714
+ ],
715
+ "slot_index": 0
716
+ }
717
+ ],
718
+ "properties": {
719
+ "Node name for S&R": "KSamplerAdvanced"
720
+ },
721
+ "widgets_values": [
722
+ "disable",
723
+ 555222333111,
724
+ "fixed",
725
+ 9,
726
+ 1.0,
727
+ "euler",
728
+ "simple",
729
+ 1,
730
+ 5,
731
+ "enable"
732
+ ],
733
+ "title": "Stage2 - High+lightx2v, CFG 1, steps 1->5 of 9"
734
+ },
735
+ {
736
+ "id": 17,
737
+ "type": "KSamplerAdvanced",
738
+ "pos": [
739
+ 2620,
740
+ 120
741
+ ],
742
+ "size": [
743
+ 320,
744
+ 260
745
+ ],
746
+ "flags": {},
747
+ "order": 16,
748
+ "mode": 0,
749
+ "inputs": [
750
+ {
751
+ "name": "model",
752
+ "type": "MODEL",
753
+ "link": 23
754
+ },
755
+ {
756
+ "name": "positive",
757
+ "type": "CONDITIONING",
758
+ "link": 24
759
+ },
760
+ {
761
+ "name": "negative",
762
+ "type": "CONDITIONING",
763
+ "link": 25
764
+ },
765
+ {
766
+ "name": "latent_image",
767
+ "type": "LATENT",
768
+ "link": 26
769
+ }
770
+ ],
771
+ "outputs": [
772
+ {
773
+ "name": "LATENT",
774
+ "type": "LATENT",
775
+ "links": [
776
+ 27
777
+ ],
778
+ "slot_index": 0
779
+ }
780
+ ],
781
+ "properties": {
782
+ "Node name for S&R": "KSamplerAdvanced"
783
+ },
784
+ "widgets_values": [
785
+ "disable",
786
+ 555222333111,
787
+ "fixed",
788
+ 9,
789
+ 1.0,
790
+ "euler",
791
+ "simple",
792
+ 5,
793
+ 9,
794
+ "disable"
795
+ ],
796
+ "title": "Stage3 - Low+lightx2v, CFG 1, steps 5->9 of 9"
797
+ },
798
+ {
799
+ "id": 18,
800
+ "type": "VAEDecode",
801
+ "pos": [
802
+ 2980,
803
+ 120
804
+ ],
805
+ "size": [
806
+ 320,
807
+ 120
808
+ ],
809
+ "flags": {},
810
+ "order": 17,
811
+ "mode": 0,
812
+ "inputs": [
813
+ {
814
+ "name": "samples",
815
+ "type": "LATENT",
816
+ "link": 27
817
+ },
818
+ {
819
+ "name": "vae",
820
+ "type": "VAE",
821
+ "link": 28
822
+ }
823
+ ],
824
+ "outputs": [
825
+ {
826
+ "name": "IMAGE",
827
+ "type": "IMAGE",
828
+ "links": [
829
+ 29
830
+ ],
831
+ "slot_index": 0
832
+ }
833
+ ],
834
+ "properties": {
835
+ "Node name for S&R": "VAEDecode"
836
+ },
837
+ "widgets_values": []
838
+ },
839
+ {
840
+ "id": 19,
841
+ "type": "VHS_VideoCombine",
842
+ "pos": [
843
+ 3340,
844
+ 120
845
+ ],
846
+ "size": [
847
+ 320,
848
+ 260
849
+ ],
850
+ "flags": {},
851
+ "order": 18,
852
+ "mode": 0,
853
+ "inputs": [
854
+ {
855
+ "name": "images",
856
+ "type": "IMAGE",
857
+ "link": 29
858
+ }
859
+ ],
860
+ "outputs": [],
861
+ "properties": {
862
+ "Node name for S&R": "VHS_VideoCombine"
863
+ },
864
+ "widgets_values": {
865
+ "frame_rate": 16,
866
+ "loop_count": 0,
867
+ "filename_prefix": "wan22_slowmo/3ksampler_warmup",
868
+ "format": "video/h264-mp4",
869
+ "pingpong": false,
870
+ "save_image": true,
871
+ "crf": 19,
872
+ "save_metadata": true,
873
+ "audio_file": ""
874
+ }
875
+ },
876
+ {
877
+ "id": 20,
878
+ "type": "Note",
879
+ "pos": [
880
+ 2980,
881
+ 440
882
+ ],
883
+ "size": [
884
+ 900,
885
+ 260
886
+ ],
887
+ "flags": {},
888
+ "order": 19,
889
+ "mode": 0,
890
+ "inputs": [],
891
+ "outputs": [],
892
+ "properties": {
893
+ "Node name for S&R": "Note"
894
+ },
895
+ "widgets_values": [
896
+ "시나리오: 3-KSampler 워밍업 구조.\n(1) LoRA 없이 CFG3.5 1스텝(모션 방향 예열) -> (2) High+lightx2v CFG1 4스텝 -> (3) Low+lightx2v CFG1 4스텝.\n총 steps=9(1+4+4)를 세 KSamplerAdvanced가 나눠서 하나의 연속된 노이즈 스케줄로 처리.\n튜닝 포인트: Stage2/3의 폭(2~4 steps 권장 범위)을 조절, 필요시 총 steps도 함께 변경. 시드는 세 노드 동일(555222333111).\nmemory 근거: wan_lightx2v_workarounds.md 참고 — 이 구조는 채택된 'Hi=CFG3.5 no-lightx2v / Lo=lightx2v only' 조합보다 덜 선호되지만 대안으로 남겨둠."
897
+ ],
898
+ "title": "Scenario 2: 3-KSampler warmup",
899
+ "color": "#432",
900
+ "bgcolor": "#653"
901
+ }
902
+ ],
903
+ "links": [
904
+ [
905
+ 1,
906
+ 3,
907
+ 0,
908
+ 5,
909
+ 0,
910
+ "MODEL"
911
+ ],
912
+ [
913
+ 2,
914
+ 3,
915
+ 0,
916
+ 6,
917
+ 0,
918
+ "MODEL"
919
+ ],
920
+ [
921
+ 3,
922
+ 6,
923
+ 0,
924
+ 7,
925
+ 0,
926
+ "MODEL"
927
+ ],
928
+ [
929
+ 4,
930
+ 4,
931
+ 0,
932
+ 8,
933
+ 0,
934
+ "MODEL"
935
+ ],
936
+ [
937
+ 5,
938
+ 8,
939
+ 0,
940
+ 9,
941
+ 0,
942
+ "MODEL"
943
+ ],
944
+ [
945
+ 6,
946
+ 1,
947
+ 0,
948
+ 10,
949
+ 0,
950
+ "CLIP"
951
+ ],
952
+ [
953
+ 7,
954
+ 1,
955
+ 0,
956
+ 11,
957
+ 0,
958
+ "CLIP"
959
+ ],
960
+ [
961
+ 8,
962
+ 12,
963
+ 0,
964
+ 13,
965
+ 0,
966
+ "IMAGE"
967
+ ],
968
+ [
969
+ 9,
970
+ 10,
971
+ 0,
972
+ 14,
973
+ 0,
974
+ "CONDITIONING"
975
+ ],
976
+ [
977
+ 10,
978
+ 11,
979
+ 0,
980
+ 14,
981
+ 1,
982
+ "CONDITIONING"
983
+ ],
984
+ [
985
+ 11,
986
+ 2,
987
+ 0,
988
+ 14,
989
+ 2,
990
+ "VAE"
991
+ ],
992
+ [
993
+ 12,
994
+ 13,
995
+ 0,
996
+ 14,
997
+ 4,
998
+ "IMAGE"
999
+ ],
1000
+ [
1001
+ 13,
1002
+ 13,
1003
+ 1,
1004
+ 14,
1005
+ 5,
1006
+ "INT"
1007
+ ],
1008
+ [
1009
+ 14,
1010
+ 13,
1011
+ 2,
1012
+ 14,
1013
+ 6,
1014
+ "INT"
1015
+ ],
1016
+ [
1017
+ 15,
1018
+ 5,
1019
+ 0,
1020
+ 15,
1021
+ 0,
1022
+ "MODEL"
1023
+ ],
1024
+ [
1025
+ 16,
1026
+ 14,
1027
+ 0,
1028
+ 15,
1029
+ 1,
1030
+ "CONDITIONING"
1031
+ ],
1032
+ [
1033
+ 17,
1034
+ 14,
1035
+ 1,
1036
+ 15,
1037
+ 2,
1038
+ "CONDITIONING"
1039
+ ],
1040
+ [
1041
+ 18,
1042
+ 14,
1043
+ 2,
1044
+ 15,
1045
+ 3,
1046
+ "LATENT"
1047
+ ],
1048
+ [
1049
+ 19,
1050
+ 7,
1051
+ 0,
1052
+ 16,
1053
+ 0,
1054
+ "MODEL"
1055
+ ],
1056
+ [
1057
+ 20,
1058
+ 14,
1059
+ 0,
1060
+ 16,
1061
+ 1,
1062
+ "CONDITIONING"
1063
+ ],
1064
+ [
1065
+ 21,
1066
+ 14,
1067
+ 1,
1068
+ 16,
1069
+ 2,
1070
+ "CONDITIONING"
1071
+ ],
1072
+ [
1073
+ 22,
1074
+ 15,
1075
+ 0,
1076
+ 16,
1077
+ 3,
1078
+ "LATENT"
1079
+ ],
1080
+ [
1081
+ 23,
1082
+ 9,
1083
+ 0,
1084
+ 17,
1085
+ 0,
1086
+ "MODEL"
1087
+ ],
1088
+ [
1089
+ 24,
1090
+ 14,
1091
+ 0,
1092
+ 17,
1093
+ 1,
1094
+ "CONDITIONING"
1095
+ ],
1096
+ [
1097
+ 25,
1098
+ 14,
1099
+ 1,
1100
+ 17,
1101
+ 2,
1102
+ "CONDITIONING"
1103
+ ],
1104
+ [
1105
+ 26,
1106
+ 16,
1107
+ 0,
1108
+ 17,
1109
+ 3,
1110
+ "LATENT"
1111
+ ],
1112
+ [
1113
+ 27,
1114
+ 17,
1115
+ 0,
1116
+ 18,
1117
+ 0,
1118
+ "LATENT"
1119
+ ],
1120
+ [
1121
+ 28,
1122
+ 2,
1123
+ 0,
1124
+ 18,
1125
+ 1,
1126
+ "VAE"
1127
+ ],
1128
+ [
1129
+ 29,
1130
+ 18,
1131
+ 0,
1132
+ 19,
1133
+ 0,
1134
+ "IMAGE"
1135
+ ]
1136
+ ],
1137
+ "groups": [],
1138
+ "config": {},
1139
+ "extra": {},
1140
+ "version": 0.4
1141
+ }
_archive/workflows/slowmo_svi_scenarioA_2clip_continuation.json ADDED
@@ -0,0 +1,1737 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 30,
3
+ "last_link_id": 47,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 120
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "inputs": [],
20
+ "outputs": [
21
+ {
22
+ "name": "CLIP",
23
+ "type": "CLIP",
24
+ "links": [
25
+ 7,
26
+ 8,
27
+ 30,
28
+ 31
29
+ ],
30
+ "slot_index": 0
31
+ }
32
+ ],
33
+ "properties": {
34
+ "Node name for S&R": "CLIPLoader"
35
+ },
36
+ "widgets_values": [
37
+ "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
38
+ "wan",
39
+ "default"
40
+ ]
41
+ },
42
+ {
43
+ "id": 2,
44
+ "type": "VAELoader",
45
+ "pos": [
46
+ 0,
47
+ 160
48
+ ],
49
+ "size": [
50
+ 320,
51
+ 120
52
+ ],
53
+ "flags": {},
54
+ "order": 1,
55
+ "mode": 0,
56
+ "inputs": [],
57
+ "outputs": [
58
+ {
59
+ "name": "VAE",
60
+ "type": "VAE",
61
+ "links": [
62
+ 11,
63
+ 14,
64
+ 25,
65
+ 29,
66
+ 34,
67
+ 46
68
+ ],
69
+ "slot_index": 0
70
+ }
71
+ ],
72
+ "properties": {
73
+ "Node name for S&R": "VAELoader"
74
+ },
75
+ "widgets_values": [
76
+ "wan_2.1_vae.safetensors"
77
+ ]
78
+ },
79
+ {
80
+ "id": 3,
81
+ "type": "UnetLoaderGGUF",
82
+ "pos": [
83
+ 0,
84
+ 320
85
+ ],
86
+ "size": [
87
+ 320,
88
+ 120
89
+ ],
90
+ "flags": {},
91
+ "order": 2,
92
+ "mode": 0,
93
+ "inputs": [],
94
+ "outputs": [
95
+ {
96
+ "name": "MODEL",
97
+ "type": "MODEL",
98
+ "links": [
99
+ 1
100
+ ],
101
+ "slot_index": 0
102
+ }
103
+ ],
104
+ "properties": {
105
+ "Node name for S&R": "UnetLoaderGGUF"
106
+ },
107
+ "widgets_values": [
108
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
109
+ ],
110
+ "title": "High-noise UNet"
111
+ },
112
+ {
113
+ "id": 4,
114
+ "type": "UnetLoaderGGUF",
115
+ "pos": [
116
+ 0,
117
+ 480
118
+ ],
119
+ "size": [
120
+ 320,
121
+ 120
122
+ ],
123
+ "flags": {},
124
+ "order": 3,
125
+ "mode": 0,
126
+ "inputs": [],
127
+ "outputs": [
128
+ {
129
+ "name": "MODEL",
130
+ "type": "MODEL",
131
+ "links": [
132
+ 4
133
+ ],
134
+ "slot_index": 0
135
+ }
136
+ ],
137
+ "properties": {
138
+ "Node name for S&R": "UnetLoaderGGUF"
139
+ },
140
+ "widgets_values": [
141
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
142
+ ],
143
+ "title": "Low-noise UNet"
144
+ },
145
+ {
146
+ "id": 5,
147
+ "type": "LoraLoaderModelOnly",
148
+ "pos": [
149
+ 360,
150
+ 320
151
+ ],
152
+ "size": [
153
+ 320,
154
+ 120
155
+ ],
156
+ "flags": {},
157
+ "order": 4,
158
+ "mode": 0,
159
+ "inputs": [
160
+ {
161
+ "name": "model",
162
+ "type": "MODEL",
163
+ "link": 1
164
+ }
165
+ ],
166
+ "outputs": [
167
+ {
168
+ "name": "MODEL",
169
+ "type": "MODEL",
170
+ "links": [
171
+ 2
172
+ ],
173
+ "slot_index": 0
174
+ }
175
+ ],
176
+ "properties": {
177
+ "Node name for S&R": "LoraLoaderModelOnly"
178
+ },
179
+ "widgets_values": [
180
+ "SVI/SVI_Wan2.2-I2V-A14B_high_noise_lora_v2.0.safetensors",
181
+ 1.0
182
+ ],
183
+ "title": "SVI LoRA (HIGH, strength 1.0)"
184
+ },
185
+ {
186
+ "id": 6,
187
+ "type": "LoraLoaderModelOnly",
188
+ "pos": [
189
+ 720,
190
+ 320
191
+ ],
192
+ "size": [
193
+ 320,
194
+ 120
195
+ ],
196
+ "flags": {},
197
+ "order": 5,
198
+ "mode": 0,
199
+ "inputs": [
200
+ {
201
+ "name": "model",
202
+ "type": "MODEL",
203
+ "link": 2
204
+ }
205
+ ],
206
+ "outputs": [
207
+ {
208
+ "name": "MODEL",
209
+ "type": "MODEL",
210
+ "links": [
211
+ 3
212
+ ],
213
+ "slot_index": 0
214
+ }
215
+ ],
216
+ "properties": {
217
+ "Node name for S&R": "LoraLoaderModelOnly"
218
+ },
219
+ "widgets_values": [
220
+ "Wan_2_2_I2V_A14B_HIGH_lightx2v_4step_lora_v1030_rank_64_bf16.safetensors",
221
+ 0.5
222
+ ],
223
+ "title": "lightx2v LoRA (HIGH, strength 0.5)"
224
+ },
225
+ {
226
+ "id": 7,
227
+ "type": "ModelSamplingSD3",
228
+ "pos": [
229
+ 1080,
230
+ 320
231
+ ],
232
+ "size": [
233
+ 320,
234
+ 120
235
+ ],
236
+ "flags": {},
237
+ "order": 6,
238
+ "mode": 0,
239
+ "inputs": [
240
+ {
241
+ "name": "model",
242
+ "type": "MODEL",
243
+ "link": 3
244
+ }
245
+ ],
246
+ "outputs": [
247
+ {
248
+ "name": "MODEL",
249
+ "type": "MODEL",
250
+ "links": [
251
+ 16,
252
+ 37
253
+ ],
254
+ "slot_index": 0
255
+ }
256
+ ],
257
+ "properties": {
258
+ "Node name for S&R": "ModelSamplingSD3"
259
+ },
260
+ "widgets_values": [
261
+ 5.0
262
+ ],
263
+ "title": "High noise (SVI 1.0 + lightx2v 0.5) - shared"
264
+ },
265
+ {
266
+ "id": 8,
267
+ "type": "LoraLoaderModelOnly",
268
+ "pos": [
269
+ 360,
270
+ 480
271
+ ],
272
+ "size": [
273
+ 320,
274
+ 120
275
+ ],
276
+ "flags": {},
277
+ "order": 7,
278
+ "mode": 0,
279
+ "inputs": [
280
+ {
281
+ "name": "model",
282
+ "type": "MODEL",
283
+ "link": 4
284
+ }
285
+ ],
286
+ "outputs": [
287
+ {
288
+ "name": "MODEL",
289
+ "type": "MODEL",
290
+ "links": [
291
+ 5
292
+ ],
293
+ "slot_index": 0
294
+ }
295
+ ],
296
+ "properties": {
297
+ "Node name for S&R": "LoraLoaderModelOnly"
298
+ },
299
+ "widgets_values": [
300
+ "SVI/SVI_Wan2.2-I2V-A14B_low_noise_lora_v2.0.safetensors",
301
+ 1.0
302
+ ],
303
+ "title": "SVI LoRA (LOW, strength 1.0)"
304
+ },
305
+ {
306
+ "id": 9,
307
+ "type": "LoraLoaderModelOnly",
308
+ "pos": [
309
+ 720,
310
+ 480
311
+ ],
312
+ "size": [
313
+ 320,
314
+ 120
315
+ ],
316
+ "flags": {},
317
+ "order": 8,
318
+ "mode": 0,
319
+ "inputs": [
320
+ {
321
+ "name": "model",
322
+ "type": "MODEL",
323
+ "link": 5
324
+ }
325
+ ],
326
+ "outputs": [
327
+ {
328
+ "name": "MODEL",
329
+ "type": "MODEL",
330
+ "links": [
331
+ 6
332
+ ],
333
+ "slot_index": 0
334
+ }
335
+ ],
336
+ "properties": {
337
+ "Node name for S&R": "LoraLoaderModelOnly"
338
+ },
339
+ "widgets_values": [
340
+ "wan2.2_i2v_A14b_low_noise_lora_rank64_lightx2v_4step_1022.safetensors",
341
+ 1.0
342
+ ],
343
+ "title": "lightx2v LoRA (LOW, strength 1.0)"
344
+ },
345
+ {
346
+ "id": 10,
347
+ "type": "ModelSamplingSD3",
348
+ "pos": [
349
+ 1080,
350
+ 480
351
+ ],
352
+ "size": [
353
+ 320,
354
+ 120
355
+ ],
356
+ "flags": {},
357
+ "order": 9,
358
+ "mode": 0,
359
+ "inputs": [
360
+ {
361
+ "name": "model",
362
+ "type": "MODEL",
363
+ "link": 6
364
+ }
365
+ ],
366
+ "outputs": [
367
+ {
368
+ "name": "MODEL",
369
+ "type": "MODEL",
370
+ "links": [
371
+ 20,
372
+ 41
373
+ ],
374
+ "slot_index": 0
375
+ }
376
+ ],
377
+ "properties": {
378
+ "Node name for S&R": "ModelSamplingSD3"
379
+ },
380
+ "widgets_values": [
381
+ 5.0
382
+ ],
383
+ "title": "Low noise (SVI 1.0 + lightx2v 1.0) - shared"
384
+ },
385
+ {
386
+ "id": 11,
387
+ "type": "CLIPTextEncode",
388
+ "pos": [
389
+ 1080,
390
+ 0
391
+ ],
392
+ "size": [
393
+ 320,
394
+ 120
395
+ ],
396
+ "flags": {},
397
+ "order": 10,
398
+ "mode": 0,
399
+ "inputs": [
400
+ {
401
+ "name": "clip",
402
+ "type": "CLIP",
403
+ "link": 7
404
+ }
405
+ ],
406
+ "outputs": [
407
+ {
408
+ "name": "CONDITIONING",
409
+ "type": "CONDITIONING",
410
+ "links": [
411
+ 12
412
+ ],
413
+ "slot_index": 0
414
+ }
415
+ ],
416
+ "properties": {
417
+ "Node name for S&R": "CLIPTextEncode"
418
+ },
419
+ "widgets_values": [
420
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
421
+ ],
422
+ "title": "Clip1 Positive prompt"
423
+ },
424
+ {
425
+ "id": 12,
426
+ "type": "CLIPTextEncode",
427
+ "pos": [
428
+ 1080,
429
+ 160
430
+ ],
431
+ "size": [
432
+ 320,
433
+ 120
434
+ ],
435
+ "flags": {},
436
+ "order": 11,
437
+ "mode": 0,
438
+ "inputs": [
439
+ {
440
+ "name": "clip",
441
+ "type": "CLIP",
442
+ "link": 8
443
+ }
444
+ ],
445
+ "outputs": [
446
+ {
447
+ "name": "CONDITIONING",
448
+ "type": "CONDITIONING",
449
+ "links": [
450
+ 13
451
+ ],
452
+ "slot_index": 0
453
+ }
454
+ ],
455
+ "properties": {
456
+ "Node name for S&R": "CLIPTextEncode"
457
+ },
458
+ "widgets_values": [
459
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
460
+ ],
461
+ "title": "Clip1 Negative prompt"
462
+ },
463
+ {
464
+ "id": 13,
465
+ "type": "LoadImage",
466
+ "pos": [
467
+ 1080,
468
+ 660
469
+ ],
470
+ "size": [
471
+ 320,
472
+ 320
473
+ ],
474
+ "flags": {},
475
+ "order": 12,
476
+ "mode": 0,
477
+ "inputs": [],
478
+ "outputs": [
479
+ {
480
+ "name": "IMAGE",
481
+ "type": "IMAGE",
482
+ "links": [
483
+ 9
484
+ ],
485
+ "slot_index": 0
486
+ },
487
+ {
488
+ "name": "MASK",
489
+ "type": "MASK",
490
+ "links": [],
491
+ "slot_index": 1
492
+ }
493
+ ],
494
+ "properties": {
495
+ "Node name for S&R": "LoadImage"
496
+ },
497
+ "widgets_values": [
498
+ "input.png",
499
+ "image"
500
+ ]
501
+ },
502
+ {
503
+ "id": 14,
504
+ "type": "WanNativeResize_Dolphin",
505
+ "pos": [
506
+ 1440,
507
+ 660
508
+ ],
509
+ "size": [
510
+ 320,
511
+ 120
512
+ ],
513
+ "flags": {},
514
+ "order": 13,
515
+ "mode": 0,
516
+ "inputs": [
517
+ {
518
+ "name": "image",
519
+ "type": "IMAGE",
520
+ "link": 9
521
+ }
522
+ ],
523
+ "outputs": [
524
+ {
525
+ "name": "IMAGE",
526
+ "type": "IMAGE",
527
+ "links": [
528
+ 10
529
+ ],
530
+ "slot_index": 0
531
+ },
532
+ {
533
+ "name": "width",
534
+ "type": "INT",
535
+ "links": [],
536
+ "slot_index": 1
537
+ },
538
+ {
539
+ "name": "height",
540
+ "type": "INT",
541
+ "links": [],
542
+ "slot_index": 2
543
+ }
544
+ ],
545
+ "properties": {
546
+ "Node name for S&R": "WanNativeResize_Dolphin"
547
+ },
548
+ "widgets_values": [
549
+ "480P (832x480)",
550
+ "lanczos",
551
+ 16
552
+ ]
553
+ },
554
+ {
555
+ "id": 15,
556
+ "type": "VAEEncode",
557
+ "pos": [
558
+ 1840,
559
+ 660
560
+ ],
561
+ "size": [
562
+ 320,
563
+ 120
564
+ ],
565
+ "flags": {},
566
+ "order": 14,
567
+ "mode": 0,
568
+ "inputs": [
569
+ {
570
+ "name": "pixels",
571
+ "type": "IMAGE",
572
+ "link": 10
573
+ },
574
+ {
575
+ "name": "vae",
576
+ "type": "VAE",
577
+ "link": 11
578
+ }
579
+ ],
580
+ "outputs": [
581
+ {
582
+ "name": "LATENT",
583
+ "type": "LATENT",
584
+ "links": [
585
+ 15
586
+ ],
587
+ "slot_index": 0
588
+ }
589
+ ],
590
+ "properties": {
591
+ "Node name for S&R": "VAEEncode"
592
+ },
593
+ "widgets_values": [],
594
+ "title": "Clip1 anchor encode"
595
+ },
596
+ {
597
+ "id": 16,
598
+ "type": "WanImageToVideoSVIProGray_Dolphin",
599
+ "pos": [
600
+ 2240,
601
+ 160
602
+ ],
603
+ "size": [
604
+ 340,
605
+ 220
606
+ ],
607
+ "flags": {},
608
+ "order": 15,
609
+ "mode": 0,
610
+ "inputs": [
611
+ {
612
+ "name": "positive",
613
+ "type": "CONDITIONING",
614
+ "link": 12
615
+ },
616
+ {
617
+ "name": "negative",
618
+ "type": "CONDITIONING",
619
+ "link": 13
620
+ },
621
+ {
622
+ "name": "vae",
623
+ "type": "VAE",
624
+ "link": 14
625
+ },
626
+ {
627
+ "name": "anchor_samples",
628
+ "type": "LATENT",
629
+ "link": 15
630
+ },
631
+ {
632
+ "name": "prev_samples",
633
+ "type": "LATENT",
634
+ "link": null,
635
+ "shape": 7
636
+ }
637
+ ],
638
+ "outputs": [
639
+ {
640
+ "name": "positive",
641
+ "type": "CONDITIONING",
642
+ "links": [
643
+ 17,
644
+ 21
645
+ ],
646
+ "slot_index": 0
647
+ },
648
+ {
649
+ "name": "negative",
650
+ "type": "CONDITIONING",
651
+ "links": [
652
+ 18,
653
+ 22
654
+ ],
655
+ "slot_index": 1
656
+ },
657
+ {
658
+ "name": "latent",
659
+ "type": "LATENT",
660
+ "links": [
661
+ 19
662
+ ],
663
+ "slot_index": 2
664
+ }
665
+ ],
666
+ "properties": {
667
+ "Node name for S&R": "WanImageToVideoSVIProGray_Dolphin"
668
+ },
669
+ "widgets_values": [
670
+ 81,
671
+ 0
672
+ ],
673
+ "title": "Clip1: SVIProGray (cold start, no prev_samples - unavoidable for clip 1)"
674
+ },
675
+ {
676
+ "id": 17,
677
+ "type": "KSamplerAdvanced",
678
+ "pos": [
679
+ 2640,
680
+ 160
681
+ ],
682
+ "size": [
683
+ 320,
684
+ 260
685
+ ],
686
+ "flags": {},
687
+ "order": 16,
688
+ "mode": 0,
689
+ "inputs": [
690
+ {
691
+ "name": "model",
692
+ "type": "MODEL",
693
+ "link": 16
694
+ },
695
+ {
696
+ "name": "positive",
697
+ "type": "CONDITIONING",
698
+ "link": 17
699
+ },
700
+ {
701
+ "name": "negative",
702
+ "type": "CONDITIONING",
703
+ "link": 18
704
+ },
705
+ {
706
+ "name": "latent_image",
707
+ "type": "LATENT",
708
+ "link": 19
709
+ }
710
+ ],
711
+ "outputs": [
712
+ {
713
+ "name": "LATENT",
714
+ "type": "LATENT",
715
+ "links": [
716
+ 23
717
+ ],
718
+ "slot_index": 0
719
+ }
720
+ ],
721
+ "properties": {
722
+ "Node name for S&R": "KSamplerAdvanced"
723
+ },
724
+ "widgets_values": [
725
+ "enable",
726
+ 424242424242,
727
+ "fixed",
728
+ 8,
729
+ 1.5,
730
+ "euler",
731
+ "simple",
732
+ 0,
733
+ 4,
734
+ "enable"
735
+ ],
736
+ "title": "Clip1 HIGH - CFG1.5, 0->4 of 8"
737
+ },
738
+ {
739
+ "id": 18,
740
+ "type": "KSamplerAdvanced",
741
+ "pos": [
742
+ 3000,
743
+ 160
744
+ ],
745
+ "size": [
746
+ 320,
747
+ 260
748
+ ],
749
+ "flags": {},
750
+ "order": 17,
751
+ "mode": 0,
752
+ "inputs": [
753
+ {
754
+ "name": "model",
755
+ "type": "MODEL",
756
+ "link": 20
757
+ },
758
+ {
759
+ "name": "positive",
760
+ "type": "CONDITIONING",
761
+ "link": 21
762
+ },
763
+ {
764
+ "name": "negative",
765
+ "type": "CONDITIONING",
766
+ "link": 22
767
+ },
768
+ {
769
+ "name": "latent_image",
770
+ "type": "LATENT",
771
+ "link": 23
772
+ }
773
+ ],
774
+ "outputs": [
775
+ {
776
+ "name": "LATENT",
777
+ "type": "LATENT",
778
+ "links": [
779
+ 24,
780
+ 36
781
+ ],
782
+ "slot_index": 0
783
+ }
784
+ ],
785
+ "properties": {
786
+ "Node name for S&R": "KSamplerAdvanced"
787
+ },
788
+ "widgets_values": [
789
+ "disable",
790
+ 424242424242,
791
+ "fixed",
792
+ 8,
793
+ 1.5,
794
+ "euler",
795
+ "simple",
796
+ 4,
797
+ 8,
798
+ "disable"
799
+ ],
800
+ "title": "Clip1 LOW - CFG1.5, 4->8 of 8"
801
+ },
802
+ {
803
+ "id": 19,
804
+ "type": "VAEDecode",
805
+ "pos": [
806
+ 3360,
807
+ 160
808
+ ],
809
+ "size": [
810
+ 320,
811
+ 120
812
+ ],
813
+ "flags": {},
814
+ "order": 18,
815
+ "mode": 0,
816
+ "inputs": [
817
+ {
818
+ "name": "samples",
819
+ "type": "LATENT",
820
+ "link": 24
821
+ },
822
+ {
823
+ "name": "vae",
824
+ "type": "VAE",
825
+ "link": 25
826
+ }
827
+ ],
828
+ "outputs": [
829
+ {
830
+ "name": "IMAGE",
831
+ "type": "IMAGE",
832
+ "links": [
833
+ 26,
834
+ 27
835
+ ],
836
+ "slot_index": 0
837
+ }
838
+ ],
839
+ "properties": {
840
+ "Node name for S&R": "VAEDecode"
841
+ },
842
+ "widgets_values": [],
843
+ "title": "Clip1 decode"
844
+ },
845
+ {
846
+ "id": 20,
847
+ "type": "VHS_VideoCombine",
848
+ "pos": [
849
+ 3720,
850
+ 160
851
+ ],
852
+ "size": [
853
+ 320,
854
+ 260
855
+ ],
856
+ "flags": {},
857
+ "order": 19,
858
+ "mode": 0,
859
+ "inputs": [
860
+ {
861
+ "name": "images",
862
+ "type": "IMAGE",
863
+ "link": 26
864
+ }
865
+ ],
866
+ "outputs": [],
867
+ "properties": {
868
+ "Node name for S&R": "VHS_VideoCombine"
869
+ },
870
+ "widgets_values": {
871
+ "frame_rate": 16,
872
+ "loop_count": 0,
873
+ "filename_prefix": "wan22_slowmo/svi_scenarioA_2clip/clip1",
874
+ "format": "video/h264-mp4",
875
+ "pingpong": false,
876
+ "save_image": true,
877
+ "crf": 19,
878
+ "save_metadata": true,
879
+ "audio_file": ""
880
+ },
881
+ "title": "Clip1 save"
882
+ },
883
+ {
884
+ "id": 21,
885
+ "type": "ImageFromBatch",
886
+ "pos": [
887
+ 4080,
888
+ 660
889
+ ],
890
+ "size": [
891
+ 320,
892
+ 120
893
+ ],
894
+ "flags": {},
895
+ "order": 20,
896
+ "mode": 0,
897
+ "inputs": [
898
+ {
899
+ "name": "image",
900
+ "type": "IMAGE",
901
+ "link": 27
902
+ }
903
+ ],
904
+ "outputs": [
905
+ {
906
+ "name": "IMAGE",
907
+ "type": "IMAGE",
908
+ "links": [
909
+ 28
910
+ ],
911
+ "slot_index": 0
912
+ }
913
+ ],
914
+ "properties": {
915
+ "Node name for S&R": "ImageFromBatch"
916
+ },
917
+ "widgets_values": [
918
+ -1,
919
+ 1
920
+ ],
921
+ "title": "Get last frame of Clip1"
922
+ },
923
+ {
924
+ "id": 22,
925
+ "type": "VAEEncode",
926
+ "pos": [
927
+ 4440,
928
+ 660
929
+ ],
930
+ "size": [
931
+ 320,
932
+ 120
933
+ ],
934
+ "flags": {},
935
+ "order": 21,
936
+ "mode": 0,
937
+ "inputs": [
938
+ {
939
+ "name": "pixels",
940
+ "type": "IMAGE",
941
+ "link": 28
942
+ },
943
+ {
944
+ "name": "vae",
945
+ "type": "VAE",
946
+ "link": 29
947
+ }
948
+ ],
949
+ "outputs": [
950
+ {
951
+ "name": "LATENT",
952
+ "type": "LATENT",
953
+ "links": [
954
+ 35
955
+ ],
956
+ "slot_index": 0
957
+ }
958
+ ],
959
+ "properties": {
960
+ "Node name for S&R": "VAEEncode"
961
+ },
962
+ "widgets_values": [],
963
+ "title": "Clip2 anchor encode (= Clip1 last frame)"
964
+ },
965
+ {
966
+ "id": 23,
967
+ "type": "CLIPTextEncode",
968
+ "pos": [
969
+ 4080,
970
+ 0
971
+ ],
972
+ "size": [
973
+ 320,
974
+ 120
975
+ ],
976
+ "flags": {},
977
+ "order": 22,
978
+ "mode": 0,
979
+ "inputs": [
980
+ {
981
+ "name": "clip",
982
+ "type": "CLIP",
983
+ "link": 30
984
+ }
985
+ ],
986
+ "outputs": [
987
+ {
988
+ "name": "CONDITIONING",
989
+ "type": "CONDITIONING",
990
+ "links": [
991
+ 32
992
+ ],
993
+ "slot_index": 0
994
+ }
995
+ ],
996
+ "properties": {
997
+ "Node name for S&R": "CLIPTextEncode"
998
+ },
999
+ "widgets_values": [
1000
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
1001
+ ],
1002
+ "title": "Clip2 Positive prompt"
1003
+ },
1004
+ {
1005
+ "id": 24,
1006
+ "type": "CLIPTextEncode",
1007
+ "pos": [
1008
+ 4080,
1009
+ 160
1010
+ ],
1011
+ "size": [
1012
+ 320,
1013
+ 120
1014
+ ],
1015
+ "flags": {},
1016
+ "order": 23,
1017
+ "mode": 0,
1018
+ "inputs": [
1019
+ {
1020
+ "name": "clip",
1021
+ "type": "CLIP",
1022
+ "link": 31
1023
+ }
1024
+ ],
1025
+ "outputs": [
1026
+ {
1027
+ "name": "CONDITIONING",
1028
+ "type": "CONDITIONING",
1029
+ "links": [
1030
+ 33
1031
+ ],
1032
+ "slot_index": 0
1033
+ }
1034
+ ],
1035
+ "properties": {
1036
+ "Node name for S&R": "CLIPTextEncode"
1037
+ },
1038
+ "widgets_values": [
1039
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
1040
+ ],
1041
+ "title": "Clip2 Negative prompt"
1042
+ },
1043
+ {
1044
+ "id": 25,
1045
+ "type": "WanImageToVideoSVIProGray_Dolphin",
1046
+ "pos": [
1047
+ 4840,
1048
+ 160
1049
+ ],
1050
+ "size": [
1051
+ 340,
1052
+ 220
1053
+ ],
1054
+ "flags": {},
1055
+ "order": 24,
1056
+ "mode": 0,
1057
+ "inputs": [
1058
+ {
1059
+ "name": "positive",
1060
+ "type": "CONDITIONING",
1061
+ "link": 32
1062
+ },
1063
+ {
1064
+ "name": "negative",
1065
+ "type": "CONDITIONING",
1066
+ "link": 33
1067
+ },
1068
+ {
1069
+ "name": "vae",
1070
+ "type": "VAE",
1071
+ "link": 34
1072
+ },
1073
+ {
1074
+ "name": "anchor_samples",
1075
+ "type": "LATENT",
1076
+ "link": 35
1077
+ },
1078
+ {
1079
+ "name": "prev_samples",
1080
+ "type": "LATENT",
1081
+ "link": 36,
1082
+ "shape": 7
1083
+ }
1084
+ ],
1085
+ "outputs": [
1086
+ {
1087
+ "name": "positive",
1088
+ "type": "CONDITIONING",
1089
+ "links": [
1090
+ 38,
1091
+ 42
1092
+ ],
1093
+ "slot_index": 0
1094
+ },
1095
+ {
1096
+ "name": "negative",
1097
+ "type": "CONDITIONING",
1098
+ "links": [
1099
+ 39,
1100
+ 43
1101
+ ],
1102
+ "slot_index": 1
1103
+ },
1104
+ {
1105
+ "name": "latent",
1106
+ "type": "LATENT",
1107
+ "links": [
1108
+ 40
1109
+ ],
1110
+ "slot_index": 2
1111
+ }
1112
+ ],
1113
+ "properties": {
1114
+ "Node name for S&R": "WanImageToVideoSVIProGray_Dolphin"
1115
+ },
1116
+ "widgets_values": [
1117
+ 81,
1118
+ 3
1119
+ ],
1120
+ "title": "Clip2: SVIProGray (REAL continuation - anchor=clip1 last frame, prev_samples=clip1 raw latent, motion_latent_count=3)"
1121
+ },
1122
+ {
1123
+ "id": 26,
1124
+ "type": "KSamplerAdvanced",
1125
+ "pos": [
1126
+ 5240,
1127
+ 160
1128
+ ],
1129
+ "size": [
1130
+ 320,
1131
+ 260
1132
+ ],
1133
+ "flags": {},
1134
+ "order": 25,
1135
+ "mode": 0,
1136
+ "inputs": [
1137
+ {
1138
+ "name": "model",
1139
+ "type": "MODEL",
1140
+ "link": 37
1141
+ },
1142
+ {
1143
+ "name": "positive",
1144
+ "type": "CONDITIONING",
1145
+ "link": 38
1146
+ },
1147
+ {
1148
+ "name": "negative",
1149
+ "type": "CONDITIONING",
1150
+ "link": 39
1151
+ },
1152
+ {
1153
+ "name": "latent_image",
1154
+ "type": "LATENT",
1155
+ "link": 40
1156
+ }
1157
+ ],
1158
+ "outputs": [
1159
+ {
1160
+ "name": "LATENT",
1161
+ "type": "LATENT",
1162
+ "links": [
1163
+ 44
1164
+ ],
1165
+ "slot_index": 0
1166
+ }
1167
+ ],
1168
+ "properties": {
1169
+ "Node name for S&R": "KSamplerAdvanced"
1170
+ },
1171
+ "widgets_values": [
1172
+ "enable",
1173
+ 858585858585,
1174
+ "fixed",
1175
+ 8,
1176
+ 1.5,
1177
+ "euler",
1178
+ "simple",
1179
+ 0,
1180
+ 4,
1181
+ "enable"
1182
+ ],
1183
+ "title": "Clip2 HIGH - CFG1.5, 0->4 of 8"
1184
+ },
1185
+ {
1186
+ "id": 27,
1187
+ "type": "KSamplerAdvanced",
1188
+ "pos": [
1189
+ 5600,
1190
+ 160
1191
+ ],
1192
+ "size": [
1193
+ 320,
1194
+ 260
1195
+ ],
1196
+ "flags": {},
1197
+ "order": 26,
1198
+ "mode": 0,
1199
+ "inputs": [
1200
+ {
1201
+ "name": "model",
1202
+ "type": "MODEL",
1203
+ "link": 41
1204
+ },
1205
+ {
1206
+ "name": "positive",
1207
+ "type": "CONDITIONING",
1208
+ "link": 42
1209
+ },
1210
+ {
1211
+ "name": "negative",
1212
+ "type": "CONDITIONING",
1213
+ "link": 43
1214
+ },
1215
+ {
1216
+ "name": "latent_image",
1217
+ "type": "LATENT",
1218
+ "link": 44
1219
+ }
1220
+ ],
1221
+ "outputs": [
1222
+ {
1223
+ "name": "LATENT",
1224
+ "type": "LATENT",
1225
+ "links": [
1226
+ 45
1227
+ ],
1228
+ "slot_index": 0
1229
+ }
1230
+ ],
1231
+ "properties": {
1232
+ "Node name for S&R": "KSamplerAdvanced"
1233
+ },
1234
+ "widgets_values": [
1235
+ "disable",
1236
+ 858585858585,
1237
+ "fixed",
1238
+ 8,
1239
+ 1.5,
1240
+ "euler",
1241
+ "simple",
1242
+ 4,
1243
+ 8,
1244
+ "disable"
1245
+ ],
1246
+ "title": "Clip2 LOW - CFG1.5, 4->8 of 8"
1247
+ },
1248
+ {
1249
+ "id": 28,
1250
+ "type": "VAEDecode",
1251
+ "pos": [
1252
+ 5960,
1253
+ 160
1254
+ ],
1255
+ "size": [
1256
+ 320,
1257
+ 120
1258
+ ],
1259
+ "flags": {},
1260
+ "order": 27,
1261
+ "mode": 0,
1262
+ "inputs": [
1263
+ {
1264
+ "name": "samples",
1265
+ "type": "LATENT",
1266
+ "link": 45
1267
+ },
1268
+ {
1269
+ "name": "vae",
1270
+ "type": "VAE",
1271
+ "link": 46
1272
+ }
1273
+ ],
1274
+ "outputs": [
1275
+ {
1276
+ "name": "IMAGE",
1277
+ "type": "IMAGE",
1278
+ "links": [
1279
+ 47
1280
+ ],
1281
+ "slot_index": 0
1282
+ }
1283
+ ],
1284
+ "properties": {
1285
+ "Node name for S&R": "VAEDecode"
1286
+ },
1287
+ "widgets_values": [],
1288
+ "title": "Clip2 decode"
1289
+ },
1290
+ {
1291
+ "id": 29,
1292
+ "type": "VHS_VideoCombine",
1293
+ "pos": [
1294
+ 6320,
1295
+ 160
1296
+ ],
1297
+ "size": [
1298
+ 320,
1299
+ 260
1300
+ ],
1301
+ "flags": {},
1302
+ "order": 28,
1303
+ "mode": 0,
1304
+ "inputs": [
1305
+ {
1306
+ "name": "images",
1307
+ "type": "IMAGE",
1308
+ "link": 47
1309
+ }
1310
+ ],
1311
+ "outputs": [],
1312
+ "properties": {
1313
+ "Node name for S&R": "VHS_VideoCombine"
1314
+ },
1315
+ "widgets_values": {
1316
+ "frame_rate": 16,
1317
+ "loop_count": 0,
1318
+ "filename_prefix": "wan22_slowmo/svi_scenarioA_2clip/clip2",
1319
+ "format": "video/h264-mp4",
1320
+ "pingpong": false,
1321
+ "save_image": true,
1322
+ "crf": 19,
1323
+ "save_metadata": true,
1324
+ "audio_file": ""
1325
+ },
1326
+ "title": "Clip2 save"
1327
+ },
1328
+ {
1329
+ "id": 30,
1330
+ "type": "Note",
1331
+ "pos": [
1332
+ 2640,
1333
+ 480
1334
+ ],
1335
+ "size": [
1336
+ 1100,
1337
+ 340
1338
+ ],
1339
+ "flags": {},
1340
+ "order": 29,
1341
+ "mode": 0,
1342
+ "inputs": [],
1343
+ "outputs": [],
1344
+ "properties": {
1345
+ "Node name for S&R": "Note"
1346
+ },
1347
+ "widgets_values": [
1348
+ "시나리오 A (2클립 진짜 연속 테스트, 2026-07-28).\n이전 버전(콜드스타트, anchor 1장만)에서 후반부에 그레이 노이즈가 나온 것에 대한 후속 테스트.\n가설: SVI Pro는 원래 '이전 클립의 진짜 모션 프레임'을 prev_samples로 이어받는 연속 생성용으로 튜닝된 것이라, anchor 1장짜리 콜드스타트(필러 20/21)로 테스트한 건 설계 범위 밖이었을 수 있음.\n이번엔 Clip1은 어쩔 수 없이 콜드스타트(anchor만, prev_samples 없음)로 만들고, Clip2는 Clip1의 마지막 프레임을 anchor로, Clip1의 원본(디코딩 안 된) latent를 prev_samples로 이어받아(motion_latent_count=3) 실제 연속 생성 상황을 재현함.\nCFG=1.5/steps=8(4+4)는 이전 테스트와 동일하게 유지 — 그레이 노이즈 원인이 '콜드스타트라서'였는지 'CFG/steps가 애초에 부족해서'였는지 이 변경 하나로 분리해서 확인하려는 목적.\n공식 README 권장대로 클립마다 다른 시드 사용(Clip1=424242424242, Clip2=858585858585).\nClip2가 여전히 그레이가 나오면 CFG/steps 부족 쪽에 무게가 실림 — 그땐 CFG를 올리거나 0705_vision3.json의 실제 TripleKSampler+튜닝값으로 SVIPro만 교체하는 테스트로 넘어갈 것."
1349
+ ],
1350
+ "title": "Scenario A - 2-clip real continuation test",
1351
+ "color": "#432",
1352
+ "bgcolor": "#653"
1353
+ }
1354
+ ],
1355
+ "links": [
1356
+ [
1357
+ 1,
1358
+ 3,
1359
+ 0,
1360
+ 5,
1361
+ 0,
1362
+ "MODEL"
1363
+ ],
1364
+ [
1365
+ 2,
1366
+ 5,
1367
+ 0,
1368
+ 6,
1369
+ 0,
1370
+ "MODEL"
1371
+ ],
1372
+ [
1373
+ 3,
1374
+ 6,
1375
+ 0,
1376
+ 7,
1377
+ 0,
1378
+ "MODEL"
1379
+ ],
1380
+ [
1381
+ 4,
1382
+ 4,
1383
+ 0,
1384
+ 8,
1385
+ 0,
1386
+ "MODEL"
1387
+ ],
1388
+ [
1389
+ 5,
1390
+ 8,
1391
+ 0,
1392
+ 9,
1393
+ 0,
1394
+ "MODEL"
1395
+ ],
1396
+ [
1397
+ 6,
1398
+ 9,
1399
+ 0,
1400
+ 10,
1401
+ 0,
1402
+ "MODEL"
1403
+ ],
1404
+ [
1405
+ 7,
1406
+ 1,
1407
+ 0,
1408
+ 11,
1409
+ 0,
1410
+ "CLIP"
1411
+ ],
1412
+ [
1413
+ 8,
1414
+ 1,
1415
+ 0,
1416
+ 12,
1417
+ 0,
1418
+ "CLIP"
1419
+ ],
1420
+ [
1421
+ 9,
1422
+ 13,
1423
+ 0,
1424
+ 14,
1425
+ 0,
1426
+ "IMAGE"
1427
+ ],
1428
+ [
1429
+ 10,
1430
+ 14,
1431
+ 0,
1432
+ 15,
1433
+ 0,
1434
+ "IMAGE"
1435
+ ],
1436
+ [
1437
+ 11,
1438
+ 2,
1439
+ 0,
1440
+ 15,
1441
+ 1,
1442
+ "VAE"
1443
+ ],
1444
+ [
1445
+ 12,
1446
+ 11,
1447
+ 0,
1448
+ 16,
1449
+ 0,
1450
+ "CONDITIONING"
1451
+ ],
1452
+ [
1453
+ 13,
1454
+ 12,
1455
+ 0,
1456
+ 16,
1457
+ 1,
1458
+ "CONDITIONING"
1459
+ ],
1460
+ [
1461
+ 14,
1462
+ 2,
1463
+ 0,
1464
+ 16,
1465
+ 2,
1466
+ "VAE"
1467
+ ],
1468
+ [
1469
+ 15,
1470
+ 15,
1471
+ 0,
1472
+ 16,
1473
+ 3,
1474
+ "LATENT"
1475
+ ],
1476
+ [
1477
+ 16,
1478
+ 7,
1479
+ 0,
1480
+ 17,
1481
+ 0,
1482
+ "MODEL"
1483
+ ],
1484
+ [
1485
+ 17,
1486
+ 16,
1487
+ 0,
1488
+ 17,
1489
+ 1,
1490
+ "CONDITIONING"
1491
+ ],
1492
+ [
1493
+ 18,
1494
+ 16,
1495
+ 1,
1496
+ 17,
1497
+ 2,
1498
+ "CONDITIONING"
1499
+ ],
1500
+ [
1501
+ 19,
1502
+ 16,
1503
+ 2,
1504
+ 17,
1505
+ 3,
1506
+ "LATENT"
1507
+ ],
1508
+ [
1509
+ 20,
1510
+ 10,
1511
+ 0,
1512
+ 18,
1513
+ 0,
1514
+ "MODEL"
1515
+ ],
1516
+ [
1517
+ 21,
1518
+ 16,
1519
+ 0,
1520
+ 18,
1521
+ 1,
1522
+ "CONDITIONING"
1523
+ ],
1524
+ [
1525
+ 22,
1526
+ 16,
1527
+ 1,
1528
+ 18,
1529
+ 2,
1530
+ "CONDITIONING"
1531
+ ],
1532
+ [
1533
+ 23,
1534
+ 17,
1535
+ 0,
1536
+ 18,
1537
+ 3,
1538
+ "LATENT"
1539
+ ],
1540
+ [
1541
+ 24,
1542
+ 18,
1543
+ 0,
1544
+ 19,
1545
+ 0,
1546
+ "LATENT"
1547
+ ],
1548
+ [
1549
+ 25,
1550
+ 2,
1551
+ 0,
1552
+ 19,
1553
+ 1,
1554
+ "VAE"
1555
+ ],
1556
+ [
1557
+ 26,
1558
+ 19,
1559
+ 0,
1560
+ 20,
1561
+ 0,
1562
+ "IMAGE"
1563
+ ],
1564
+ [
1565
+ 27,
1566
+ 19,
1567
+ 0,
1568
+ 21,
1569
+ 0,
1570
+ "IMAGE"
1571
+ ],
1572
+ [
1573
+ 28,
1574
+ 21,
1575
+ 0,
1576
+ 22,
1577
+ 0,
1578
+ "IMAGE"
1579
+ ],
1580
+ [
1581
+ 29,
1582
+ 2,
1583
+ 0,
1584
+ 22,
1585
+ 1,
1586
+ "VAE"
1587
+ ],
1588
+ [
1589
+ 30,
1590
+ 1,
1591
+ 0,
1592
+ 23,
1593
+ 0,
1594
+ "CLIP"
1595
+ ],
1596
+ [
1597
+ 31,
1598
+ 1,
1599
+ 0,
1600
+ 24,
1601
+ 0,
1602
+ "CLIP"
1603
+ ],
1604
+ [
1605
+ 32,
1606
+ 23,
1607
+ 0,
1608
+ 25,
1609
+ 0,
1610
+ "CONDITIONING"
1611
+ ],
1612
+ [
1613
+ 33,
1614
+ 24,
1615
+ 0,
1616
+ 25,
1617
+ 1,
1618
+ "CONDITIONING"
1619
+ ],
1620
+ [
1621
+ 34,
1622
+ 2,
1623
+ 0,
1624
+ 25,
1625
+ 2,
1626
+ "VAE"
1627
+ ],
1628
+ [
1629
+ 35,
1630
+ 22,
1631
+ 0,
1632
+ 25,
1633
+ 3,
1634
+ "LATENT"
1635
+ ],
1636
+ [
1637
+ 36,
1638
+ 18,
1639
+ 0,
1640
+ 25,
1641
+ 4,
1642
+ "LATENT"
1643
+ ],
1644
+ [
1645
+ 37,
1646
+ 7,
1647
+ 0,
1648
+ 26,
1649
+ 0,
1650
+ "MODEL"
1651
+ ],
1652
+ [
1653
+ 38,
1654
+ 25,
1655
+ 0,
1656
+ 26,
1657
+ 1,
1658
+ "CONDITIONING"
1659
+ ],
1660
+ [
1661
+ 39,
1662
+ 25,
1663
+ 1,
1664
+ 26,
1665
+ 2,
1666
+ "CONDITIONING"
1667
+ ],
1668
+ [
1669
+ 40,
1670
+ 25,
1671
+ 2,
1672
+ 26,
1673
+ 3,
1674
+ "LATENT"
1675
+ ],
1676
+ [
1677
+ 41,
1678
+ 10,
1679
+ 0,
1680
+ 27,
1681
+ 0,
1682
+ "MODEL"
1683
+ ],
1684
+ [
1685
+ 42,
1686
+ 25,
1687
+ 0,
1688
+ 27,
1689
+ 1,
1690
+ "CONDITIONING"
1691
+ ],
1692
+ [
1693
+ 43,
1694
+ 25,
1695
+ 1,
1696
+ 27,
1697
+ 2,
1698
+ "CONDITIONING"
1699
+ ],
1700
+ [
1701
+ 44,
1702
+ 26,
1703
+ 0,
1704
+ 27,
1705
+ 3,
1706
+ "LATENT"
1707
+ ],
1708
+ [
1709
+ 45,
1710
+ 27,
1711
+ 0,
1712
+ 28,
1713
+ 0,
1714
+ "LATENT"
1715
+ ],
1716
+ [
1717
+ 46,
1718
+ 2,
1719
+ 0,
1720
+ 28,
1721
+ 1,
1722
+ "VAE"
1723
+ ],
1724
+ [
1725
+ 47,
1726
+ 28,
1727
+ 0,
1728
+ 29,
1729
+ 0,
1730
+ "IMAGE"
1731
+ ]
1732
+ ],
1733
+ "groups": [],
1734
+ "config": {},
1735
+ "extra": {},
1736
+ "version": 0.4
1737
+ }
_archive/workflows/slowmo_svi_scenarioA_official_recipe.json ADDED
@@ -0,0 +1,1113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 21,
3
+ "last_link_id": 26,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 120
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "inputs": [],
20
+ "outputs": [
21
+ {
22
+ "name": "CLIP",
23
+ "type": "CLIP",
24
+ "links": [
25
+ 7,
26
+ 8
27
+ ],
28
+ "slot_index": 0
29
+ }
30
+ ],
31
+ "properties": {
32
+ "Node name for S&R": "CLIPLoader"
33
+ },
34
+ "widgets_values": [
35
+ "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
36
+ "wan",
37
+ "default"
38
+ ]
39
+ },
40
+ {
41
+ "id": 2,
42
+ "type": "VAELoader",
43
+ "pos": [
44
+ 0,
45
+ 160
46
+ ],
47
+ "size": [
48
+ 320,
49
+ 120
50
+ ],
51
+ "flags": {},
52
+ "order": 1,
53
+ "mode": 0,
54
+ "inputs": [],
55
+ "outputs": [
56
+ {
57
+ "name": "VAE",
58
+ "type": "VAE",
59
+ "links": [
60
+ 11,
61
+ 14,
62
+ 25
63
+ ],
64
+ "slot_index": 0
65
+ }
66
+ ],
67
+ "properties": {
68
+ "Node name for S&R": "VAELoader"
69
+ },
70
+ "widgets_values": [
71
+ "wan_2.1_vae.safetensors"
72
+ ]
73
+ },
74
+ {
75
+ "id": 3,
76
+ "type": "UnetLoaderGGUF",
77
+ "pos": [
78
+ 0,
79
+ 320
80
+ ],
81
+ "size": [
82
+ 320,
83
+ 120
84
+ ],
85
+ "flags": {},
86
+ "order": 2,
87
+ "mode": 0,
88
+ "inputs": [],
89
+ "outputs": [
90
+ {
91
+ "name": "MODEL",
92
+ "type": "MODEL",
93
+ "links": [
94
+ 1
95
+ ],
96
+ "slot_index": 0
97
+ }
98
+ ],
99
+ "properties": {
100
+ "Node name for S&R": "UnetLoaderGGUF"
101
+ },
102
+ "widgets_values": [
103
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
104
+ ],
105
+ "title": "High-noise UNet"
106
+ },
107
+ {
108
+ "id": 4,
109
+ "type": "UnetLoaderGGUF",
110
+ "pos": [
111
+ 0,
112
+ 480
113
+ ],
114
+ "size": [
115
+ 320,
116
+ 120
117
+ ],
118
+ "flags": {},
119
+ "order": 3,
120
+ "mode": 0,
121
+ "inputs": [],
122
+ "outputs": [
123
+ {
124
+ "name": "MODEL",
125
+ "type": "MODEL",
126
+ "links": [
127
+ 4
128
+ ],
129
+ "slot_index": 0
130
+ }
131
+ ],
132
+ "properties": {
133
+ "Node name for S&R": "UnetLoaderGGUF"
134
+ },
135
+ "widgets_values": [
136
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
137
+ ],
138
+ "title": "Low-noise UNet"
139
+ },
140
+ {
141
+ "id": 5,
142
+ "type": "LoraLoaderModelOnly",
143
+ "pos": [
144
+ 360,
145
+ 320
146
+ ],
147
+ "size": [
148
+ 320,
149
+ 120
150
+ ],
151
+ "flags": {},
152
+ "order": 4,
153
+ "mode": 0,
154
+ "inputs": [
155
+ {
156
+ "name": "model",
157
+ "type": "MODEL",
158
+ "link": 1
159
+ }
160
+ ],
161
+ "outputs": [
162
+ {
163
+ "name": "MODEL",
164
+ "type": "MODEL",
165
+ "links": [
166
+ 2
167
+ ],
168
+ "slot_index": 0
169
+ }
170
+ ],
171
+ "properties": {
172
+ "Node name for S&R": "LoraLoaderModelOnly"
173
+ },
174
+ "widgets_values": [
175
+ "SVI/SVI_Wan2.2-I2V-A14B_high_noise_lora_v2.0.safetensors",
176
+ 1.0
177
+ ],
178
+ "title": "SVI LoRA (HIGH, strength 1.0)"
179
+ },
180
+ {
181
+ "id": 6,
182
+ "type": "LoraLoaderModelOnly",
183
+ "pos": [
184
+ 720,
185
+ 320
186
+ ],
187
+ "size": [
188
+ 320,
189
+ 120
190
+ ],
191
+ "flags": {},
192
+ "order": 5,
193
+ "mode": 0,
194
+ "inputs": [
195
+ {
196
+ "name": "model",
197
+ "type": "MODEL",
198
+ "link": 2
199
+ }
200
+ ],
201
+ "outputs": [
202
+ {
203
+ "name": "MODEL",
204
+ "type": "MODEL",
205
+ "links": [
206
+ 3
207
+ ],
208
+ "slot_index": 0
209
+ }
210
+ ],
211
+ "properties": {
212
+ "Node name for S&R": "LoraLoaderModelOnly"
213
+ },
214
+ "widgets_values": [
215
+ "Wan_2_2_I2V_A14B_HIGH_lightx2v_4step_lora_v1030_rank_64_bf16.safetensors",
216
+ 0.5
217
+ ],
218
+ "title": "lightx2v LoRA (HIGH, strength 0.5 - official SVI recipe)"
219
+ },
220
+ {
221
+ "id": 7,
222
+ "type": "ModelSamplingSD3",
223
+ "pos": [
224
+ 1080,
225
+ 320
226
+ ],
227
+ "size": [
228
+ 320,
229
+ 120
230
+ ],
231
+ "flags": {},
232
+ "order": 6,
233
+ "mode": 0,
234
+ "inputs": [
235
+ {
236
+ "name": "model",
237
+ "type": "MODEL",
238
+ "link": 3
239
+ }
240
+ ],
241
+ "outputs": [
242
+ {
243
+ "name": "MODEL",
244
+ "type": "MODEL",
245
+ "links": [
246
+ 16
247
+ ],
248
+ "slot_index": 0
249
+ }
250
+ ],
251
+ "properties": {
252
+ "Node name for S&R": "ModelSamplingSD3"
253
+ },
254
+ "widgets_values": [
255
+ 5.0
256
+ ],
257
+ "title": "High noise (SVI 1.0 + lightx2v 0.5)"
258
+ },
259
+ {
260
+ "id": 8,
261
+ "type": "LoraLoaderModelOnly",
262
+ "pos": [
263
+ 360,
264
+ 480
265
+ ],
266
+ "size": [
267
+ 320,
268
+ 120
269
+ ],
270
+ "flags": {},
271
+ "order": 7,
272
+ "mode": 0,
273
+ "inputs": [
274
+ {
275
+ "name": "model",
276
+ "type": "MODEL",
277
+ "link": 4
278
+ }
279
+ ],
280
+ "outputs": [
281
+ {
282
+ "name": "MODEL",
283
+ "type": "MODEL",
284
+ "links": [
285
+ 5
286
+ ],
287
+ "slot_index": 0
288
+ }
289
+ ],
290
+ "properties": {
291
+ "Node name for S&R": "LoraLoaderModelOnly"
292
+ },
293
+ "widgets_values": [
294
+ "SVI/SVI_Wan2.2-I2V-A14B_low_noise_lora_v2.0.safetensors",
295
+ 1.0
296
+ ],
297
+ "title": "SVI LoRA (LOW, strength 1.0)"
298
+ },
299
+ {
300
+ "id": 9,
301
+ "type": "LoraLoaderModelOnly",
302
+ "pos": [
303
+ 720,
304
+ 480
305
+ ],
306
+ "size": [
307
+ 320,
308
+ 120
309
+ ],
310
+ "flags": {},
311
+ "order": 8,
312
+ "mode": 0,
313
+ "inputs": [
314
+ {
315
+ "name": "model",
316
+ "type": "MODEL",
317
+ "link": 5
318
+ }
319
+ ],
320
+ "outputs": [
321
+ {
322
+ "name": "MODEL",
323
+ "type": "MODEL",
324
+ "links": [
325
+ 6
326
+ ],
327
+ "slot_index": 0
328
+ }
329
+ ],
330
+ "properties": {
331
+ "Node name for S&R": "LoraLoaderModelOnly"
332
+ },
333
+ "widgets_values": [
334
+ "wan2.2_i2v_A14b_low_noise_lora_rank64_lightx2v_4step_1022.safetensors",
335
+ 1.0
336
+ ],
337
+ "title": "lightx2v LoRA (LOW, strength 1.0)"
338
+ },
339
+ {
340
+ "id": 10,
341
+ "type": "ModelSamplingSD3",
342
+ "pos": [
343
+ 1080,
344
+ 480
345
+ ],
346
+ "size": [
347
+ 320,
348
+ 120
349
+ ],
350
+ "flags": {},
351
+ "order": 9,
352
+ "mode": 0,
353
+ "inputs": [
354
+ {
355
+ "name": "model",
356
+ "type": "MODEL",
357
+ "link": 6
358
+ }
359
+ ],
360
+ "outputs": [
361
+ {
362
+ "name": "MODEL",
363
+ "type": "MODEL",
364
+ "links": [
365
+ 20
366
+ ],
367
+ "slot_index": 0
368
+ }
369
+ ],
370
+ "properties": {
371
+ "Node name for S&R": "ModelSamplingSD3"
372
+ },
373
+ "widgets_values": [
374
+ 5.0
375
+ ],
376
+ "title": "Low noise (SVI 1.0 + lightx2v 1.0)"
377
+ },
378
+ {
379
+ "id": 11,
380
+ "type": "CLIPTextEncode",
381
+ "pos": [
382
+ 1080,
383
+ 0
384
+ ],
385
+ "size": [
386
+ 320,
387
+ 120
388
+ ],
389
+ "flags": {},
390
+ "order": 10,
391
+ "mode": 0,
392
+ "inputs": [
393
+ {
394
+ "name": "clip",
395
+ "type": "CLIP",
396
+ "link": 7
397
+ }
398
+ ],
399
+ "outputs": [
400
+ {
401
+ "name": "CONDITIONING",
402
+ "type": "CONDITIONING",
403
+ "links": [
404
+ 12
405
+ ],
406
+ "slot_index": 0
407
+ }
408
+ ],
409
+ "properties": {
410
+ "Node name for S&R": "CLIPTextEncode"
411
+ },
412
+ "widgets_values": [
413
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
414
+ ],
415
+ "title": "Positive prompt"
416
+ },
417
+ {
418
+ "id": 12,
419
+ "type": "CLIPTextEncode",
420
+ "pos": [
421
+ 1080,
422
+ 160
423
+ ],
424
+ "size": [
425
+ 320,
426
+ 120
427
+ ],
428
+ "flags": {},
429
+ "order": 11,
430
+ "mode": 0,
431
+ "inputs": [
432
+ {
433
+ "name": "clip",
434
+ "type": "CLIP",
435
+ "link": 8
436
+ }
437
+ ],
438
+ "outputs": [
439
+ {
440
+ "name": "CONDITIONING",
441
+ "type": "CONDITIONING",
442
+ "links": [
443
+ 13
444
+ ],
445
+ "slot_index": 0
446
+ }
447
+ ],
448
+ "properties": {
449
+ "Node name for S&R": "CLIPTextEncode"
450
+ },
451
+ "widgets_values": [
452
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
453
+ ],
454
+ "title": "Negative prompt"
455
+ },
456
+ {
457
+ "id": 13,
458
+ "type": "LoadImage",
459
+ "pos": [
460
+ 1080,
461
+ 660
462
+ ],
463
+ "size": [
464
+ 320,
465
+ 320
466
+ ],
467
+ "flags": {},
468
+ "order": 12,
469
+ "mode": 0,
470
+ "inputs": [],
471
+ "outputs": [
472
+ {
473
+ "name": "IMAGE",
474
+ "type": "IMAGE",
475
+ "links": [
476
+ 9
477
+ ],
478
+ "slot_index": 0
479
+ },
480
+ {
481
+ "name": "MASK",
482
+ "type": "MASK",
483
+ "links": [],
484
+ "slot_index": 1
485
+ }
486
+ ],
487
+ "properties": {
488
+ "Node name for S&R": "LoadImage"
489
+ },
490
+ "widgets_values": [
491
+ "input.png",
492
+ "image"
493
+ ]
494
+ },
495
+ {
496
+ "id": 14,
497
+ "type": "WanNativeResize_Dolphin",
498
+ "pos": [
499
+ 1440,
500
+ 660
501
+ ],
502
+ "size": [
503
+ 320,
504
+ 120
505
+ ],
506
+ "flags": {},
507
+ "order": 13,
508
+ "mode": 0,
509
+ "inputs": [
510
+ {
511
+ "name": "image",
512
+ "type": "IMAGE",
513
+ "link": 9
514
+ }
515
+ ],
516
+ "outputs": [
517
+ {
518
+ "name": "IMAGE",
519
+ "type": "IMAGE",
520
+ "links": [
521
+ 10
522
+ ],
523
+ "slot_index": 0
524
+ },
525
+ {
526
+ "name": "width",
527
+ "type": "INT",
528
+ "links": [],
529
+ "slot_index": 1
530
+ },
531
+ {
532
+ "name": "height",
533
+ "type": "INT",
534
+ "links": [],
535
+ "slot_index": 2
536
+ }
537
+ ],
538
+ "properties": {
539
+ "Node name for S&R": "WanNativeResize_Dolphin"
540
+ },
541
+ "widgets_values": [
542
+ "480P (832x480)",
543
+ "lanczos",
544
+ 16
545
+ ]
546
+ },
547
+ {
548
+ "id": 15,
549
+ "type": "VAEEncode",
550
+ "pos": [
551
+ 1840,
552
+ 660
553
+ ],
554
+ "size": [
555
+ 320,
556
+ 120
557
+ ],
558
+ "flags": {},
559
+ "order": 14,
560
+ "mode": 0,
561
+ "inputs": [
562
+ {
563
+ "name": "pixels",
564
+ "type": "IMAGE",
565
+ "link": 10
566
+ },
567
+ {
568
+ "name": "vae",
569
+ "type": "VAE",
570
+ "link": 11
571
+ }
572
+ ],
573
+ "outputs": [
574
+ {
575
+ "name": "LATENT",
576
+ "type": "LATENT",
577
+ "links": [
578
+ 15
579
+ ],
580
+ "slot_index": 0
581
+ }
582
+ ],
583
+ "properties": {
584
+ "Node name for S&R": "VAEEncode"
585
+ },
586
+ "widgets_values": [],
587
+ "title": "Encode anchor image -> LATENT"
588
+ },
589
+ {
590
+ "id": 16,
591
+ "type": "WanImageToVideoSVIProGray_Dolphin",
592
+ "pos": [
593
+ 2240,
594
+ 160
595
+ ],
596
+ "size": [
597
+ 340,
598
+ 220
599
+ ],
600
+ "flags": {},
601
+ "order": 15,
602
+ "mode": 0,
603
+ "inputs": [
604
+ {
605
+ "name": "positive",
606
+ "type": "CONDITIONING",
607
+ "link": 12
608
+ },
609
+ {
610
+ "name": "negative",
611
+ "type": "CONDITIONING",
612
+ "link": 13
613
+ },
614
+ {
615
+ "name": "vae",
616
+ "type": "VAE",
617
+ "link": 14
618
+ },
619
+ {
620
+ "name": "anchor_samples",
621
+ "type": "LATENT",
622
+ "link": 15
623
+ },
624
+ {
625
+ "name": "prev_samples",
626
+ "type": "LATENT",
627
+ "link": null,
628
+ "shape": 7
629
+ }
630
+ ],
631
+ "outputs": [
632
+ {
633
+ "name": "positive",
634
+ "type": "CONDITIONING",
635
+ "links": [
636
+ 17,
637
+ 21
638
+ ],
639
+ "slot_index": 0
640
+ },
641
+ {
642
+ "name": "negative",
643
+ "type": "CONDITIONING",
644
+ "links": [
645
+ 18,
646
+ 22
647
+ ],
648
+ "slot_index": 1
649
+ },
650
+ {
651
+ "name": "latent",
652
+ "type": "LATENT",
653
+ "links": [
654
+ 19
655
+ ],
656
+ "slot_index": 2
657
+ }
658
+ ],
659
+ "properties": {
660
+ "Node name for S&R": "WanImageToVideoSVIProGray_Dolphin"
661
+ },
662
+ "widgets_values": [
663
+ 81,
664
+ 0
665
+ ],
666
+ "title": "WanImageToVideoSVIProGray (root-cause-fixed, single clip: no prev_samples)"
667
+ },
668
+ {
669
+ "id": 17,
670
+ "type": "KSamplerAdvanced",
671
+ "pos": [
672
+ 2640,
673
+ 160
674
+ ],
675
+ "size": [
676
+ 320,
677
+ 260
678
+ ],
679
+ "flags": {},
680
+ "order": 16,
681
+ "mode": 0,
682
+ "inputs": [
683
+ {
684
+ "name": "model",
685
+ "type": "MODEL",
686
+ "link": 16
687
+ },
688
+ {
689
+ "name": "positive",
690
+ "type": "CONDITIONING",
691
+ "link": 17
692
+ },
693
+ {
694
+ "name": "negative",
695
+ "type": "CONDITIONING",
696
+ "link": 18
697
+ },
698
+ {
699
+ "name": "latent_image",
700
+ "type": "LATENT",
701
+ "link": 19
702
+ }
703
+ ],
704
+ "outputs": [
705
+ {
706
+ "name": "LATENT",
707
+ "type": "LATENT",
708
+ "links": [
709
+ 23
710
+ ],
711
+ "slot_index": 0
712
+ }
713
+ ],
714
+ "properties": {
715
+ "Node name for S&R": "KSamplerAdvanced"
716
+ },
717
+ "widgets_values": [
718
+ "enable",
719
+ 424242424242,
720
+ "fixed",
721
+ 8,
722
+ 1.5,
723
+ "euler",
724
+ "simple",
725
+ 0,
726
+ 4,
727
+ "enable"
728
+ ],
729
+ "title": "HIGH stage - SVI 1.0 + lightx2v 0.5, CFG 1.5, steps 0->4 of 8"
730
+ },
731
+ {
732
+ "id": 18,
733
+ "type": "KSamplerAdvanced",
734
+ "pos": [
735
+ 3000,
736
+ 160
737
+ ],
738
+ "size": [
739
+ 320,
740
+ 260
741
+ ],
742
+ "flags": {},
743
+ "order": 17,
744
+ "mode": 0,
745
+ "inputs": [
746
+ {
747
+ "name": "model",
748
+ "type": "MODEL",
749
+ "link": 20
750
+ },
751
+ {
752
+ "name": "positive",
753
+ "type": "CONDITIONING",
754
+ "link": 21
755
+ },
756
+ {
757
+ "name": "negative",
758
+ "type": "CONDITIONING",
759
+ "link": 22
760
+ },
761
+ {
762
+ "name": "latent_image",
763
+ "type": "LATENT",
764
+ "link": 23
765
+ }
766
+ ],
767
+ "outputs": [
768
+ {
769
+ "name": "LATENT",
770
+ "type": "LATENT",
771
+ "links": [
772
+ 24
773
+ ],
774
+ "slot_index": 0
775
+ }
776
+ ],
777
+ "properties": {
778
+ "Node name for S&R": "KSamplerAdvanced"
779
+ },
780
+ "widgets_values": [
781
+ "disable",
782
+ 424242424242,
783
+ "fixed",
784
+ 8,
785
+ 1.5,
786
+ "euler",
787
+ "simple",
788
+ 4,
789
+ 8,
790
+ "disable"
791
+ ],
792
+ "title": "LOW stage - SVI 1.0 + lightx2v 1.0, CFG 1.5, steps 4->8 of 8"
793
+ },
794
+ {
795
+ "id": 19,
796
+ "type": "VAEDecode",
797
+ "pos": [
798
+ 3360,
799
+ 160
800
+ ],
801
+ "size": [
802
+ 320,
803
+ 120
804
+ ],
805
+ "flags": {},
806
+ "order": 18,
807
+ "mode": 0,
808
+ "inputs": [
809
+ {
810
+ "name": "samples",
811
+ "type": "LATENT",
812
+ "link": 24
813
+ },
814
+ {
815
+ "name": "vae",
816
+ "type": "VAE",
817
+ "link": 25
818
+ }
819
+ ],
820
+ "outputs": [
821
+ {
822
+ "name": "IMAGE",
823
+ "type": "IMAGE",
824
+ "links": [
825
+ 26
826
+ ],
827
+ "slot_index": 0
828
+ }
829
+ ],
830
+ "properties": {
831
+ "Node name for S&R": "VAEDecode"
832
+ },
833
+ "widgets_values": []
834
+ },
835
+ {
836
+ "id": 20,
837
+ "type": "VHS_VideoCombine",
838
+ "pos": [
839
+ 3720,
840
+ 160
841
+ ],
842
+ "size": [
843
+ 320,
844
+ 260
845
+ ],
846
+ "flags": {},
847
+ "order": 19,
848
+ "mode": 0,
849
+ "inputs": [
850
+ {
851
+ "name": "images",
852
+ "type": "IMAGE",
853
+ "link": 26
854
+ }
855
+ ],
856
+ "outputs": [],
857
+ "properties": {
858
+ "Node name for S&R": "VHS_VideoCombine"
859
+ },
860
+ "widgets_values": {
861
+ "frame_rate": 16,
862
+ "loop_count": 0,
863
+ "filename_prefix": "wan22_slowmo/svi_scenarioA_official_recipe",
864
+ "format": "video/h264-mp4",
865
+ "pingpong": false,
866
+ "save_image": true,
867
+ "crf": 19,
868
+ "save_metadata": true,
869
+ "audio_file": ""
870
+ }
871
+ },
872
+ {
873
+ "id": 21,
874
+ "type": "Note",
875
+ "pos": [
876
+ 2640,
877
+ 480
878
+ ],
879
+ "size": [
880
+ 1000,
881
+ 320
882
+ ],
883
+ "flags": {},
884
+ "order": 20,
885
+ "mode": 0,
886
+ "inputs": [],
887
+ "outputs": [],
888
+ "properties": {
889
+ "Node name for S&R": "Note"
890
+ },
891
+ "widgets_values": [
892
+ "시나리오 A: 공식 SVI Pro 레시피 재현 (vita-epfl/Stable-Video-Infinity, svi_wan22 브랜치 SVI-Wan22-1210-4-Clips.json 직접 분석해서 만듦).\n- lightx2v LoRA: High=0.5(절반 강도), Low=1.0(전체 강도) — 공식 워크플로우에서 그대로 확인.\n- SVI LoRA: High/Low 둘 다 1.0.\n- CFG: 1.5 균일 (README 원문: 'cfg 5가 원��� 기본값이지만 가속 컴포넌트(lightx2v) 때문에 유지가 안 돼서 1.5로 고정, 텍스트 순응도 올리려면 1~2 범위에서만 살짝 올려라').\n- 해상도: 480P(832x480) — 공식 예시와 커뮤니티 보고 모두 '480p가 720p보다 SVI+lightx2v 조합에서 더 안정적'이라고 함.\n ⚠ 이건 메모리의 '832x480이 슬로우모션 유발'이라는 기록과 반대되는 관찰이라, SVI 맥락에서도 실제로 맞는지 이 테스트로 확인 필요.\n- 안카(anchor)만 있고 prev_samples는 없는 단일 클립 테스트입니다(WanImageToVideoSVIProGray_Dolphin의 root-cause fix는 적용됨). 멀티클립 체인(시나리오 D)은 별도 파일로.\n출처: raw.githubusercontent.com/vita-epfl/Stable-Video-Infinity/svi_wan22/comfyui_workflow/SVI-Wan22-1210-4-Clips.json, comfyui_workflow/README.md"
893
+ ],
894
+ "title": "Scenario A: Official SVI recipe (lightx2v Hi=0.5/Lo=1.0, CFG1.5, 480p)",
895
+ "color": "#432",
896
+ "bgcolor": "#653"
897
+ }
898
+ ],
899
+ "links": [
900
+ [
901
+ 1,
902
+ 3,
903
+ 0,
904
+ 5,
905
+ 0,
906
+ "MODEL"
907
+ ],
908
+ [
909
+ 2,
910
+ 5,
911
+ 0,
912
+ 6,
913
+ 0,
914
+ "MODEL"
915
+ ],
916
+ [
917
+ 3,
918
+ 6,
919
+ 0,
920
+ 7,
921
+ 0,
922
+ "MODEL"
923
+ ],
924
+ [
925
+ 4,
926
+ 4,
927
+ 0,
928
+ 8,
929
+ 0,
930
+ "MODEL"
931
+ ],
932
+ [
933
+ 5,
934
+ 8,
935
+ 0,
936
+ 9,
937
+ 0,
938
+ "MODEL"
939
+ ],
940
+ [
941
+ 6,
942
+ 9,
943
+ 0,
944
+ 10,
945
+ 0,
946
+ "MODEL"
947
+ ],
948
+ [
949
+ 7,
950
+ 1,
951
+ 0,
952
+ 11,
953
+ 0,
954
+ "CLIP"
955
+ ],
956
+ [
957
+ 8,
958
+ 1,
959
+ 0,
960
+ 12,
961
+ 0,
962
+ "CLIP"
963
+ ],
964
+ [
965
+ 9,
966
+ 13,
967
+ 0,
968
+ 14,
969
+ 0,
970
+ "IMAGE"
971
+ ],
972
+ [
973
+ 10,
974
+ 14,
975
+ 0,
976
+ 15,
977
+ 0,
978
+ "IMAGE"
979
+ ],
980
+ [
981
+ 11,
982
+ 2,
983
+ 0,
984
+ 15,
985
+ 1,
986
+ "VAE"
987
+ ],
988
+ [
989
+ 12,
990
+ 11,
991
+ 0,
992
+ 16,
993
+ 0,
994
+ "CONDITIONING"
995
+ ],
996
+ [
997
+ 13,
998
+ 12,
999
+ 0,
1000
+ 16,
1001
+ 1,
1002
+ "CONDITIONING"
1003
+ ],
1004
+ [
1005
+ 14,
1006
+ 2,
1007
+ 0,
1008
+ 16,
1009
+ 2,
1010
+ "VAE"
1011
+ ],
1012
+ [
1013
+ 15,
1014
+ 15,
1015
+ 0,
1016
+ 16,
1017
+ 3,
1018
+ "LATENT"
1019
+ ],
1020
+ [
1021
+ 16,
1022
+ 7,
1023
+ 0,
1024
+ 17,
1025
+ 0,
1026
+ "MODEL"
1027
+ ],
1028
+ [
1029
+ 17,
1030
+ 16,
1031
+ 0,
1032
+ 17,
1033
+ 1,
1034
+ "CONDITIONING"
1035
+ ],
1036
+ [
1037
+ 18,
1038
+ 16,
1039
+ 1,
1040
+ 17,
1041
+ 2,
1042
+ "CONDITIONING"
1043
+ ],
1044
+ [
1045
+ 19,
1046
+ 16,
1047
+ 2,
1048
+ 17,
1049
+ 3,
1050
+ "LATENT"
1051
+ ],
1052
+ [
1053
+ 20,
1054
+ 10,
1055
+ 0,
1056
+ 18,
1057
+ 0,
1058
+ "MODEL"
1059
+ ],
1060
+ [
1061
+ 21,
1062
+ 16,
1063
+ 0,
1064
+ 18,
1065
+ 1,
1066
+ "CONDITIONING"
1067
+ ],
1068
+ [
1069
+ 22,
1070
+ 16,
1071
+ 1,
1072
+ 18,
1073
+ 2,
1074
+ "CONDITIONING"
1075
+ ],
1076
+ [
1077
+ 23,
1078
+ 17,
1079
+ 0,
1080
+ 18,
1081
+ 3,
1082
+ "LATENT"
1083
+ ],
1084
+ [
1085
+ 24,
1086
+ 18,
1087
+ 0,
1088
+ 19,
1089
+ 0,
1090
+ "LATENT"
1091
+ ],
1092
+ [
1093
+ 25,
1094
+ 2,
1095
+ 0,
1096
+ 19,
1097
+ 1,
1098
+ "VAE"
1099
+ ],
1100
+ [
1101
+ 26,
1102
+ 19,
1103
+ 0,
1104
+ 20,
1105
+ 0,
1106
+ "IMAGE"
1107
+ ]
1108
+ ],
1109
+ "groups": [],
1110
+ "config": {},
1111
+ "extra": {},
1112
+ "version": 0.4
1113
+ }
_archive/workflows/slowmo_svi_simple_nag_test.json ADDED
@@ -0,0 +1,1243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 23,
3
+ "last_link_id": 30,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 120
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "inputs": [],
20
+ "outputs": [
21
+ {
22
+ "name": "CLIP",
23
+ "type": "CLIP",
24
+ "links": [
25
+ 7,
26
+ 8
27
+ ],
28
+ "slot_index": 0
29
+ }
30
+ ],
31
+ "properties": {
32
+ "Node name for S&R": "CLIPLoader"
33
+ },
34
+ "widgets_values": [
35
+ "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
36
+ "wan",
37
+ "default"
38
+ ]
39
+ },
40
+ {
41
+ "id": 2,
42
+ "type": "VAELoader",
43
+ "pos": [
44
+ 0,
45
+ 160
46
+ ],
47
+ "size": [
48
+ 320,
49
+ 120
50
+ ],
51
+ "flags": {},
52
+ "order": 1,
53
+ "mode": 0,
54
+ "inputs": [],
55
+ "outputs": [
56
+ {
57
+ "name": "VAE",
58
+ "type": "VAE",
59
+ "links": [
60
+ 15,
61
+ 18,
62
+ 29
63
+ ],
64
+ "slot_index": 0
65
+ }
66
+ ],
67
+ "properties": {
68
+ "Node name for S&R": "VAELoader"
69
+ },
70
+ "widgets_values": [
71
+ "wan_2.1_vae.safetensors"
72
+ ]
73
+ },
74
+ {
75
+ "id": 3,
76
+ "type": "UnetLoaderGGUF",
77
+ "pos": [
78
+ 0,
79
+ 320
80
+ ],
81
+ "size": [
82
+ 320,
83
+ 120
84
+ ],
85
+ "flags": {},
86
+ "order": 2,
87
+ "mode": 0,
88
+ "inputs": [],
89
+ "outputs": [
90
+ {
91
+ "name": "MODEL",
92
+ "type": "MODEL",
93
+ "links": [
94
+ 1
95
+ ],
96
+ "slot_index": 0
97
+ }
98
+ ],
99
+ "properties": {
100
+ "Node name for S&R": "UnetLoaderGGUF"
101
+ },
102
+ "widgets_values": [
103
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
104
+ ],
105
+ "title": "High-noise UNet"
106
+ },
107
+ {
108
+ "id": 4,
109
+ "type": "UnetLoaderGGUF",
110
+ "pos": [
111
+ 0,
112
+ 480
113
+ ],
114
+ "size": [
115
+ 320,
116
+ 120
117
+ ],
118
+ "flags": {},
119
+ "order": 3,
120
+ "mode": 0,
121
+ "inputs": [],
122
+ "outputs": [
123
+ {
124
+ "name": "MODEL",
125
+ "type": "MODEL",
126
+ "links": [
127
+ 4
128
+ ],
129
+ "slot_index": 0
130
+ }
131
+ ],
132
+ "properties": {
133
+ "Node name for S&R": "UnetLoaderGGUF"
134
+ },
135
+ "widgets_values": [
136
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
137
+ ],
138
+ "title": "Low-noise UNet"
139
+ },
140
+ {
141
+ "id": 5,
142
+ "type": "LoraLoaderModelOnly",
143
+ "pos": [
144
+ 360,
145
+ 320
146
+ ],
147
+ "size": [
148
+ 320,
149
+ 120
150
+ ],
151
+ "flags": {},
152
+ "order": 4,
153
+ "mode": 0,
154
+ "inputs": [
155
+ {
156
+ "name": "model",
157
+ "type": "MODEL",
158
+ "link": 1
159
+ }
160
+ ],
161
+ "outputs": [
162
+ {
163
+ "name": "MODEL",
164
+ "type": "MODEL",
165
+ "links": [
166
+ 2
167
+ ],
168
+ "slot_index": 0
169
+ }
170
+ ],
171
+ "properties": {
172
+ "Node name for S&R": "LoraLoaderModelOnly"
173
+ },
174
+ "widgets_values": [
175
+ "SVI_v2_PRO_Wan2.2-I2V-A14B_HIGH_lora_rank_128_fp16.safetensors",
176
+ 1.0
177
+ ],
178
+ "title": "SVI Pro 2.0 LoRA (HIGH, 1.0)"
179
+ },
180
+ {
181
+ "id": 6,
182
+ "type": "LoraLoaderModelOnly",
183
+ "pos": [
184
+ 720,
185
+ 320
186
+ ],
187
+ "size": [
188
+ 320,
189
+ 120
190
+ ],
191
+ "flags": {},
192
+ "order": 5,
193
+ "mode": 0,
194
+ "inputs": [
195
+ {
196
+ "name": "model",
197
+ "type": "MODEL",
198
+ "link": 2
199
+ }
200
+ ],
201
+ "outputs": [
202
+ {
203
+ "name": "MODEL",
204
+ "type": "MODEL",
205
+ "links": [
206
+ 3
207
+ ],
208
+ "slot_index": 0
209
+ }
210
+ ],
211
+ "properties": {
212
+ "Node name for S&R": "LoraLoaderModelOnly"
213
+ },
214
+ "widgets_values": [
215
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
216
+ 0.5
217
+ ],
218
+ "title": "lightx2v LoRA (HIGH, 0.5)"
219
+ },
220
+ {
221
+ "id": 7,
222
+ "type": "ModelSamplingSD3",
223
+ "pos": [
224
+ 1080,
225
+ 320
226
+ ],
227
+ "size": [
228
+ 320,
229
+ 120
230
+ ],
231
+ "flags": {},
232
+ "order": 6,
233
+ "mode": 0,
234
+ "inputs": [
235
+ {
236
+ "name": "model",
237
+ "type": "MODEL",
238
+ "link": 3
239
+ }
240
+ ],
241
+ "outputs": [
242
+ {
243
+ "name": "MODEL",
244
+ "type": "MODEL",
245
+ "links": [
246
+ 9
247
+ ],
248
+ "slot_index": 0
249
+ }
250
+ ],
251
+ "properties": {
252
+ "Node name for S&R": "ModelSamplingSD3"
253
+ },
254
+ "widgets_values": [
255
+ 9.0
256
+ ],
257
+ "title": "ModelSamplingSD3 shift=9.0 (HIGH)"
258
+ },
259
+ {
260
+ "id": 8,
261
+ "type": "LoraLoaderModelOnly",
262
+ "pos": [
263
+ 360,
264
+ 480
265
+ ],
266
+ "size": [
267
+ 320,
268
+ 120
269
+ ],
270
+ "flags": {},
271
+ "order": 7,
272
+ "mode": 0,
273
+ "inputs": [
274
+ {
275
+ "name": "model",
276
+ "type": "MODEL",
277
+ "link": 4
278
+ }
279
+ ],
280
+ "outputs": [
281
+ {
282
+ "name": "MODEL",
283
+ "type": "MODEL",
284
+ "links": [
285
+ 5
286
+ ],
287
+ "slot_index": 0
288
+ }
289
+ ],
290
+ "properties": {
291
+ "Node name for S&R": "LoraLoaderModelOnly"
292
+ },
293
+ "widgets_values": [
294
+ "SVI_v2_PRO_Wan2.2-I2V-A14B_LOW_lora_rank_128_fp16.safetensors",
295
+ 1.0
296
+ ],
297
+ "title": "SVI Pro 2.0 LoRA (LOW, 1.0)"
298
+ },
299
+ {
300
+ "id": 9,
301
+ "type": "LoraLoaderModelOnly",
302
+ "pos": [
303
+ 720,
304
+ 480
305
+ ],
306
+ "size": [
307
+ 320,
308
+ 120
309
+ ],
310
+ "flags": {},
311
+ "order": 8,
312
+ "mode": 0,
313
+ "inputs": [
314
+ {
315
+ "name": "model",
316
+ "type": "MODEL",
317
+ "link": 5
318
+ }
319
+ ],
320
+ "outputs": [
321
+ {
322
+ "name": "MODEL",
323
+ "type": "MODEL",
324
+ "links": [
325
+ 6
326
+ ],
327
+ "slot_index": 0
328
+ }
329
+ ],
330
+ "properties": {
331
+ "Node name for S&R": "LoraLoaderModelOnly"
332
+ },
333
+ "widgets_values": [
334
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
335
+ 1.0
336
+ ],
337
+ "title": "lightx2v LoRA (LOW, 1.0)"
338
+ },
339
+ {
340
+ "id": 10,
341
+ "type": "ModelSamplingSD3",
342
+ "pos": [
343
+ 1080,
344
+ 480
345
+ ],
346
+ "size": [
347
+ 320,
348
+ 120
349
+ ],
350
+ "flags": {},
351
+ "order": 9,
352
+ "mode": 0,
353
+ "inputs": [
354
+ {
355
+ "name": "model",
356
+ "type": "MODEL",
357
+ "link": 6
358
+ }
359
+ ],
360
+ "outputs": [
361
+ {
362
+ "name": "MODEL",
363
+ "type": "MODEL",
364
+ "links": [
365
+ 11
366
+ ],
367
+ "slot_index": 0
368
+ }
369
+ ],
370
+ "properties": {
371
+ "Node name for S&R": "ModelSamplingSD3"
372
+ },
373
+ "widgets_values": [
374
+ 9.0
375
+ ],
376
+ "title": "ModelSamplingSD3 shift=9.0 (LOW)"
377
+ },
378
+ {
379
+ "id": 11,
380
+ "type": "CLIPTextEncode",
381
+ "pos": [
382
+ 1080,
383
+ 0
384
+ ],
385
+ "size": [
386
+ 320,
387
+ 120
388
+ ],
389
+ "flags": {},
390
+ "order": 10,
391
+ "mode": 0,
392
+ "inputs": [
393
+ {
394
+ "name": "clip",
395
+ "type": "CLIP",
396
+ "link": 7
397
+ }
398
+ ],
399
+ "outputs": [
400
+ {
401
+ "name": "CONDITIONING",
402
+ "type": "CONDITIONING",
403
+ "links": [
404
+ 10,
405
+ 12,
406
+ 16
407
+ ],
408
+ "slot_index": 0
409
+ }
410
+ ],
411
+ "properties": {
412
+ "Node name for S&R": "CLIPTextEncode"
413
+ },
414
+ "widgets_values": [
415
+ "fast fluid continuous motion, real-time speed, dynamic movement, no slow motion, no freeze frame"
416
+ ],
417
+ "title": "Positive prompt"
418
+ },
419
+ {
420
+ "id": 12,
421
+ "type": "CLIPTextEncode",
422
+ "pos": [
423
+ 1080,
424
+ 160
425
+ ],
426
+ "size": [
427
+ 320,
428
+ 120
429
+ ],
430
+ "flags": {},
431
+ "order": 11,
432
+ "mode": 0,
433
+ "inputs": [
434
+ {
435
+ "name": "clip",
436
+ "type": "CLIP",
437
+ "link": 8
438
+ }
439
+ ],
440
+ "outputs": [
441
+ {
442
+ "name": "CONDITIONING",
443
+ "type": "CONDITIONING",
444
+ "links": [
445
+ 17
446
+ ],
447
+ "slot_index": 0
448
+ }
449
+ ],
450
+ "properties": {
451
+ "Node name for S&R": "CLIPTextEncode"
452
+ },
453
+ "widgets_values": [
454
+ "static, frozen, slow motion, freeze frame, low quality, blurry, motionless"
455
+ ],
456
+ "title": "Negative prompt"
457
+ },
458
+ {
459
+ "id": 13,
460
+ "type": "WanVideoNAG",
461
+ "pos": [
462
+ 1440,
463
+ 320
464
+ ],
465
+ "size": [
466
+ 320,
467
+ 120
468
+ ],
469
+ "flags": {},
470
+ "order": 12,
471
+ "mode": 0,
472
+ "inputs": [
473
+ {
474
+ "name": "model",
475
+ "type": "MODEL",
476
+ "link": 9
477
+ },
478
+ {
479
+ "name": "conditioning",
480
+ "type": "CONDITIONING",
481
+ "link": 10
482
+ }
483
+ ],
484
+ "outputs": [
485
+ {
486
+ "name": "model",
487
+ "type": "MODEL",
488
+ "links": [
489
+ 20
490
+ ],
491
+ "slot_index": 0
492
+ }
493
+ ],
494
+ "properties": {
495
+ "Node name for S&R": "WanVideoNAG"
496
+ },
497
+ "widgets_values": [
498
+ 11.0,
499
+ 0.25,
500
+ 2.5,
501
+ "default",
502
+ false
503
+ ],
504
+ "title": "NAG (HIGH) - nag_scale 11/alpha .25/tau 2.5"
505
+ },
506
+ {
507
+ "id": 14,
508
+ "type": "WanVideoNAG",
509
+ "pos": [
510
+ 1440,
511
+ 480
512
+ ],
513
+ "size": [
514
+ 320,
515
+ 120
516
+ ],
517
+ "flags": {},
518
+ "order": 13,
519
+ "mode": 0,
520
+ "inputs": [
521
+ {
522
+ "name": "model",
523
+ "type": "MODEL",
524
+ "link": 11
525
+ },
526
+ {
527
+ "name": "conditioning",
528
+ "type": "CONDITIONING",
529
+ "link": 12
530
+ }
531
+ ],
532
+ "outputs": [
533
+ {
534
+ "name": "model",
535
+ "type": "MODEL",
536
+ "links": [
537
+ 24
538
+ ],
539
+ "slot_index": 0
540
+ }
541
+ ],
542
+ "properties": {
543
+ "Node name for S&R": "WanVideoNAG"
544
+ },
545
+ "widgets_values": [
546
+ 11.0,
547
+ 0.25,
548
+ 2.5,
549
+ "default",
550
+ false
551
+ ],
552
+ "title": "NAG (LOW) - nag_scale 11/alpha .25/tau 2.5"
553
+ },
554
+ {
555
+ "id": 15,
556
+ "type": "LoadImage",
557
+ "pos": [
558
+ 1080,
559
+ 660
560
+ ],
561
+ "size": [
562
+ 320,
563
+ 320
564
+ ],
565
+ "flags": {},
566
+ "order": 14,
567
+ "mode": 0,
568
+ "inputs": [],
569
+ "outputs": [
570
+ {
571
+ "name": "IMAGE",
572
+ "type": "IMAGE",
573
+ "links": [
574
+ 13
575
+ ],
576
+ "slot_index": 0
577
+ },
578
+ {
579
+ "name": "MASK",
580
+ "type": "MASK",
581
+ "links": [],
582
+ "slot_index": 1
583
+ }
584
+ ],
585
+ "properties": {
586
+ "Node name for S&R": "LoadImage"
587
+ },
588
+ "widgets_values": [
589
+ "input.png",
590
+ "image"
591
+ ]
592
+ },
593
+ {
594
+ "id": 16,
595
+ "type": "WanNativeResize_Dolphin",
596
+ "pos": [
597
+ 1440,
598
+ 660
599
+ ],
600
+ "size": [
601
+ 320,
602
+ 120
603
+ ],
604
+ "flags": {},
605
+ "order": 15,
606
+ "mode": 0,
607
+ "inputs": [
608
+ {
609
+ "name": "image",
610
+ "type": "IMAGE",
611
+ "link": 13
612
+ }
613
+ ],
614
+ "outputs": [
615
+ {
616
+ "name": "IMAGE",
617
+ "type": "IMAGE",
618
+ "links": [
619
+ 14
620
+ ],
621
+ "slot_index": 0
622
+ },
623
+ {
624
+ "name": "width",
625
+ "type": "INT",
626
+ "links": [],
627
+ "slot_index": 1
628
+ },
629
+ {
630
+ "name": "height",
631
+ "type": "INT",
632
+ "links": [],
633
+ "slot_index": 2
634
+ }
635
+ ],
636
+ "properties": {
637
+ "Node name for S&R": "WanNativeResize_Dolphin"
638
+ },
639
+ "widgets_values": [
640
+ "480P (832x480)",
641
+ "lanczos",
642
+ 16
643
+ ]
644
+ },
645
+ {
646
+ "id": 17,
647
+ "type": "VAEEncode",
648
+ "pos": [
649
+ 1840,
650
+ 660
651
+ ],
652
+ "size": [
653
+ 320,
654
+ 120
655
+ ],
656
+ "flags": {},
657
+ "order": 16,
658
+ "mode": 0,
659
+ "inputs": [
660
+ {
661
+ "name": "pixels",
662
+ "type": "IMAGE",
663
+ "link": 14
664
+ },
665
+ {
666
+ "name": "vae",
667
+ "type": "VAE",
668
+ "link": 15
669
+ }
670
+ ],
671
+ "outputs": [
672
+ {
673
+ "name": "LATENT",
674
+ "type": "LATENT",
675
+ "links": [
676
+ 19
677
+ ],
678
+ "slot_index": 0
679
+ }
680
+ ],
681
+ "properties": {
682
+ "Node name for S&R": "VAEEncode"
683
+ },
684
+ "widgets_values": [],
685
+ "title": "Anchor encode"
686
+ },
687
+ {
688
+ "id": 18,
689
+ "type": "WanImageToVideoSVIProGray_Dolphin",
690
+ "pos": [
691
+ 1840,
692
+ 160
693
+ ],
694
+ "size": [
695
+ 340,
696
+ 220
697
+ ],
698
+ "flags": {},
699
+ "order": 17,
700
+ "mode": 0,
701
+ "inputs": [
702
+ {
703
+ "name": "positive",
704
+ "type": "CONDITIONING",
705
+ "link": 16
706
+ },
707
+ {
708
+ "name": "negative",
709
+ "type": "CONDITIONING",
710
+ "link": 17
711
+ },
712
+ {
713
+ "name": "vae",
714
+ "type": "VAE",
715
+ "link": 18
716
+ },
717
+ {
718
+ "name": "anchor_samples",
719
+ "type": "LATENT",
720
+ "link": 19
721
+ },
722
+ {
723
+ "name": "prev_samples",
724
+ "type": "LATENT",
725
+ "link": null,
726
+ "shape": 7
727
+ }
728
+ ],
729
+ "outputs": [
730
+ {
731
+ "name": "positive",
732
+ "type": "CONDITIONING",
733
+ "links": [
734
+ 21,
735
+ 25
736
+ ],
737
+ "slot_index": 0
738
+ },
739
+ {
740
+ "name": "negative",
741
+ "type": "CONDITIONING",
742
+ "links": [
743
+ 22,
744
+ 26
745
+ ],
746
+ "slot_index": 1
747
+ },
748
+ {
749
+ "name": "latent",
750
+ "type": "LATENT",
751
+ "links": [
752
+ 23
753
+ ],
754
+ "slot_index": 2
755
+ }
756
+ ],
757
+ "properties": {
758
+ "Node name for S&R": "WanImageToVideoSVIProGray_Dolphin"
759
+ },
760
+ "widgets_values": [
761
+ 81,
762
+ 0
763
+ ],
764
+ "title": "SVIProGray (root-cause-fixed, single clip)"
765
+ },
766
+ {
767
+ "id": 19,
768
+ "type": "KSamplerAdvanced",
769
+ "pos": [
770
+ 2240,
771
+ 160
772
+ ],
773
+ "size": [
774
+ 320,
775
+ 260
776
+ ],
777
+ "flags": {},
778
+ "order": 18,
779
+ "mode": 0,
780
+ "inputs": [
781
+ {
782
+ "name": "model",
783
+ "type": "MODEL",
784
+ "link": 20
785
+ },
786
+ {
787
+ "name": "positive",
788
+ "type": "CONDITIONING",
789
+ "link": 21
790
+ },
791
+ {
792
+ "name": "negative",
793
+ "type": "CONDITIONING",
794
+ "link": 22
795
+ },
796
+ {
797
+ "name": "latent_image",
798
+ "type": "LATENT",
799
+ "link": 23
800
+ }
801
+ ],
802
+ "outputs": [
803
+ {
804
+ "name": "LATENT",
805
+ "type": "LATENT",
806
+ "links": [
807
+ 27
808
+ ],
809
+ "slot_index": 0
810
+ }
811
+ ],
812
+ "properties": {
813
+ "Node name for S&R": "KSamplerAdvanced"
814
+ },
815
+ "widgets_values": [
816
+ "enable",
817
+ 373737373737,
818
+ "fixed",
819
+ 8,
820
+ 1.0,
821
+ "euler",
822
+ "simple",
823
+ 0,
824
+ 4,
825
+ "enable"
826
+ ],
827
+ "title": "HIGH - NAG model, CFG 1.0, 0->4 of 8"
828
+ },
829
+ {
830
+ "id": 20,
831
+ "type": "KSamplerAdvanced",
832
+ "pos": [
833
+ 2600,
834
+ 160
835
+ ],
836
+ "size": [
837
+ 320,
838
+ 260
839
+ ],
840
+ "flags": {},
841
+ "order": 19,
842
+ "mode": 0,
843
+ "inputs": [
844
+ {
845
+ "name": "model",
846
+ "type": "MODEL",
847
+ "link": 24
848
+ },
849
+ {
850
+ "name": "positive",
851
+ "type": "CONDITIONING",
852
+ "link": 25
853
+ },
854
+ {
855
+ "name": "negative",
856
+ "type": "CONDITIONING",
857
+ "link": 26
858
+ },
859
+ {
860
+ "name": "latent_image",
861
+ "type": "LATENT",
862
+ "link": 27
863
+ }
864
+ ],
865
+ "outputs": [
866
+ {
867
+ "name": "LATENT",
868
+ "type": "LATENT",
869
+ "links": [
870
+ 28
871
+ ],
872
+ "slot_index": 0
873
+ }
874
+ ],
875
+ "properties": {
876
+ "Node name for S&R": "KSamplerAdvanced"
877
+ },
878
+ "widgets_values": [
879
+ "disable",
880
+ 373737373737,
881
+ "fixed",
882
+ 8,
883
+ 1.0,
884
+ "euler",
885
+ "simple",
886
+ 4,
887
+ 8,
888
+ "disable"
889
+ ],
890
+ "title": "LOW - NAG model, CFG 1.0, 4->8 of 8"
891
+ },
892
+ {
893
+ "id": 21,
894
+ "type": "VAEDecode",
895
+ "pos": [
896
+ 2960,
897
+ 160
898
+ ],
899
+ "size": [
900
+ 320,
901
+ 120
902
+ ],
903
+ "flags": {},
904
+ "order": 20,
905
+ "mode": 0,
906
+ "inputs": [
907
+ {
908
+ "name": "samples",
909
+ "type": "LATENT",
910
+ "link": 28
911
+ },
912
+ {
913
+ "name": "vae",
914
+ "type": "VAE",
915
+ "link": 29
916
+ }
917
+ ],
918
+ "outputs": [
919
+ {
920
+ "name": "IMAGE",
921
+ "type": "IMAGE",
922
+ "links": [
923
+ 30
924
+ ],
925
+ "slot_index": 0
926
+ }
927
+ ],
928
+ "properties": {
929
+ "Node name for S&R": "VAEDecode"
930
+ },
931
+ "widgets_values": []
932
+ },
933
+ {
934
+ "id": 22,
935
+ "type": "VHS_VideoCombine",
936
+ "pos": [
937
+ 3320,
938
+ 160
939
+ ],
940
+ "size": [
941
+ 320,
942
+ 260
943
+ ],
944
+ "flags": {},
945
+ "order": 21,
946
+ "mode": 0,
947
+ "inputs": [
948
+ {
949
+ "name": "images",
950
+ "type": "IMAGE",
951
+ "link": 30
952
+ }
953
+ ],
954
+ "outputs": [],
955
+ "properties": {
956
+ "Node name for S&R": "VHS_VideoCombine"
957
+ },
958
+ "widgets_values": {
959
+ "frame_rate": 16,
960
+ "loop_count": 0,
961
+ "filename_prefix": "wan22_slowmo/svi_simple_nag",
962
+ "format": "video/h264-mp4",
963
+ "pingpong": false,
964
+ "save_image": true,
965
+ "crf": 19,
966
+ "save_metadata": true,
967
+ "audio_file": ""
968
+ }
969
+ },
970
+ {
971
+ "id": 23,
972
+ "type": "Note",
973
+ "pos": [
974
+ 2240,
975
+ 480
976
+ ],
977
+ "size": [
978
+ 1000,
979
+ 300
980
+ ],
981
+ "flags": {},
982
+ "order": 22,
983
+ "mode": 0,
984
+ "inputs": [],
985
+ "outputs": [],
986
+ "properties": {
987
+ "Node name for S&R": "Note"
988
+ },
989
+ "widgets_values": [
990
+ "간단 버전 (0705_vision3.json 대체, 2026-07-28). 단일 클립, 프롬프트 디렉터/액션 스플리터 없음.\n이전 그레이 노이즈 실패 원인 진단 결과 반영:\n- WanVideoNAG 추가(nag_scale=11/alpha=0.25/tau=2.5, 기본값) - CFG를 1.0까지 낮춰도 되는 건 NAG가 negative guidance를 대신하기 때문. NAG 없이 CFG1.5만 썼던 이전 버전이 그레이 노이즈 원인이었을 가능성 높음 (civitai.com/models/2305587 'SVI2Pro w/ NAG' 참고).\n- ModelSamplingSD3 shift=9.0 (이전엔 5.0 - vita-epfl 리서치 기준이었는데, 실제 CivitAI 레시피는 9.0).\n- LoRA는 사용자 실제 파일과 동일 계보: SVI Pro 2.0(rank128 fp16, Kijai/WanVideo_comfy 미러) + lightx2v Wan2.1(High=0.5/Low=1.0, CivitAI 권장값).\n- WanImageToVideoSVIProGray_Dolphin(필러 버그 수정판) 그대로 사용.\n튜닝 포인트: nag_scale/alpha/tau, shift, LoRA 강도(0.5~0.6 범위 권장), steps(8=4+4)."
991
+ ],
992
+ "title": "Simple SVI+NAG test (replaces heavy 0705_vision3.json)",
993
+ "color": "#432",
994
+ "bgcolor": "#653"
995
+ }
996
+ ],
997
+ "links": [
998
+ [
999
+ 1,
1000
+ 3,
1001
+ 0,
1002
+ 5,
1003
+ 0,
1004
+ "MODEL"
1005
+ ],
1006
+ [
1007
+ 2,
1008
+ 5,
1009
+ 0,
1010
+ 6,
1011
+ 0,
1012
+ "MODEL"
1013
+ ],
1014
+ [
1015
+ 3,
1016
+ 6,
1017
+ 0,
1018
+ 7,
1019
+ 0,
1020
+ "MODEL"
1021
+ ],
1022
+ [
1023
+ 4,
1024
+ 4,
1025
+ 0,
1026
+ 8,
1027
+ 0,
1028
+ "MODEL"
1029
+ ],
1030
+ [
1031
+ 5,
1032
+ 8,
1033
+ 0,
1034
+ 9,
1035
+ 0,
1036
+ "MODEL"
1037
+ ],
1038
+ [
1039
+ 6,
1040
+ 9,
1041
+ 0,
1042
+ 10,
1043
+ 0,
1044
+ "MODEL"
1045
+ ],
1046
+ [
1047
+ 7,
1048
+ 1,
1049
+ 0,
1050
+ 11,
1051
+ 0,
1052
+ "CLIP"
1053
+ ],
1054
+ [
1055
+ 8,
1056
+ 1,
1057
+ 0,
1058
+ 12,
1059
+ 0,
1060
+ "CLIP"
1061
+ ],
1062
+ [
1063
+ 9,
1064
+ 7,
1065
+ 0,
1066
+ 13,
1067
+ 0,
1068
+ "MODEL"
1069
+ ],
1070
+ [
1071
+ 10,
1072
+ 11,
1073
+ 0,
1074
+ 13,
1075
+ 1,
1076
+ "CONDITIONING"
1077
+ ],
1078
+ [
1079
+ 11,
1080
+ 10,
1081
+ 0,
1082
+ 14,
1083
+ 0,
1084
+ "MODEL"
1085
+ ],
1086
+ [
1087
+ 12,
1088
+ 11,
1089
+ 0,
1090
+ 14,
1091
+ 1,
1092
+ "CONDITIONING"
1093
+ ],
1094
+ [
1095
+ 13,
1096
+ 15,
1097
+ 0,
1098
+ 16,
1099
+ 0,
1100
+ "IMAGE"
1101
+ ],
1102
+ [
1103
+ 14,
1104
+ 16,
1105
+ 0,
1106
+ 17,
1107
+ 0,
1108
+ "IMAGE"
1109
+ ],
1110
+ [
1111
+ 15,
1112
+ 2,
1113
+ 0,
1114
+ 17,
1115
+ 1,
1116
+ "VAE"
1117
+ ],
1118
+ [
1119
+ 16,
1120
+ 11,
1121
+ 0,
1122
+ 18,
1123
+ 0,
1124
+ "CONDITIONING"
1125
+ ],
1126
+ [
1127
+ 17,
1128
+ 12,
1129
+ 0,
1130
+ 18,
1131
+ 1,
1132
+ "CONDITIONING"
1133
+ ],
1134
+ [
1135
+ 18,
1136
+ 2,
1137
+ 0,
1138
+ 18,
1139
+ 2,
1140
+ "VAE"
1141
+ ],
1142
+ [
1143
+ 19,
1144
+ 17,
1145
+ 0,
1146
+ 18,
1147
+ 3,
1148
+ "LATENT"
1149
+ ],
1150
+ [
1151
+ 20,
1152
+ 13,
1153
+ 0,
1154
+ 19,
1155
+ 0,
1156
+ "MODEL"
1157
+ ],
1158
+ [
1159
+ 21,
1160
+ 18,
1161
+ 0,
1162
+ 19,
1163
+ 1,
1164
+ "CONDITIONING"
1165
+ ],
1166
+ [
1167
+ 22,
1168
+ 18,
1169
+ 1,
1170
+ 19,
1171
+ 2,
1172
+ "CONDITIONING"
1173
+ ],
1174
+ [
1175
+ 23,
1176
+ 18,
1177
+ 2,
1178
+ 19,
1179
+ 3,
1180
+ "LATENT"
1181
+ ],
1182
+ [
1183
+ 24,
1184
+ 14,
1185
+ 0,
1186
+ 20,
1187
+ 0,
1188
+ "MODEL"
1189
+ ],
1190
+ [
1191
+ 25,
1192
+ 18,
1193
+ 0,
1194
+ 20,
1195
+ 1,
1196
+ "CONDITIONING"
1197
+ ],
1198
+ [
1199
+ 26,
1200
+ 18,
1201
+ 1,
1202
+ 20,
1203
+ 2,
1204
+ "CONDITIONING"
1205
+ ],
1206
+ [
1207
+ 27,
1208
+ 19,
1209
+ 0,
1210
+ 20,
1211
+ 3,
1212
+ "LATENT"
1213
+ ],
1214
+ [
1215
+ 28,
1216
+ 20,
1217
+ 0,
1218
+ 21,
1219
+ 0,
1220
+ "LATENT"
1221
+ ],
1222
+ [
1223
+ 29,
1224
+ 2,
1225
+ 0,
1226
+ 21,
1227
+ 1,
1228
+ "VAE"
1229
+ ],
1230
+ [
1231
+ 30,
1232
+ 21,
1233
+ 0,
1234
+ 22,
1235
+ 0,
1236
+ "IMAGE"
1237
+ ]
1238
+ ],
1239
+ "groups": [],
1240
+ "config": {},
1241
+ "extra": {},
1242
+ "version": 0.4
1243
+ }
_archive/workflows/v30.json ADDED
The diff for this file is too large to render. See raw diff
 
_archive/workflows/wan22_v2v_high_denoise_compare.json ADDED
@@ -0,0 +1,1198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "last_node_id": 21,
3
+ "last_link_id": 31,
4
+ "nodes": [
5
+ {
6
+ "id": 1,
7
+ "type": "CLIPLoader",
8
+ "pos": [
9
+ 0,
10
+ 0
11
+ ],
12
+ "size": [
13
+ 320,
14
+ 110
15
+ ],
16
+ "flags": {},
17
+ "order": 0,
18
+ "mode": 0,
19
+ "properties": {
20
+ "Node name for S&R": "CLIPLoader"
21
+ },
22
+ "widgets_values": [
23
+ "nsfw_wan_umt5-xxl_fp8_scaled.safetensors",
24
+ "wan",
25
+ "default"
26
+ ],
27
+ "outputs": [
28
+ {
29
+ "name": "CLIP",
30
+ "type": "CLIP",
31
+ "links": [
32
+ 5,
33
+ 6
34
+ ],
35
+ "slot_index": 0
36
+ }
37
+ ]
38
+ },
39
+ {
40
+ "id": 2,
41
+ "type": "VAELoader",
42
+ "pos": [
43
+ 0,
44
+ 150
45
+ ],
46
+ "size": [
47
+ 320,
48
+ 60
49
+ ],
50
+ "flags": {},
51
+ "order": 0,
52
+ "mode": 0,
53
+ "properties": {
54
+ "Node name for S&R": "VAELoader"
55
+ },
56
+ "widgets_values": [
57
+ "Wan2_1_VAE_bf16.safetensors"
58
+ ],
59
+ "outputs": [
60
+ {
61
+ "name": "VAE",
62
+ "type": "VAE",
63
+ "links": [
64
+ 9,
65
+ 19,
66
+ 30
67
+ ],
68
+ "slot_index": 0
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "id": 3,
74
+ "type": "UnetLoaderGGUF",
75
+ "pos": [
76
+ 0,
77
+ 250
78
+ ],
79
+ "size": [
80
+ 320,
81
+ 60
82
+ ],
83
+ "flags": {},
84
+ "order": 0,
85
+ "mode": 0,
86
+ "properties": {
87
+ "Node name for S&R": "UnetLoaderGGUF"
88
+ },
89
+ "widgets_values": [
90
+ "Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf"
91
+ ],
92
+ "title": "Load HIGH-noise GGUF",
93
+ "outputs": [
94
+ {
95
+ "name": "MODEL",
96
+ "type": "MODEL",
97
+ "links": [
98
+ 1
99
+ ],
100
+ "slot_index": 0
101
+ }
102
+ ]
103
+ },
104
+ {
105
+ "id": 4,
106
+ "type": "UnetLoaderGGUF",
107
+ "pos": [
108
+ 0,
109
+ 350
110
+ ],
111
+ "size": [
112
+ 320,
113
+ 60
114
+ ],
115
+ "flags": {},
116
+ "order": 0,
117
+ "mode": 0,
118
+ "properties": {
119
+ "Node name for S&R": "UnetLoaderGGUF"
120
+ },
121
+ "widgets_values": [
122
+ "Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf"
123
+ ],
124
+ "title": "Load LOW-noise GGUF",
125
+ "outputs": [
126
+ {
127
+ "name": "MODEL",
128
+ "type": "MODEL",
129
+ "links": [
130
+ 2
131
+ ],
132
+ "slot_index": 0
133
+ }
134
+ ]
135
+ },
136
+ {
137
+ "id": 5,
138
+ "type": "LoraLoaderModelOnly",
139
+ "pos": [
140
+ 360,
141
+ 250
142
+ ],
143
+ "size": [
144
+ 320,
145
+ 80
146
+ ],
147
+ "flags": {},
148
+ "order": 0,
149
+ "mode": 0,
150
+ "properties": {
151
+ "Node name for S&R": "LoraLoaderModelOnly"
152
+ },
153
+ "widgets_values": [
154
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
155
+ 1.0
156
+ ],
157
+ "title": "lightx2v LoRA (HIGH)",
158
+ "inputs": [
159
+ {
160
+ "name": "model",
161
+ "type": "MODEL",
162
+ "link": 1
163
+ }
164
+ ],
165
+ "outputs": [
166
+ {
167
+ "name": "MODEL",
168
+ "type": "MODEL",
169
+ "links": [
170
+ 3
171
+ ],
172
+ "slot_index": 0
173
+ }
174
+ ]
175
+ },
176
+ {
177
+ "id": 6,
178
+ "type": "LoraLoaderModelOnly",
179
+ "pos": [
180
+ 360,
181
+ 350
182
+ ],
183
+ "size": [
184
+ 320,
185
+ 80
186
+ ],
187
+ "flags": {},
188
+ "order": 0,
189
+ "mode": 0,
190
+ "properties": {
191
+ "Node name for S&R": "LoraLoaderModelOnly"
192
+ },
193
+ "widgets_values": [
194
+ "Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors",
195
+ 1.0
196
+ ],
197
+ "title": "lightx2v LoRA (LOW)",
198
+ "inputs": [
199
+ {
200
+ "name": "model",
201
+ "type": "MODEL",
202
+ "link": 2
203
+ }
204
+ ],
205
+ "outputs": [
206
+ {
207
+ "name": "MODEL",
208
+ "type": "MODEL",
209
+ "links": [
210
+ 4
211
+ ],
212
+ "slot_index": 0
213
+ }
214
+ ]
215
+ },
216
+ {
217
+ "id": 7,
218
+ "type": "ModelSamplingSD3",
219
+ "pos": [
220
+ 720,
221
+ 250
222
+ ],
223
+ "size": [
224
+ 260,
225
+ 60
226
+ ],
227
+ "flags": {},
228
+ "order": 0,
229
+ "mode": 0,
230
+ "properties": {
231
+ "Node name for S&R": "ModelSamplingSD3"
232
+ },
233
+ "widgets_values": [
234
+ 5.0
235
+ ],
236
+ "title": "ModelSamplingSD3 (HIGH, shift=5)",
237
+ "inputs": [
238
+ {
239
+ "name": "model",
240
+ "type": "MODEL",
241
+ "link": 3
242
+ }
243
+ ],
244
+ "outputs": [
245
+ {
246
+ "name": "MODEL",
247
+ "type": "MODEL",
248
+ "links": [
249
+ 10,
250
+ 21
251
+ ],
252
+ "slot_index": 0
253
+ }
254
+ ]
255
+ },
256
+ {
257
+ "id": 8,
258
+ "type": "ModelSamplingSD3",
259
+ "pos": [
260
+ 720,
261
+ 350
262
+ ],
263
+ "size": [
264
+ 260,
265
+ 60
266
+ ],
267
+ "flags": {},
268
+ "order": 0,
269
+ "mode": 0,
270
+ "properties": {
271
+ "Node name for S&R": "ModelSamplingSD3"
272
+ },
273
+ "widgets_values": [
274
+ 5.0
275
+ ],
276
+ "title": "ModelSamplingSD3 (LOW, shift=5)",
277
+ "inputs": [
278
+ {
279
+ "name": "model",
280
+ "type": "MODEL",
281
+ "link": 4
282
+ }
283
+ ],
284
+ "outputs": [
285
+ {
286
+ "name": "MODEL",
287
+ "type": "MODEL",
288
+ "links": [
289
+ 14,
290
+ 25
291
+ ],
292
+ "slot_index": 0
293
+ }
294
+ ]
295
+ },
296
+ {
297
+ "id": 9,
298
+ "type": "CLIPTextEncode",
299
+ "pos": [
300
+ 360,
301
+ 0
302
+ ],
303
+ "size": [
304
+ 400,
305
+ 150
306
+ ],
307
+ "flags": {},
308
+ "order": 0,
309
+ "mode": 0,
310
+ "properties": {
311
+ "Node name for S&R": "CLIPTextEncode"
312
+ },
313
+ "widgets_values": [
314
+ "fast fluid continuous motion, real-time speed, no slow motion, no freeze"
315
+ ],
316
+ "title": "Positive prompt",
317
+ "inputs": [
318
+ {
319
+ "name": "clip",
320
+ "type": "CLIP",
321
+ "link": 5
322
+ }
323
+ ],
324
+ "outputs": [
325
+ {
326
+ "name": "CONDITIONING",
327
+ "type": "CONDITIONING",
328
+ "links": [
329
+ 11,
330
+ 15,
331
+ 22,
332
+ 26
333
+ ],
334
+ "slot_index": 0
335
+ }
336
+ ]
337
+ },
338
+ {
339
+ "id": 10,
340
+ "type": "CLIPTextEncode",
341
+ "pos": [
342
+ 800,
343
+ 0
344
+ ],
345
+ "size": [
346
+ 400,
347
+ 150
348
+ ],
349
+ "flags": {},
350
+ "order": 0,
351
+ "mode": 0,
352
+ "properties": {
353
+ "Node name for S&R": "CLIPTextEncode"
354
+ },
355
+ "widgets_values": [
356
+ "static, frozen, slow motion, low quality, blurry"
357
+ ],
358
+ "title": "Negative prompt",
359
+ "inputs": [
360
+ {
361
+ "name": "clip",
362
+ "type": "CLIP",
363
+ "link": 6
364
+ }
365
+ ],
366
+ "outputs": [
367
+ {
368
+ "name": "CONDITIONING",
369
+ "type": "CONDITIONING",
370
+ "links": [
371
+ 12,
372
+ 16,
373
+ 23,
374
+ 27
375
+ ],
376
+ "slot_index": 0
377
+ }
378
+ ]
379
+ },
380
+ {
381
+ "id": 11,
382
+ "type": "VHS_LoadVideo",
383
+ "pos": [
384
+ 0,
385
+ 450
386
+ ],
387
+ "size": [
388
+ 320,
389
+ 380
390
+ ],
391
+ "flags": {},
392
+ "order": 0,
393
+ "mode": 0,
394
+ "properties": {
395
+ "Node name for S&R": "VHS_LoadVideo"
396
+ },
397
+ "widgets_values": {
398
+ "video": "existing_animation.mp4",
399
+ "force_rate": 16,
400
+ "force_size": "Disabled",
401
+ "custom_width": 0,
402
+ "custom_height": 0,
403
+ "frame_load_cap": 81,
404
+ "skip_first_frames": 0,
405
+ "select_every_nth": 1
406
+ },
407
+ "title": "Load EXISTING animation (V2V source)",
408
+ "outputs": [
409
+ {
410
+ "name": "IMAGE",
411
+ "type": "IMAGE",
412
+ "links": [
413
+ 7
414
+ ],
415
+ "slot_index": 0
416
+ },
417
+ {
418
+ "name": "frame_count",
419
+ "type": "INT",
420
+ "links": [],
421
+ "slot_index": 1
422
+ },
423
+ {
424
+ "name": "audio",
425
+ "type": "AUDIO",
426
+ "links": [],
427
+ "slot_index": 2
428
+ },
429
+ {
430
+ "name": "video_info",
431
+ "type": "VHS_VIDEOINFO",
432
+ "links": [],
433
+ "slot_index": 3
434
+ }
435
+ ]
436
+ },
437
+ {
438
+ "id": 12,
439
+ "type": "WanNativeResize_Dolphin",
440
+ "pos": [
441
+ 360,
442
+ 450
443
+ ],
444
+ "size": [
445
+ 320,
446
+ 100
447
+ ],
448
+ "flags": {},
449
+ "order": 0,
450
+ "mode": 0,
451
+ "properties": {
452
+ "Node name for S&R": "WanNativeResize_Dolphin"
453
+ },
454
+ "widgets_values": [
455
+ "720P (1280x720)",
456
+ "lanczos",
457
+ 16
458
+ ],
459
+ "title": "Align source video to WAN resolution",
460
+ "inputs": [
461
+ {
462
+ "name": "image",
463
+ "type": "IMAGE",
464
+ "link": 7
465
+ }
466
+ ],
467
+ "outputs": [
468
+ {
469
+ "name": "IMAGE",
470
+ "type": "IMAGE",
471
+ "links": [
472
+ 8
473
+ ],
474
+ "slot_index": 0
475
+ },
476
+ {
477
+ "name": "width",
478
+ "type": "INT",
479
+ "links": [],
480
+ "slot_index": 1
481
+ },
482
+ {
483
+ "name": "height",
484
+ "type": "INT",
485
+ "links": [],
486
+ "slot_index": 2
487
+ }
488
+ ]
489
+ },
490
+ {
491
+ "id": 13,
492
+ "type": "VAEEncode",
493
+ "pos": [
494
+ 720,
495
+ 450
496
+ ],
497
+ "size": [
498
+ 260,
499
+ 60
500
+ ],
501
+ "flags": {},
502
+ "order": 0,
503
+ "mode": 0,
504
+ "properties": {
505
+ "Node name for S&R": "VAEEncode"
506
+ },
507
+ "widgets_values": [],
508
+ "title": "Encode existing animation -> latent",
509
+ "inputs": [
510
+ {
511
+ "name": "pixels",
512
+ "type": "IMAGE",
513
+ "link": 8
514
+ },
515
+ {
516
+ "name": "vae",
517
+ "type": "VAE",
518
+ "link": 9
519
+ }
520
+ ],
521
+ "outputs": [
522
+ {
523
+ "name": "LATENT",
524
+ "type": "LATENT",
525
+ "links": [
526
+ 13,
527
+ 24
528
+ ],
529
+ "slot_index": 0
530
+ }
531
+ ]
532
+ },
533
+ {
534
+ "id": 14,
535
+ "type": "KSamplerAdvanced",
536
+ "pos": [
537
+ 1100,
538
+ 250
539
+ ],
540
+ "size": [
541
+ 320,
542
+ 260
543
+ ],
544
+ "flags": {},
545
+ "order": 0,
546
+ "mode": 0,
547
+ "properties": {
548
+ "Node name for S&R": "KSamplerAdvanced"
549
+ },
550
+ "widgets_values": [
551
+ "enable",
552
+ 12345,
553
+ "fixed",
554
+ 6,
555
+ 1.0,
556
+ "euler",
557
+ "simple",
558
+ 3,
559
+ 4,
560
+ "enable"
561
+ ],
562
+ "title": "[CaseA] HIGH stage (denoise~0.5, steps 3->4)",
563
+ "inputs": [
564
+ {
565
+ "name": "model",
566
+ "type": "MODEL",
567
+ "link": 10
568
+ },
569
+ {
570
+ "name": "positive",
571
+ "type": "CONDITIONING",
572
+ "link": 11
573
+ },
574
+ {
575
+ "name": "negative",
576
+ "type": "CONDITIONING",
577
+ "link": 12
578
+ },
579
+ {
580
+ "name": "latent_image",
581
+ "type": "LATENT",
582
+ "link": 13
583
+ }
584
+ ],
585
+ "outputs": [
586
+ {
587
+ "name": "LATENT",
588
+ "type": "LATENT",
589
+ "links": [
590
+ 17
591
+ ],
592
+ "slot_index": 0
593
+ }
594
+ ]
595
+ },
596
+ {
597
+ "id": 15,
598
+ "type": "KSamplerAdvanced",
599
+ "pos": [
600
+ 1100,
601
+ 550
602
+ ],
603
+ "size": [
604
+ 320,
605
+ 260
606
+ ],
607
+ "flags": {},
608
+ "order": 0,
609
+ "mode": 0,
610
+ "properties": {
611
+ "Node name for S&R": "KSamplerAdvanced"
612
+ },
613
+ "widgets_values": [
614
+ "disable",
615
+ 12345,
616
+ "fixed",
617
+ 6,
618
+ 1.0,
619
+ "euler",
620
+ "simple",
621
+ 4,
622
+ 6,
623
+ "disable"
624
+ ],
625
+ "title": "[CaseA] LOW stage (steps 4->6)",
626
+ "inputs": [
627
+ {
628
+ "name": "model",
629
+ "type": "MODEL",
630
+ "link": 14
631
+ },
632
+ {
633
+ "name": "positive",
634
+ "type": "CONDITIONING",
635
+ "link": 15
636
+ },
637
+ {
638
+ "name": "negative",
639
+ "type": "CONDITIONING",
640
+ "link": 16
641
+ },
642
+ {
643
+ "name": "latent_image",
644
+ "type": "LATENT",
645
+ "link": 17
646
+ }
647
+ ],
648
+ "outputs": [
649
+ {
650
+ "name": "LATENT",
651
+ "type": "LATENT",
652
+ "links": [
653
+ 18
654
+ ],
655
+ "slot_index": 0
656
+ }
657
+ ]
658
+ },
659
+ {
660
+ "id": 16,
661
+ "type": "VAEDecode",
662
+ "pos": [
663
+ 1100,
664
+ 850
665
+ ],
666
+ "size": [
667
+ 260,
668
+ 60
669
+ ],
670
+ "flags": {},
671
+ "order": 0,
672
+ "mode": 0,
673
+ "properties": {
674
+ "Node name for S&R": "VAEDecode"
675
+ },
676
+ "widgets_values": [],
677
+ "title": "[CaseA] Decode",
678
+ "inputs": [
679
+ {
680
+ "name": "samples",
681
+ "type": "LATENT",
682
+ "link": 18
683
+ },
684
+ {
685
+ "name": "vae",
686
+ "type": "VAE",
687
+ "link": 19
688
+ }
689
+ ],
690
+ "outputs": [
691
+ {
692
+ "name": "IMAGE",
693
+ "type": "IMAGE",
694
+ "links": [
695
+ 20
696
+ ],
697
+ "slot_index": 0
698
+ }
699
+ ]
700
+ },
701
+ {
702
+ "id": 17,
703
+ "type": "VHS_VideoCombine",
704
+ "pos": [
705
+ 1100,
706
+ 950
707
+ ],
708
+ "size": [
709
+ 320,
710
+ 260
711
+ ],
712
+ "flags": {},
713
+ "order": 0,
714
+ "mode": 0,
715
+ "properties": {
716
+ "Node name for S&R": "VHS_VideoCombine"
717
+ },
718
+ "widgets_values": {
719
+ "frame_rate": 16,
720
+ "loop_count": 0,
721
+ "filename_prefix": "wan22_v2v_CaseA_denoise0.5",
722
+ "format": "video/h264-mp4",
723
+ "pingpong": false,
724
+ "save_image": true,
725
+ "crf": 19,
726
+ "save_metadata": true,
727
+ "audio_file": ""
728
+ },
729
+ "title": "[CaseA] Save (denoise~0.5)",
730
+ "inputs": [
731
+ {
732
+ "name": "images",
733
+ "type": "IMAGE",
734
+ "link": 20
735
+ }
736
+ ]
737
+ },
738
+ {
739
+ "id": 18,
740
+ "type": "KSamplerAdvanced",
741
+ "pos": [
742
+ 1500,
743
+ 250
744
+ ],
745
+ "size": [
746
+ 320,
747
+ 260
748
+ ],
749
+ "flags": {},
750
+ "order": 0,
751
+ "mode": 0,
752
+ "properties": {
753
+ "Node name for S&R": "KSamplerAdvanced"
754
+ },
755
+ "widgets_values": [
756
+ "enable",
757
+ 12345,
758
+ "fixed",
759
+ 6,
760
+ 1.0,
761
+ "euler",
762
+ "simple",
763
+ 2,
764
+ 4,
765
+ "enable"
766
+ ],
767
+ "title": "[CaseB] HIGH stage (denoise~0.67, steps 2->4)",
768
+ "inputs": [
769
+ {
770
+ "name": "model",
771
+ "type": "MODEL",
772
+ "link": 21
773
+ },
774
+ {
775
+ "name": "positive",
776
+ "type": "CONDITIONING",
777
+ "link": 22
778
+ },
779
+ {
780
+ "name": "negative",
781
+ "type": "CONDITIONING",
782
+ "link": 23
783
+ },
784
+ {
785
+ "name": "latent_image",
786
+ "type": "LATENT",
787
+ "link": 24
788
+ }
789
+ ],
790
+ "outputs": [
791
+ {
792
+ "name": "LATENT",
793
+ "type": "LATENT",
794
+ "links": [
795
+ 28
796
+ ],
797
+ "slot_index": 0
798
+ }
799
+ ]
800
+ },
801
+ {
802
+ "id": 19,
803
+ "type": "KSamplerAdvanced",
804
+ "pos": [
805
+ 1500,
806
+ 550
807
+ ],
808
+ "size": [
809
+ 320,
810
+ 260
811
+ ],
812
+ "flags": {},
813
+ "order": 0,
814
+ "mode": 0,
815
+ "properties": {
816
+ "Node name for S&R": "KSamplerAdvanced"
817
+ },
818
+ "widgets_values": [
819
+ "disable",
820
+ 12345,
821
+ "fixed",
822
+ 6,
823
+ 1.0,
824
+ "euler",
825
+ "simple",
826
+ 4,
827
+ 6,
828
+ "disable"
829
+ ],
830
+ "title": "[CaseB] LOW stage (steps 4->6)",
831
+ "inputs": [
832
+ {
833
+ "name": "model",
834
+ "type": "MODEL",
835
+ "link": 25
836
+ },
837
+ {
838
+ "name": "positive",
839
+ "type": "CONDITIONING",
840
+ "link": 26
841
+ },
842
+ {
843
+ "name": "negative",
844
+ "type": "CONDITIONING",
845
+ "link": 27
846
+ },
847
+ {
848
+ "name": "latent_image",
849
+ "type": "LATENT",
850
+ "link": 28
851
+ }
852
+ ],
853
+ "outputs": [
854
+ {
855
+ "name": "LATENT",
856
+ "type": "LATENT",
857
+ "links": [
858
+ 29
859
+ ],
860
+ "slot_index": 0
861
+ }
862
+ ]
863
+ },
864
+ {
865
+ "id": 20,
866
+ "type": "VAEDecode",
867
+ "pos": [
868
+ 1500,
869
+ 850
870
+ ],
871
+ "size": [
872
+ 260,
873
+ 60
874
+ ],
875
+ "flags": {},
876
+ "order": 0,
877
+ "mode": 0,
878
+ "properties": {
879
+ "Node name for S&R": "VAEDecode"
880
+ },
881
+ "widgets_values": [],
882
+ "title": "[CaseB] Decode",
883
+ "inputs": [
884
+ {
885
+ "name": "samples",
886
+ "type": "LATENT",
887
+ "link": 29
888
+ },
889
+ {
890
+ "name": "vae",
891
+ "type": "VAE",
892
+ "link": 30
893
+ }
894
+ ],
895
+ "outputs": [
896
+ {
897
+ "name": "IMAGE",
898
+ "type": "IMAGE",
899
+ "links": [
900
+ 31
901
+ ],
902
+ "slot_index": 0
903
+ }
904
+ ]
905
+ },
906
+ {
907
+ "id": 21,
908
+ "type": "VHS_VideoCombine",
909
+ "pos": [
910
+ 1500,
911
+ 950
912
+ ],
913
+ "size": [
914
+ 320,
915
+ 260
916
+ ],
917
+ "flags": {},
918
+ "order": 0,
919
+ "mode": 0,
920
+ "properties": {
921
+ "Node name for S&R": "VHS_VideoCombine"
922
+ },
923
+ "widgets_values": {
924
+ "frame_rate": 16,
925
+ "loop_count": 0,
926
+ "filename_prefix": "wan22_v2v_CaseB_denoise0.67",
927
+ "format": "video/h264-mp4",
928
+ "pingpong": false,
929
+ "save_image": true,
930
+ "crf": 19,
931
+ "save_metadata": true,
932
+ "audio_file": ""
933
+ },
934
+ "title": "[CaseB] Save (denoise~0.67)",
935
+ "inputs": [
936
+ {
937
+ "name": "images",
938
+ "type": "IMAGE",
939
+ "link": 31
940
+ }
941
+ ]
942
+ }
943
+ ],
944
+ "links": [
945
+ [
946
+ 1,
947
+ 3,
948
+ 0,
949
+ 5,
950
+ 0,
951
+ "MODEL"
952
+ ],
953
+ [
954
+ 2,
955
+ 4,
956
+ 0,
957
+ 6,
958
+ 0,
959
+ "MODEL"
960
+ ],
961
+ [
962
+ 3,
963
+ 5,
964
+ 0,
965
+ 7,
966
+ 0,
967
+ "MODEL"
968
+ ],
969
+ [
970
+ 4,
971
+ 6,
972
+ 0,
973
+ 8,
974
+ 0,
975
+ "MODEL"
976
+ ],
977
+ [
978
+ 5,
979
+ 1,
980
+ 0,
981
+ 9,
982
+ 0,
983
+ "CLIP"
984
+ ],
985
+ [
986
+ 6,
987
+ 1,
988
+ 0,
989
+ 10,
990
+ 0,
991
+ "CLIP"
992
+ ],
993
+ [
994
+ 7,
995
+ 11,
996
+ 0,
997
+ 12,
998
+ 0,
999
+ "IMAGE"
1000
+ ],
1001
+ [
1002
+ 8,
1003
+ 12,
1004
+ 0,
1005
+ 13,
1006
+ 0,
1007
+ "IMAGE"
1008
+ ],
1009
+ [
1010
+ 9,
1011
+ 2,
1012
+ 0,
1013
+ 13,
1014
+ 1,
1015
+ "VAE"
1016
+ ],
1017
+ [
1018
+ 10,
1019
+ 7,
1020
+ 0,
1021
+ 14,
1022
+ 0,
1023
+ "MODEL"
1024
+ ],
1025
+ [
1026
+ 11,
1027
+ 9,
1028
+ 0,
1029
+ 14,
1030
+ 1,
1031
+ "CONDITIONING"
1032
+ ],
1033
+ [
1034
+ 12,
1035
+ 10,
1036
+ 0,
1037
+ 14,
1038
+ 2,
1039
+ "CONDITIONING"
1040
+ ],
1041
+ [
1042
+ 13,
1043
+ 13,
1044
+ 0,
1045
+ 14,
1046
+ 3,
1047
+ "LATENT"
1048
+ ],
1049
+ [
1050
+ 14,
1051
+ 8,
1052
+ 0,
1053
+ 15,
1054
+ 0,
1055
+ "MODEL"
1056
+ ],
1057
+ [
1058
+ 15,
1059
+ 9,
1060
+ 0,
1061
+ 15,
1062
+ 1,
1063
+ "CONDITIONING"
1064
+ ],
1065
+ [
1066
+ 16,
1067
+ 10,
1068
+ 0,
1069
+ 15,
1070
+ 2,
1071
+ "CONDITIONING"
1072
+ ],
1073
+ [
1074
+ 17,
1075
+ 14,
1076
+ 0,
1077
+ 15,
1078
+ 3,
1079
+ "LATENT"
1080
+ ],
1081
+ [
1082
+ 18,
1083
+ 15,
1084
+ 0,
1085
+ 16,
1086
+ 0,
1087
+ "LATENT"
1088
+ ],
1089
+ [
1090
+ 19,
1091
+ 2,
1092
+ 0,
1093
+ 16,
1094
+ 1,
1095
+ "VAE"
1096
+ ],
1097
+ [
1098
+ 20,
1099
+ 16,
1100
+ 0,
1101
+ 17,
1102
+ 0,
1103
+ "IMAGE"
1104
+ ],
1105
+ [
1106
+ 21,
1107
+ 7,
1108
+ 0,
1109
+ 18,
1110
+ 0,
1111
+ "MODEL"
1112
+ ],
1113
+ [
1114
+ 22,
1115
+ 9,
1116
+ 0,
1117
+ 18,
1118
+ 1,
1119
+ "CONDITIONING"
1120
+ ],
1121
+ [
1122
+ 23,
1123
+ 10,
1124
+ 0,
1125
+ 18,
1126
+ 2,
1127
+ "CONDITIONING"
1128
+ ],
1129
+ [
1130
+ 24,
1131
+ 13,
1132
+ 0,
1133
+ 18,
1134
+ 3,
1135
+ "LATENT"
1136
+ ],
1137
+ [
1138
+ 25,
1139
+ 8,
1140
+ 0,
1141
+ 19,
1142
+ 0,
1143
+ "MODEL"
1144
+ ],
1145
+ [
1146
+ 26,
1147
+ 9,
1148
+ 0,
1149
+ 19,
1150
+ 1,
1151
+ "CONDITIONING"
1152
+ ],
1153
+ [
1154
+ 27,
1155
+ 10,
1156
+ 0,
1157
+ 19,
1158
+ 2,
1159
+ "CONDITIONING"
1160
+ ],
1161
+ [
1162
+ 28,
1163
+ 18,
1164
+ 0,
1165
+ 19,
1166
+ 3,
1167
+ "LATENT"
1168
+ ],
1169
+ [
1170
+ 29,
1171
+ 19,
1172
+ 0,
1173
+ 20,
1174
+ 0,
1175
+ "LATENT"
1176
+ ],
1177
+ [
1178
+ 30,
1179
+ 2,
1180
+ 0,
1181
+ 20,
1182
+ 1,
1183
+ "VAE"
1184
+ ],
1185
+ [
1186
+ 31,
1187
+ 20,
1188
+ 0,
1189
+ 21,
1190
+ 0,
1191
+ "IMAGE"
1192
+ ]
1193
+ ],
1194
+ "groups": [],
1195
+ "config": {},
1196
+ "extra": {},
1197
+ "version": 0.4
1198
+ }
arch_logic.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import folder_paths
4
+ import comfy.sd
5
+ import comfy.utils
6
+ from safetensors import safe_open
7
+ import hashlib
8
+ import urllib.request
9
+
10
+ CACHE_FILE = os.path.join(os.path.dirname(__file__), "dolphin_tags_cache.json")
11
+
12
+ def load_dolphin_cache():
13
+ if os.path.exists(CACHE_FILE):
14
+ try:
15
+ with open(CACHE_FILE, "r", encoding="utf-8") as f:
16
+ return json.load(f)
17
+ except: return {}
18
+ return {}
19
+
20
+ def save_dolphin_cache(cache_data):
21
+ try:
22
+ with open(CACHE_FILE, "w", encoding="utf-8") as f:
23
+ json.dump(cache_data, f, indent=4)
24
+ except: pass
25
+
26
+ class DolphinArchitectV15:
27
+ @classmethod
28
+ def INPUT_TYPES(s):
29
+ loras = ["None"] + folder_paths.get_filename_list("loras")
30
+
31
+ res = {
32
+ "required": {
33
+ "global_switch": ("BOOLEAN", {"default": True, "label_on": "GLOBAL ON", "label_off": "GLOBAL OFF (SKIP)"}),
34
+ "model_base": ("MODEL",), "clip_base": ("CLIP",),
35
+ "model_high": ("MODEL",), "clip_high": ("CLIP",),
36
+ "model_low": ("MODEL",), "clip_low": ("CLIP",),
37
+ },
38
+ "optional": {}
39
+ }
40
+
41
+ # [다이어트 완료] 오직 이름과 웨이트만 남겨서 가장 얇은 UI를 만듭니다.
42
+ res["optional"]["header_master"] = ("STRING", {"default": "─── [ MASTER: B+H ] ───", "multiline": False})
43
+ for i in range(1, 5):
44
+ res["optional"][f"master_l_{i}"] = (loras, {"default": "None"})
45
+ res["optional"][f"master_b_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
46
+ res["optional"][f"master_h_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
47
+
48
+ res["optional"]["header_low"] = ("STRING", {"default": "─── [ LOW ONLY ] ───", "multiline": False})
49
+ for i in range(1, 5):
50
+ res["optional"][f"low_l_{i}"] = (loras, {"default": "None"})
51
+ res["optional"][f"low_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
52
+
53
+ res["optional"]["manual_triggers"] = ("STRING", {"default": "", "multiline": True})
54
+ return res
55
+
56
+ RETURN_TYPES = ("MODEL", "CLIP", "MODEL", "CLIP", "MODEL", "CLIP", "STRING")
57
+ RETURN_NAMES = ("M_BASE", "C_BASE", "M_HIGH", "C_HIGH", "M_LOW", "C_LOW", "triggers")
58
+ FUNCTION = "exec"
59
+ CATEGORY = "Dolphin"
60
+
61
+ def get_trig(self, lora_name):
62
+ if lora_name == "None": return []
63
+ path = folder_paths.get_full_path("loras", lora_name)
64
+ if not path or not os.path.exists(path): return []
65
+
66
+ triggers = []
67
+ try:
68
+ with safe_open(path, framework="pt", device="cpu") as f:
69
+ m = f.metadata()
70
+ if m and "modelspec.trigger_words" in m:
71
+ tw = json.loads(m["modelspec.trigger_words"])
72
+ if isinstance(tw, list): triggers.extend(tw)
73
+ elif isinstance(tw, str): triggers.append(tw)
74
+ except: pass
75
+ if triggers: return triggers
76
+
77
+ cache = load_dolphin_cache()
78
+ if lora_name in cache and cache[lora_name]: return cache[lora_name]
79
+
80
+ try:
81
+ sha256_hash = hashlib.sha256()
82
+ with open(path, "rb") as f:
83
+ for byte_block in iter(lambda: f.read(4096 * 1024), b""):
84
+ sha256_hash.update(byte_block)
85
+
86
+ url = f"https://civitai.com/api/v1/model-versions/by-hash/{sha256_hash.hexdigest()}"
87
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
88
+ with urllib.request.urlopen(req, timeout=5) as response:
89
+ data = json.loads(response.read().decode('utf-8'))
90
+ api_triggers = data.get("trainedWords", [])
91
+ if api_triggers:
92
+ cache[lora_name] = api_triggers
93
+ save_dolphin_cache(cache)
94
+ return api_triggers
95
+ except: pass
96
+ return triggers
97
+
98
+ def exec(self, global_switch, model_base, clip_base, model_high, clip_high, model_low, clip_low, manual_triggers="", **kwargs):
99
+ mb, cb = model_base, clip_base
100
+ mh, ch = model_high, clip_high
101
+ ml, cl = model_low, clip_low
102
+
103
+ if not global_switch:
104
+ trigs = [t.strip() for t in manual_triggers.split(',') if t.strip()]
105
+ ut = ", ".join(list(dict.fromkeys(trigs)))
106
+ return (mb, cb, mh, ch, ml, cl, ut)
107
+
108
+ applied_names = []
109
+
110
+ for i in range(1, 5):
111
+ n = kwargs.get(f"master_l_{i}", "None")
112
+ w_b = kwargs.get(f"master_b_w_{i}", 1.0)
113
+ w_h = kwargs.get(f"master_h_w_{i}", 1.0)
114
+
115
+ # [최적화] None이 아니면서, 웨이트가 0.0이 아닐 때만 적용 (0.0이면 스위치 OFF 효과)
116
+ if n != "None" and (w_b != 0.0 or w_h != 0.0):
117
+ applied_names.append(n)
118
+ lora_data = comfy.utils.load_torch_file(folder_paths.get_full_path("loras", n))
119
+ mb, cb = comfy.sd.load_lora_for_models(mb, cb, lora_data, w_b, w_b)
120
+ mh, ch = comfy.sd.load_lora_for_models(mh, ch, lora_data, w_h, w_h)
121
+
122
+ for i in range(1, 5):
123
+ n = kwargs.get(f"low_l_{i}", "None")
124
+ w = kwargs.get(f"low_w_{i}", 1.0)
125
+
126
+ if n != "None" and w != 0.0:
127
+ applied_names.append(n)
128
+ lora_data = comfy.utils.load_torch_file(folder_paths.get_full_path("loras", n))
129
+ ml, cl = comfy.sd.load_lora_for_models(ml, cl, lora_data, w, w)
130
+
131
+ trigs = []
132
+ for n in set(applied_names):
133
+ trigs.extend(self.get_trig(n))
134
+
135
+ if manual_triggers.strip():
136
+ manual_list = [t.strip() for t in manual_triggers.split(',') if t.strip()]
137
+ trigs.extend(manual_list)
138
+
139
+ unique_trigs = ", ".join(list(dict.fromkeys(trigs)))
140
+
141
+ return (mb, cb, mh, ch, ml, cl, unique_trigs)
arch_logic_v16.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import comfy.sd
3
+ import comfy.utils
4
+ import folder_paths
5
+
6
+ # civitai/civitaired 파일명 쿼리 방식은 개인 학습 로라(공개 DB 미등록)에 대해
7
+ # 항상 빈 값으로 실패한다. DolphinVisionDirector가 이미 검증한 조회 순서
8
+ # (safetensors 임베디드 메타데이터 -> LoRA Manager 로컬 SQLite -> 해시 기반 API)를
9
+ # 그대로 재사용한다 - 중복 구현하면 두 노드의 트리거 조회 결과가 따로 놀게 됨.
10
+ from .dolphin_vision_director import get_triggers, _load_cache, _save_cache
11
+
12
+ _TRIPLE_LORA_CACHE = {}
13
+
14
+ class DolphinTripleLoraMatrix:
15
+ @classmethod
16
+ def INPUT_TYPES(s):
17
+ loras = ["None"] + folder_paths.get_filename_list("loras")
18
+ inputs = {
19
+ "model_base": ("MODEL",), "clip_base": ("CLIP",),
20
+ "model_high": ("MODEL",), "clip_high": ("CLIP",),
21
+ "model_low": ("MODEL",), "clip_low": ("CLIP",),
22
+ }
23
+ for i in range(1, 7):
24
+ inputs[f"lora_{i}"] = (loras, {"default": "None"})
25
+ # 토글을 이름 바로 옆(가중치 앞)에 붙임 - Vision Director(c{c}_lora_{n},
26
+ # c{c}_lora_{n}_on, c{c}_w_{n} 순서)와 동일한 배치. optional 위젯은 항상
27
+ # required 위젯 전부 뒤에 렌더링되므로 "이름 옆"이 되려면 required여야 한다.
28
+ # 이 재배치로 위젯 순서가 바뀌므로, 이미 저장된 워크플로우의 widgets_values는
29
+ # migrate_architect_v16_widgets.py로 재배치해야 한다 (구버전 노드는 이름이
30
+ # 아니라 위치로 값이 매핑됨).
31
+ inputs[f"lora_{i}_on"] = ("BOOLEAN", {"default": True,
32
+ "label_on": "ON", "label_off": "OFF"})
33
+ inputs[f"weights_{i}"] = ("STRING", {"default": "0.0, 0.0, 0.0"})
34
+ opt = {
35
+ # civitai/civitaired에 등록 안 된 개인 학습 로라는 자동 트리거 조회가
36
+ # 항상 빈 값으로 실패한다. 그런 경우 여기에 콤마로 직접 적으면
37
+ # trigger_words 출력에 합쳐진다.
38
+ "manual_triggers": ("STRING", {"default": "", "multiline": True}),
39
+ }
40
+ return {"required": inputs, "optional": opt}
41
+
42
+ RETURN_TYPES = ("MODEL", "CLIP", "MODEL", "CLIP", "MODEL", "CLIP", "STRING")
43
+ RETURN_NAMES = ("M_BASE", "C_BASE", "M_HIGH", "C_HIGH", "M_LOW", "C_LOW", "trigger_words")
44
+ FUNCTION = "apply_matrix"
45
+ CATEGORY = "Dolphin"
46
+
47
+ def apply_matrix(self, model_base, clip_base, model_high, clip_high, model_low, clip_low, **kwargs):
48
+ m_b, c_b = model_base, clip_base
49
+ m_h, c_h = model_high, clip_high
50
+ m_l, c_l = model_low, clip_low
51
+ all_triggers = []
52
+ trigger_cache = _load_cache()
53
+
54
+ for i in range(1, 7):
55
+ lora_name = kwargs.get(f"lora_{i}")
56
+ w_str = kwargs.get(f"weights_{i}")
57
+
58
+ if lora_name == "None": continue
59
+ if not kwargs.get(f"lora_{i}_on", True): continue
60
+
61
+ # 트리거 워드 수집 (safetensors 메타데이터 -> LoRA Manager DB -> 해시 API)
62
+ all_triggers.extend(get_triggers(lora_name, trigger_cache))
63
+
64
+ try:
65
+ parts = [float(x.strip()) for x in w_str.split(',')]
66
+ wb, wh, wl = parts[0], parts[1], parts[2]
67
+ except: continue
68
+
69
+ if lora_name not in _TRIPLE_LORA_CACHE:
70
+ path = folder_paths.get_full_path("loras", lora_name)
71
+ _TRIPLE_LORA_CACHE[lora_name] = comfy.utils.load_torch_file(path)
72
+ data = _TRIPLE_LORA_CACHE[lora_name]
73
+
74
+ if wb != 0: m_b, c_b = comfy.sd.load_lora_for_models(m_b, c_b, data, wb, wb)
75
+ if wh != 0: m_h, c_h = comfy.sd.load_lora_for_models(m_h, c_h, data, wh, wh)
76
+ if wl != 0: m_l, c_l = comfy.sd.load_lora_for_models(m_l, c_l, data, wl, wl)
77
+
78
+ manual = [t.strip() for t in kwargs.get("manual_triggers", "").split(',') if t.strip()]
79
+ all_triggers = list(dict.fromkeys(all_triggers + manual))
80
+ return (m_b, c_b, m_h, c_h, m_l, c_l, ", ".join(all_triggers))
disk_cache_provider.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 디스크 기반 노드 출력 캐시 (ComfyUI cache provider API).
4
+
5
+ ComfyUI는 노드 출력을 "입력 서명"(위젯 값 + 모든 조상 노드의 서명, 세션 간
6
+ 결정적 SHA256)으로 캐싱하는데, 그 캐시는 메모리에만 있어서 (1) 재시작하면
7
+ 사라지고 (2) 기본 RAM_PRESSURE 모드에서는 RAM이 부족하면 수시로 증발한다.
8
+
9
+ 이 프로바이더는 캐시 저장 시 디스크에도 쓰고, 메모리 캐시 미스 시 디스크에서
10
+ 복원한다. 효과:
11
+ - 재시작해도 시드/프롬프트/이미지가 같은 클립은 JoyCaption부터 샘플러까지
12
+ 전부 스킵 (실행되지 않고 디스크에서 결과만 복원)
13
+ - 시드 하나만 바꾸면 정확히 그 클립 체인만 다시 생성 (서명에 모든 입력이
14
+ 포함되므로 무효화 판정은 ComfyUI가 알아서 정확하게 해줌)
15
+ - MODEL/CLIP/VAE/NOISE 같은 객체 출력은 직렬화 불가로 자동 제외되어 저장 안 됨
16
+
17
+ 캐시 위치/용량은 환경변수로 조정:
18
+ DOLPHIN_DISK_CACHE_DIR (기본: <output>/_node_disk_cache)
19
+ DOLPHIN_DISK_CACHE_MAX_GB (기본: 40 — 초과 시 오래된 것부터 삭제)
20
+ DOLPHIN_DISK_CACHE_MAX_ENTRY_GB (기본: 2 — 이보다 큰 단일 출력은 저장 안 함)
21
+ """
22
+ import os
23
+ import time
24
+ import asyncio
25
+ import logging
26
+
27
+ import torch
28
+
29
+ import folder_paths
30
+ from comfy_execution.cache_provider import register_cache_provider
31
+ from comfy_api.latest._caching import CacheProvider, CacheValue
32
+
33
+ log = logging.getLogger("dolphin.diskcache")
34
+
35
+ _DEFAULT_DIR = os.path.join(folder_paths.get_output_directory(), "_node_disk_cache")
36
+ CACHE_DIR = os.environ.get("DOLPHIN_DISK_CACHE_DIR", _DEFAULT_DIR)
37
+ MAX_TOTAL_BYTES = int(float(os.environ.get("DOLPHIN_DISK_CACHE_MAX_GB", "15")) * (1024 ** 3))
38
+ # VAEDecode/이미지 배치 결과(수백MB~1GB, 디코드된 프레임)는 일부러 상한 아래로 뒀다.
39
+ # 비싼 건 디퓨전 샘플링(latent, 수십MB)이고 VAE 디코드는 latent만 있으면 몇 초면
40
+ # 다시 되므로, 큰 디코드 결과까지 디스크에 쌓아둘 실익이 없다 - 용량만 먹는다.
41
+ MAX_ENTRY_BYTES = int(float(os.environ.get("DOLPHIN_DISK_CACHE_MAX_ENTRY_GB", "0.2")) * (1024 ** 3))
42
+
43
+ _ALLOWED_SCALARS = (str, int, float, bool, bytes, type(None))
44
+
45
+
46
+ def _serializable(obj):
47
+ """텐서/스칼라/컨테이너만 허용. MODEL, NOISE, SAMPLER 등 객체가 섞이면 False."""
48
+ if isinstance(obj, torch.Tensor):
49
+ return True
50
+ if isinstance(obj, _ALLOWED_SCALARS):
51
+ return True
52
+ if isinstance(obj, (list, tuple)):
53
+ return all(_serializable(x) for x in obj)
54
+ if isinstance(obj, dict):
55
+ return all(isinstance(k, _ALLOWED_SCALARS) and _serializable(v) for k, v in obj.items())
56
+ return False
57
+
58
+
59
+ def _to_cpu(obj):
60
+ if isinstance(obj, torch.Tensor):
61
+ return obj.detach().to("cpu")
62
+ if isinstance(obj, list):
63
+ return [_to_cpu(x) for x in obj]
64
+ if isinstance(obj, tuple):
65
+ return tuple(_to_cpu(x) for x in obj)
66
+ if isinstance(obj, dict):
67
+ return {k: _to_cpu(v) for k, v in obj.items()}
68
+ return obj
69
+
70
+
71
+ def _tensor_bytes(obj):
72
+ if isinstance(obj, torch.Tensor):
73
+ return obj.numel() * obj.element_size()
74
+ if isinstance(obj, dict):
75
+ return sum(_tensor_bytes(v) for v in obj.values())
76
+ if isinstance(obj, (list, tuple)):
77
+ return sum(_tensor_bytes(v) for v in obj)
78
+ return 0
79
+
80
+
81
+ class DolphinDiskCache(CacheProvider):
82
+ def __init__(self, directory=CACHE_DIR):
83
+ self.dir = directory
84
+ os.makedirs(self.dir, exist_ok=True)
85
+ self.log_path = os.path.join(self.dir, "_diskcache.log")
86
+
87
+ def _flog(self, msg):
88
+ # 콘솔이 안 보이는 환경에서도 저장/스킵 경로를 추적할 수 있게 파일로 남긴다.
89
+ # 주의: should_cache가 아예 호출 안 된 노드는 ComfyUI 쪽 NaN 키 게이트
90
+ # (_contains_self_unequal)에서 걸러진 것 - 로그에 안 찍히는 것 자체가 단서.
91
+ try:
92
+ with open(self.log_path, "a", encoding="utf-8") as f:
93
+ f.write(f"{time.strftime('%H:%M:%S')} {msg}\n")
94
+ except OSError:
95
+ pass
96
+
97
+ def _path(self, context):
98
+ return os.path.join(self.dir, f"{context.cache_key_hash}.pt")
99
+
100
+ async def on_lookup(self, context):
101
+ path = self._path(context)
102
+ if not os.path.isfile(path):
103
+ return None
104
+
105
+ def load():
106
+ return torch.load(path, map_location="cpu", weights_only=False)
107
+
108
+ try:
109
+ data = await asyncio.to_thread(load)
110
+ except Exception as e:
111
+ log.warning(f"[DiskCache] 손상된 캐시 삭제 ({context.class_type}): {e}")
112
+ try:
113
+ os.remove(path)
114
+ except OSError:
115
+ pass
116
+ return None
117
+ try:
118
+ os.utime(path, None) # LRU: 접근 시각 갱신 (prune 시 오래된 것부터 삭제)
119
+ except OSError:
120
+ pass
121
+ print(f"💾 [DiskCache] {context.class_type} 디스크에서 복원 (node {context.node_id})")
122
+ self._flog(f"RESTORE {context.class_type} (node {context.node_id})")
123
+ return CacheValue(outputs=data["outputs"], ui=data.get("ui"))
124
+
125
+ def should_cache(self, context, value=None):
126
+ if value is None: # lookup 시점 - 파일 존재 여부로 판단하므로 항상 시도
127
+ return True
128
+ if not _serializable(value.outputs):
129
+ self._flog(f"SKIP not-serializable {context.class_type} (node {context.node_id})")
130
+ return False
131
+ size = _tensor_bytes(value.outputs)
132
+ if size > MAX_ENTRY_BYTES:
133
+ self._flog(f"SKIP too-big {context.class_type} {size/1024/1024:.0f}MB (node {context.node_id})")
134
+ return False
135
+ self._flog(f"STORE-OK {context.class_type} {size/1024/1024:.1f}MB (node {context.node_id})")
136
+ return True
137
+
138
+ async def on_store(self, context, value):
139
+ path = self._path(context)
140
+ if os.path.exists(path):
141
+ return
142
+ try:
143
+ payload = {
144
+ "outputs": _to_cpu(value.outputs),
145
+ "ui": value.ui,
146
+ "class_type": context.class_type,
147
+ "saved_at": time.time(),
148
+ }
149
+
150
+ def save():
151
+ tmp = path + ".tmp"
152
+ torch.save(payload, tmp)
153
+ os.replace(tmp, path)
154
+
155
+ await asyncio.to_thread(save)
156
+ self._flog(f"SAVED {context.class_type} (node {context.node_id})")
157
+ except Exception as e:
158
+ log.warning(f"[DiskCache] 저장 실패 ({context.class_type}): {e}")
159
+ self._flog(f"SAVE-FAIL {context.class_type} (node {context.node_id}): {type(e).__name__}: {e}")
160
+ try:
161
+ os.remove(path + ".tmp")
162
+ except OSError:
163
+ pass
164
+
165
+ def on_prompt_end(self, prompt_id):
166
+ try:
167
+ self._prune()
168
+ except Exception as e:
169
+ log.warning(f"[DiskCache] prune 실패: {e}")
170
+
171
+ def _prune(self):
172
+ entries = []
173
+ total = 0
174
+ with os.scandir(self.dir) as it:
175
+ for e in it:
176
+ if e.name.endswith(".pt") and e.is_file():
177
+ st = e.stat()
178
+ entries.append((st.st_mtime, st.st_size, e.path))
179
+ total += st.st_size
180
+ if total <= MAX_TOTAL_BYTES:
181
+ return
182
+ entries.sort() # mtime 오래된 순
183
+ for _, size, path in entries:
184
+ try:
185
+ os.remove(path)
186
+ total -= size
187
+ except OSError:
188
+ pass
189
+ if total <= MAX_TOTAL_BYTES:
190
+ break
191
+
192
+
193
+ _provider = None
194
+
195
+
196
+ def register():
197
+ global _provider
198
+ if _provider is None:
199
+ _provider = DolphinDiskCache()
200
+ register_cache_provider(_provider)
201
+ print(f"💾 [Dolphin] 디스크 노드 캐시 활성화: {_provider.dir}")
dolphin_director.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ import hashlib
5
+ import urllib.request
6
+ import urllib.error
7
+
8
+ import folder_paths
9
+ import comfy.sd
10
+ import comfy.lora
11
+ import comfy.utils
12
+ from safetensors import safe_open
13
+
14
+ try:
15
+ from .promp_logic_v32_story import MODEL_PRESETS, MODEL_PRESET_LABELS, resolve_model
16
+ except Exception:
17
+ MODEL_PRESETS = {"🔞 DeepSeek V3.2": "deepseek/deepseek-v3.2"}
18
+ MODEL_PRESET_LABELS = list(MODEL_PRESETS.keys())
19
+ def resolve_model(p, c): return c if p == "custom" else "deepseek/deepseek-v3.2"
20
+
21
+ class DolphinCinematicDirector:
22
+ @classmethod
23
+ def INPUT_TYPES(s):
24
+ loras = ["None"] + folder_paths.get_filename_list("loras")
25
+ return {
26
+ "required": {
27
+ "model_base": ("MODEL",), "model_high": ("MODEL",), "model_low": ("MODEL",),
28
+ "mode": (["🤖 Auto LLM (Story Splitter)", "✍️ Manual"], {"default": "🤖 Auto LLM (Story Splitter)"}),
29
+ "master_story": ("STRING", {"multiline": True}),
30
+ "tagger_context": ("STRING", {"multiline": True}),
31
+ "openrouter_api_key": ("STRING", {"default": ""}),
32
+ },
33
+ "optional": {
34
+ "external_triggers": ("STRING", {"forceInput": True}),
35
+ }
36
+ }
37
+
38
+ RETURN_TYPES = ("MODEL", "MODEL", "MODEL", "STRING") * 4 + ("STRING",)
39
+ RETURN_NAMES = tuple([f"c{i}_{n}" for i in range(1, 5) for n in ["base", "high", "low", "prompt"]] + ["debug_info"])
40
+ FUNCTION = "direct"
41
+ CATEGORY = "Dolphin"
42
+
43
+ def _build_final(self, action, tags, master_scene, name, trigger_str):
44
+ # 1. 트리거 워드(trigger_str)와 캐릭터 이름(name)만 상단에 배치
45
+ header = []
46
+ if name: header.append(name)
47
+ if trigger_str: header.append(trigger_str)
48
+
49
+ # 2. 동작(action)과 배경(master_scene)만 하단에 배치
50
+ # 🚀 핵심: 여기서 'tags' 변수를 아예 사용하지 않음으로써 묘사 포함을 원천 차단
51
+ body = []
52
+ if master_scene and master_scene.strip():
53
+ body.append(master_scene.strip())
54
+ if action and action.strip():
55
+ body.append(action.strip())
56
+
57
+ # 결과값 결합
58
+ header_str = ", ".join(header)
59
+ body_str = " ".join(body)
60
+
61
+ if header_str and body_str:
62
+ return f"{header_str}\n{body_str}"
63
+ return header_str or body_str
64
+
65
+ def _llm_split(self, story, name, tags, key, model):
66
+ # 🚀 수정: LLM에게는 묘사를 보내되, 출력 JSON에서는 동작만 뽑도록 강제
67
+ sys_p = (
68
+ "You are an action-only generator. "
69
+ "DO NOT output any visual descriptions, clothing, lighting, or setting details. "
70
+ "Your output must ONLY be the physical movement of the character. "
71
+ "If you include any adjective-heavy descriptions, you will be penalized. "
72
+ "Strictly output only the kinetic action in the JSON fields."
73
+ )
74
+
75
+ def direct(self, model_base, model_high, model_low, mode, master_story, tagger_context, openrouter_api_key, external_triggers="", **kw):
76
+ # 🚀 강력한 방어: 어떤 묘사가 들어와도 태그는 강제로 빈 값으로 대체
77
+ tagger_context = ""
78
+
79
+ # 이후 로직...
80
+ # 모드별 처리
81
+ clips = [master_story] * 4 # 단순화 로직
82
+ master_scene = "action"
83
+ final_name = "character"
84
+
85
+ outs = []
86
+ for c in range(1, 5):
87
+ # 🚀 강제로 tags(tagger_context)를 빈 문자열("")로 전달하여 무력화
88
+ prompt = self._build_final(clips[c-1], "", master_scene, final_name, external_triggers)
89
+ outs.extend([model_base, model_high, model_low, prompt])
90
+
91
+ return tuple(outs) + ("Success",)
92
+
93
+ NODE_CLASS_MAPPINGS = {"DolphinCinematicDirector": DolphinCinematicDirector}
dolphin_lora_prompt.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 🧬 Dolphin LoRA Prompt Weaver
4
+
5
+ 이미 만들어진 클립 프롬프트를, "그 클립에 실제로 걸리는 로라들이 잘 발화하도록"
6
+ 다시 쓰는 후처리 노드. 기존 노드는 하나도 건드리지 않는 독립 노드다.
7
+
8
+ 왜 후처리인가:
9
+ 프롬프트 생성 로직(_plan_shots의 연출 규칙 9개)은 오래 튜닝된 자산이라
10
+ 복제하면 두 곳이 따로 놀게 된다. 그래서 생성은 기존 노드에 그대로 맡기고,
11
+ 이 노드는 '완성된 프롬프트 + 로라 목록 -> 다시 쓴 프롬프트'만 담당한다.
12
+ 덕분에 어떤 소스(Vision Director의 c*_prompt, Prompt Director Single의
13
+ prompt, 그냥 텍스트 노드)에든 뒤에 끼워 넣을 수 있다.
14
+
15
+ 무엇을 고치는가:
16
+ 기존 파이프라인은 트리거 워드를 캐릭터 이름 옆에 콤마로 나열한다
17
+ ("Yuna, penis, bounce, swing"). 문법적 맥락이 없는 '맨몸 단어'라 모델이
18
+ 어느 부위에 걸린 디테일인지 알 방법이 없고, bounce/swing 같은 범용 동작
19
+ 동사가 전신 모션으로 새서 캐릭터가 무슨 동작을 하든 콩콩 뛰는 문제가
20
+ 실측으로 확인됐다.
21
+
22
+ 이 노드는 로라의 사이드카 메타데이터(설명/태그/학습캡션)를 읽어서 LLM에게
23
+ "이 로라가 무엇을 하는 물건인지"를 먼저 알려준 뒤, 트리거를 문장 안에
24
+ 해당 부위에 붙여서 다시 쓰게 한다 ("her penis bounces and swings as ...").
25
+ 로라는 학습 캡션 분포와 닮은 프롬프트에서 가장 잘 발화하므로, 발화 자체도
26
+ 같이 좋아지는 게 기대 효과.
27
+
28
+ 안전장치:
29
+ - enabled=OFF, 로라 0개, API 키 없음, LLM 실패 -> 입력 프롬프트를 그대로 통과.
30
+ 어떤 경우에도 프롬프트를 잃지 않는다.
31
+ - `4n4lfi5tin6` 같은 학습 식별자는 LLM이 "정상 철자로 고쳐주는" 순간 로라가
32
+ 안 걸리므로, 출력에서 원문 그대로 있는지 검증하고 없으면 1회 재시도한 뒤
33
+ 그래도 없으면 문장 앞에 직접 붙인다(예전 콤마 방식 품질로 폴백).
34
+ """
35
+ import json
36
+ import os
37
+
38
+ import folder_paths
39
+
40
+ from . import lora_profile
41
+ from .dolphin_vision_director import (
42
+ DolphinVisionDirector,
43
+ resolve_slug,
44
+ resolve_api_key,
45
+ _load_cache,
46
+ _get_triggers_raw,
47
+ LLM_PRESETS,
48
+ LLM_PRESET_LABELS,
49
+ )
50
+
51
+ MAX_SLOTS = 6
52
+
53
+
54
+ SYSTEM_PROMPT = (
55
+ "You rewrite a single video-clip motion prompt so that the LoRA adapters listed below "
56
+ "will actually fire, WITHOUT changing what happens in the clip.\n"
57
+ "\n"
58
+ "HARD CONSTRAINTS — violating any of these is a failure:\n"
59
+ "1) DO NOT change the choreography. Same actions, same order, same body positions, same "
60
+ "pacing and energy. You are not re-directing the shot; you are re-wording it. Do not add a "
61
+ "new action, do not remove one, do not reorder them.\n"
62
+ "2) DO NOT add scenery, lighting, mood, clothing description or camera talk that was not "
63
+ "already there. This is a motion description for an animation model, not prose.\n"
64
+ "3) Keep the output roughly the same length as the input (±25%). Do not pad.\n"
65
+ "4) If the input starts with a short line that is just a character name, keep that line "
66
+ "exactly as it is, on its own line, and rewrite only what follows.\n"
67
+ "5) Keep the blunt, literal, anatomical register of the input. Never soften body parts or "
68
+ "acts into euphemism or metaphor.\n"
69
+ "\n"
70
+ "WHAT TO CHANGE — weave the LoRA vocabulary in:\n"
71
+ "6) Each adapter below only fires when the prompt resembles the captions it was trained on. "
72
+ "Rewrite the sentence so that vocabulary appears naturally inside it, with correct grammar, "
73
+ "never as an appended tag list.\n"
74
+ "7) Any string marked MUST APPEAR VERBATIM is a learned identifier. Reproduce it "
75
+ "character-for-character exactly as written — same spelling, digits and capitalisation "
76
+ "(e.g. '4n4lfi5tin6' stays '4n4lfi5tin6'; NEVER 'correct' it into real words). Place it "
77
+ "naturally, normally near the start.\n"
78
+ "8) Words listed as 'vocabulary it was trained on' are OPTIONAL. Use one ONLY where it "
79
+ "genuinely fits the action that is already in the prompt, and ALWAYS grammatically attached "
80
+ "to the specific body part or object it belongs to — write 'her penis bounces and swings as "
81
+ "she thrusts', NEVER a bare whole-body 'she bounces'. If a word does not fit what is already "
82
+ "happening, omit it entirely: a forced word is worse than a missing one, because the model "
83
+ "then applies that motion to the whole body.\n"
84
+ "9) 'training caption example' shows the phrasing style that adapter responds to. Move the "
85
+ "wording toward that style only where rules 1-5 still hold.\n"
86
+ "10) An adapter being active describes what is ALREADY in the clip. It never licenses you to "
87
+ "add the adapter's subject matter if the clip does not contain it.\n"
88
+ "\n"
89
+ 'OUTPUT ONLY JSON: {"prompt":"<the rewritten prompt>"}'
90
+ )
91
+
92
+
93
+ class DolphinLoraPromptWeaver:
94
+ """완성된 프롬프트 + 로라 선택 -> 로라에 맞게 다시 쓴 프롬프트."""
95
+
96
+ @classmethod
97
+ def INPUT_TYPES(s):
98
+ loras = ["None"] + (folder_paths.get_filename_list("loras") or [])
99
+ req = {
100
+ "prompt": ("STRING", {"multiline": True, "default": "", "forceInput": True,
101
+ "tooltip": "다시 쓸 원본 클립 프롬프트. Vision Director의 "
102
+ "c1_prompt나 Prompt Director Single의 prompt를 연결."}),
103
+ "enabled": ("BOOLEAN", {"default": True, "label_on": "WEAVE ON",
104
+ "label_off": "PASSTHROUGH (원본 그대로)",
105
+ "tooltip": "OFF면 입력 프롬프트를 손대지 않고 그대로 내보낸다. "
106
+ "A/B 비교용."}),
107
+ "openrouter_api_key": ("STRING", {"default": ""}),
108
+ "llm_preset": (LLM_PRESET_LABELS, {"default": "🔞 DeepSeek V3.2 (무삭제·JSON안정·추천)"}),
109
+ "llm_model": ("STRING", {"default": "deepseek/deepseek-v3.2"}),
110
+ "creativity": ("FLOAT", {"default": 0.4, "min": 0.1, "max": 1.5, "step": 0.05,
111
+ "tooltip": "다시 쓰기는 창작이 아니라 재표현이라 원 프롬프트 "
112
+ "생성(0.7)보다 낮게 잡는 게 맞다."}),
113
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
114
+ }
115
+ for i in range(1, MAX_SLOTS + 1):
116
+ req[f"lora_{i}"] = (loras, {"default": "None"})
117
+ req[f"lora_{i}_on"] = ("BOOLEAN", {"default": True,
118
+ "label_on": "ON", "label_off": "OFF"})
119
+ opt = {
120
+ # Architect V16 등이 이미 로라 이름을 문자열로 들고 있으면 여기로 넘겨도 된다
121
+ # (위젯으로 고른 것과 합쳐진다). 줄바꿈 또는 콤마 구분.
122
+ "lora_names": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
123
+ # civitai에 없는 개인 학습 로라처럼 자동 조회가 실패하는 경우 직접 입력.
124
+ "manual_triggers": ("STRING", {"default": "", "multiline": True,
125
+ "tooltip": "콤마 구분. 자동 조회가 안 되는 로라의 "
126
+ "트리거를 직접 넣으면 같이 녹여준다."}),
127
+ }
128
+ return {"required": req, "optional": opt}
129
+
130
+ RETURN_TYPES = ("STRING", "STRING", "STRING")
131
+ RETURN_NAMES = ("prompt", "lora_report", "debug_info")
132
+ FUNCTION = "weave"
133
+ CATEGORY = "Dolphin"
134
+
135
+ # ------------------------------------------------------------------
136
+ @staticmethod
137
+ def _split_names(text):
138
+ """줄바꿈/콤마 구분 로라 이름 파싱.
139
+
140
+ 로라 파일명 자체에 콤마가 들어있는 경우가 있어서
141
+ (예: 'Penis Lora (+Blowjob, +Cumshot) [Taz]...') 줄바꿈이 하나라도
142
+ 있으면 줄 단위로만 자른다.
143
+ """
144
+ raw = str(text or "").strip()
145
+ if not raw:
146
+ return []
147
+ parts = raw.splitlines() if "\n" in raw else raw.split(",")
148
+ return [p.strip() for p in parts if p.strip() and p.strip() != "None"]
149
+
150
+ def _gather_names(self, kw):
151
+ names = []
152
+ for i in range(1, MAX_SLOTS + 1):
153
+ name = kw.get(f"lora_{i}", "None")
154
+ if name and name != "None" and kw.get(f"lora_{i}_on", True):
155
+ names.append(name)
156
+ names.extend(self._split_names(kw.get("lora_names")))
157
+ return list(dict.fromkeys(names))
158
+
159
+ @staticmethod
160
+ def _profiles(names, manual_list):
161
+ cache = _load_cache()
162
+ profs = []
163
+ for name in names:
164
+ # 사이드카(.metadata.json)가 있으면 그걸 쓰고, 없을 때만 기존 조회
165
+ # 경로(sha256 + 네트워크)를 지연 호출한다.
166
+ p = lora_profile.load_profile(
167
+ name, fallback_triggers=lambda n=name: _get_triggers_raw(n, cache))
168
+ if p:
169
+ profs.append(p)
170
+ else:
171
+ print(f"ℹ️ [LoraWeaver] 프로필 없음(설명·트리거 모두 조회 실패): "
172
+ f"{os.path.basename(name)}")
173
+ if manual_list:
174
+ hard, soft, captions = lora_profile.classify_trained_words(manual_list)
175
+ if hard or soft or captions:
176
+ profs.append({
177
+ "lora_name": "(manual)", "file": "(manual)",
178
+ "display_name": "(수동 입력 트리거)", "description": "",
179
+ "tags": [], "base_model": "",
180
+ "hard_tokens": hard, "soft_words": soft, "captions": captions,
181
+ "source": "manual",
182
+ })
183
+ return profs
184
+
185
+ @staticmethod
186
+ def _report(profs):
187
+ if not profs:
188
+ return "(로라 없음)"
189
+ lines = []
190
+ for p in profs:
191
+ bits = [p["display_name"]]
192
+ if p["hard_tokens"]:
193
+ bits.append("verbatim=" + ",".join(p["hard_tokens"]))
194
+ if p["soft_words"]:
195
+ bits.append("vocab=" + ",".join(p["soft_words"]))
196
+ if p["captions"]:
197
+ bits.append(f"caption예시 {len(p['captions'])}개")
198
+ if not p["description"]:
199
+ bits.append("설명없음")
200
+ lines.append(" | ".join(bits))
201
+ return "\n".join(lines)
202
+
203
+ @staticmethod
204
+ def _force_insert(text, missing):
205
+ """재시도까지 실패했을 때의 최후 폴백 - 토큰만 앞에 붙인다."""
206
+ body = str(text or "").strip()
207
+ return ", ".join(missing) + (f", {body}" if body else "")
208
+
209
+ # ------------------------------------------------------------------
210
+ def weave(self, prompt, enabled, openrouter_api_key, llm_preset, llm_model,
211
+ creativity, seed, **kw):
212
+ original = str(prompt or "")
213
+
214
+ if not enabled:
215
+ return (original, "(비활성)", "enabled=OFF - 원본 그대로 통과")
216
+ if not original.strip():
217
+ return (original, "(입력 없음)", "prompt 입력이 비어있음 - 그대로 통과")
218
+
219
+ manual_list = [t.strip() for t in str(kw.get("manual_triggers", "") or "").split(",")
220
+ if t.strip()]
221
+ names = self._gather_names(kw)
222
+ profs = self._profiles(names, manual_list)
223
+ report = self._report(profs)
224
+
225
+ if not profs:
226
+ return (original, report, "적용할 로라 없음 - 원본 그대로 통과")
227
+
228
+ key = resolve_api_key(openrouter_api_key)
229
+ if not key:
230
+ print("❌ [LoraWeaver] openrouter_api_key 없음(위젯도, OPENROUTER_API_KEY 환경변수도) "
231
+ "- 원본 그대로 통과")
232
+ return (original, report, "no api key - 원본 그대로 통과")
233
+
234
+ model = resolve_slug(LLM_PRESETS, llm_preset, llm_model, "deepseek/deepseek-v3.2")
235
+ engine = DolphinVisionDirector() # _call_or/_extract_json만 빌려 쓴다(상태 없음)
236
+
237
+ usr = ("LORA ADAPTERS ACTIVE FOR THIS CLIP:\n"
238
+ + lora_profile.format_profiles_for_llm(profs)
239
+ + "\n\nPROMPT TO REWRITE (keep the choreography identical):\n"
240
+ + original)
241
+ messages = [{"role": "system", "content": SYSTEM_PROMPT},
242
+ {"role": "user", "content": usr}]
243
+
244
+ temp = creativity
245
+ last_err = "no attempts ran"
246
+ for attempt in (1, 2):
247
+ try:
248
+ content = engine._call_or(messages, model, key, temp, force_json=True)
249
+ parsed = engine._extract_json(content)
250
+ new_prompt = str((parsed or {}).get("prompt", "")).strip()
251
+ if not new_prompt:
252
+ last_err = f"빈 응답 (attempt {attempt}): {str(content)[:120]!r}"
253
+ print(f"⚠️ [LoraWeaver] {last_err}")
254
+ temp = min(creativity, 0.3)
255
+ continue
256
+
257
+ missing = lora_profile.missing_hard_tokens(new_prompt, profs)
258
+ if missing and attempt == 1:
259
+ # LLM이 `4n4lfi5tin6` 같은 토큰을 정상 철자로 고쳐버리면 로라가
260
+ # 안 걸린다. 한 번은 명시적으로 지적해서 재시도.
261
+ print(f"⚠️ [LoraWeaver] 학습 토큰 누락 -> 재시도 요청: {missing}")
262
+ messages = messages + [
263
+ {"role": "assistant",
264
+ "content": json.dumps({"prompt": new_prompt}, ensure_ascii=False)},
265
+ {"role": "user",
266
+ "content": ("Your rewrite dropped these required verbatim identifiers: "
267
+ + ", ".join(f'"{t}"' for t in missing)
268
+ + ". Rewrite again, same choreography, with each of them "
269
+ "appearing EXACTLY as written, woven naturally into the "
270
+ "sentence. Output ONLY the JSON.")},
271
+ ]
272
+ last_err = f"verbatim token 누락: {missing}"
273
+ temp = min(creativity, 0.3)
274
+ continue
275
+
276
+ if missing:
277
+ new_prompt = self._force_insert(new_prompt, missing)
278
+ print(f"⚠️ [LoraWeaver] 재시도 후에도 누락 -> 앞에 강제 삽입: {missing}")
279
+ dbg = f"⚠️ ok, 토큰 강제삽입 (attempt {attempt}): {missing}"
280
+ else:
281
+ dbg = f"✅ ok (attempt {attempt}), 로라 {len(profs)}개"
282
+
283
+ print(f"\n🧬 [Dolphin LoRA Prompt Weaver] {dbg}\n"
284
+ f" BEFORE: {original[:110]}\n AFTER : {new_prompt[:110]}")
285
+ return (new_prompt, report, dbg)
286
+
287
+ except Exception as e: # 네트워크/파싱/HTTP 무엇이 터지든 원본은 지킨다
288
+ last_err = f"{type(e).__name__}: {e}"
289
+ print(f"❌ [LoraWeaver] 호출 실패 (attempt {attempt}): {last_err}")
290
+ temp = min(creativity, 0.3)
291
+
292
+ print(f"❌ [LoraWeaver] 2회 모두 실패 - 원본 그대로 통과 ({last_err})")
293
+ return (original, report, f"❌ 실패, 원본 통과: {last_err}")
294
+
295
+
296
+ NODE_CLASS_MAPPINGS = {"DolphinLoraPromptWeaver": DolphinLoraPromptWeaver}
297
+ NODE_DISPLAY_NAME_MAPPINGS = {
298
+ "DolphinLoraPromptWeaver": "🧬 Dolphin LoRA Prompt Weaver (프롬프트→로라에 맞게 재작성)",
299
+ }
dolphin_minimax_director.py ADDED
@@ -0,0 +1,1430 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ MiniMax(Hailuo) 전용 프롬프트 디렉터.
4
+
5
+ DolphinPromptDirector는 Wan 파이프라인 전제(5초 클립 N개, 클립마다 로라/트리거)라
6
+ MiniMax에 그대로 쓰면 형식이 안 맞는다. MiniMax는:
7
+ - 클립 하나가 통째로 길다 (호스팅 Hailuo 02는 6/10초지만, 로컬 가중치는 그 제약이 없음)
8
+ - 카메라 무브를 대괄호 지시어로 직접 지정 ([Push in], [Tracking shot] ...)
9
+ - 프롬프트가 "피사체 + 동작 + 카메라 + 분위기" 한 덩어리 자연어
10
+
11
+ 그래서 이 노드는 하나의 클립 안을 초 단위 비트로 쪼개서
12
+ [0-2s] 동작 [Push in]
13
+ [2-4s] 동작 [Tracking shot]
14
+ 형태의 타임라인을 만든다. 길이는 duration_seconds로 자유롭게 정한다 — 20초짜리
15
+ 한 클립이라도 주어진 동작 목록을 시간축에 순서대로 분배해서, 중간에 멍하니
16
+ 늘어지거나 같은 동작을 루프하는 걸 막는 게 이 노드의 존재 이유다.
17
+
18
+ 비전 분석(VLM으로 앵커 뽑기)은 DolphinVisionDirector 것을 그대로 위임하고,
19
+ 샷 플래닝만 MiniMax 규약에 맞춰 새로 짰다.
20
+
21
+ 렌더러가 로컬 무검열 모델이라는 전제라, LLM 지시문에는
22
+ dolphin_vision_director._plan_shots와 동일한 "노골적·해부학적 표현" 규칙을 넣었다.
23
+
24
+ ────────────────────────────────────────────────────────────────────────
25
+ H3 프롬프트 규약 (2026-08 조사 결과, 실제 컷 튐 버그에서 역산)
26
+
27
+ 초기 버전은 비트마다 `[0-2s] 동작. [Push in]` 한 줄씩을 뱉었는데, 실제 렌더에서
28
+ 그 타임코드마다 다른 컷으로 점프하는 문제가 났다. 원인:
29
+
30
+ 1. **`[...]`로 시작하는 줄 = H3의 샷 마커.** H3 공식 멀티샷 문법이
31
+ `[Shot 1]` / `[Shot 2] At 00:03.500, the camera cuts to ...` 다.
32
+ `[0-2s]`는 이것과 생김새가 같아서 모델이 샷 리스트로 읽는다.
33
+ 2. **H3는 한 클립에 여러 샷을 넣는 게 기본 지원 기능.** 막는 지시가 없으면
34
+ 알아서 자른다. "No cuts; one continuous take" 같은 제약을 명시해야 한다.
35
+ 3. **비트마다 카메라 태그를 바꾸면 컷 신호가 더 강해진다.** 뷰포인트가 바뀔 땐
36
+ 컷이 아니라 "카메라 무브"로 표현하라는 게 가이드 권장.
37
+ 4. `[Push in]` 대괄호 카메라 문법은 구형 Director 모델(T2V-01-Director 등)
38
+ 전용이다. H3는 평문으로 쓰되 **진폭+속도**를 붙이는 걸 권장
39
+ ("pulls out with large amplitude at slow speed").
40
+
41
+ 1차 수정(대괄호 제거 + 초 인라인)으로도 컷이 계속 났다. 특히 **첫 비트 경계에서**.
42
+ 2차 조사에서 원인이 대괄호가 아니라 **숫자 타임코드 자체**임이 확인됨:
43
+
44
+ > "Every prompt should begin with a shot identifier. The first shot does not
45
+ > need a timestamp. Only later shots need precise cut times."
46
+ > "blocking the clip as 0 to 2 seconds, 2 to 5 seconds ... gives the model an
47
+ > order to follow" — 이게 곧 멀티샷 블로킹 문법이다.
48
+
49
+ 즉 H3에서 `0-2s, 2-4s ...` 는 "그 시각에 컷하라"는 선언이다. 첫 구간은
50
+ 타임스탬프가 없어서 샷 1이고, 첫 숫자(2s)가 첫 컷 지점이 된다 — 사용자가 겪은
51
+ "첫 2초 후 컷"과 정확히 일치. **초 단위 지정과 무컷은 H3에서 상호배타적이다.**
52
+
53
+ 그래서 컷을 원치 않으면 숫자 마커를 아예 쓰지 않고 지속시간을 말로만 쓴다:
54
+ `For the first two seconds she pushes off the ledge, then for the next three
55
+ seconds she strides toward the camera, and through the final two seconds ...`
56
+ 줄바꿈도 구분자 신호라서 연속 모드는 전부 한 문단(공백 조인)으로 낸다.
57
+ 숫자 블로킹/멀티샷/구형 Director 포맷은 `prompt_style` 옵션으로 남겨뒀다.
58
+
59
+ 출처: photogptai.com/guides/how-to-prompt-minimax-h3-video-model,
60
+ mixio.studio/hailuo-h3-prompt-guide, videotoprompt.app/posts/hailuo-h3-prompt-guide
61
+
62
+ ────────────────────────────────────────────────────────────────────────
63
+ 공식 스펙 반영 (2026-08-10, MiniMax-AI/MiniMax-H3 공식 SKILL.md + HF 가이드 대조)
64
+
65
+ 위 문법은 전부 서드파티 블로그 기반이었는데, 공식 소스
66
+ (github.com/MiniMax-AI/MiniMax-H3/skills/h3-prompt-writing/SKILL.md,
67
+ huggingface.co/MiniMaxAI/MiniMax-H3/docs/VIDEO_PROMPT_WRITING_GUIDE_base_en.md)를
68
+ 대조해서 아래 3가지 갭을 반영했다:
69
+
70
+ 1. **오디오 필드 부재.** H3는 비디오+오디오 동시생성 모델인데 기존 출력엔 오디오
71
+ 지시가 전혀 없었다. 공식 포맷은 3필드: `integrated_multimodal_description` /
72
+ `overall_soundscape`(환경음·물리음, 대사·음악 제외) / `non_diegetic_music`
73
+ (배경음���). `include_audio_fields` 토글로 LLM이 이 둘을 추가로 써서
74
+ 라벨 붙은 섹션으로 감싼다.
75
+ 2. **카메라 용어 목록이 15개로 낡음.** 공식은 19개 — `POV`, `Roll clockwise`,
76
+ `Roll counterclockwise` 추가, `Shake`는 `Shake slightly`/`Shake strongly`로
77
+ 분리.
78
+ 3. **I2V 정렬 문장 누락.** 공식 I2VA 예시는 전부 본문 앞에
79
+ `"For the target video, at 0.00 seconds into the target video, <Picture 1>
80
+ (from [Shot 1]) is fully referenced."`를 붙인다. `i2v_alignment_prefix`
81
+ 토글로 이미지가 연결됐을 때만 붙인다.
82
+
83
+ 기존에 실측으로 검증된 "숫자 타임코드 = 컷 선언" 로직(`_assemble`)은 건드리지
84
+ 않았다 — 새 필드는 그 결과물을 감싸기만 한다. 로컬 무검열 가중치가 이 공식
85
+ 포맷을 실제로 따르는지는 미검증이라 두 토글 다 기본 ON이지만 렌더 확인 필요.
86
+
87
+ ────────────────────────────────────────────────────────────────────────
88
+ 비트별 대사(dialogue) 추가 (2026-08-23, 의미불명 발화 버그 대응)
89
+
90
+ 공식 HF 디스커션(huggingface.co/MiniMaxAI/MiniMax-H3/discussions/76)에서
91
+ "대사가 정확히 정의되지 않으면 H3가 랜덤/의미불명의 음성을 지어낸다"는 게
92
+ 확인됐다. 기존 오디오 규칙(soundscape)은 "대사를 언급하지 말라"고만 했지
93
+ 대사 자체를 넣을 자리도, 무대사를 명시할 자리도 없었다 - 정확히 이 버그를
94
+ 유발하는 구멍이었다.
95
+
96
+ 그래서 `_plan_beats`가 비트마다 "이 순간에 대사가 어울리는가"를 독립적으로
97
+ 판단하게 하고(`dialogue` 필드, rule 15), 어울리면 `_assemble`이 그 비트의
98
+ 문장에 자연스럽게 끼워넣는다. 대사는 작은따옴표(' ')로 감싸는데, 이건
99
+ rule 11(다른 필드는 따옴표 금지)의 유일한 예외이자 JSON 파싱에도 안전하다
100
+ (큰따옴표가 아니라 이스케이프 문제가 없음). 숫자 타임코드는 여기도 금지 -
101
+ 컷 유발 로직과 동일한 원칙.
102
+
103
+ 한 비트에도 대사가 안 들어갔으면 공식 부정 문장(`DIALOGUE_NEGATIVE` =
104
+ "No dialogue. No voiceover. No singing. No lyrics. No random voices. No
105
+ individual background speech.")을 자동으로 붙인다 - 침묵을 명시적으로
106
+ 선언해야 의미불명 발화가 안 나온다는 게 위 디스커션의 핵심이었다.
107
+ 별도 토글 안 만들고 기존 `include_audio_fields`에 묶었다 - 대사도 오디오
108
+ 스펙의 일부라 위젯을 늘릴 이유가 없었다.
109
+ """
110
+ import json
111
+ import math
112
+ import random
113
+ import re
114
+ import urllib.request
115
+ import urllib.error
116
+
117
+ from .dolphin_vision_director import (
118
+ DolphinVisionDirector,
119
+ resolve_slug,
120
+ resolve_api_key,
121
+ _price_tag,
122
+ tensor_to_base64,
123
+ split_action_parts,
124
+ )
125
+
126
+ # 이 노드는 위젯 UI를 영어로 노출한다 - dolphin_vision_director의 공유
127
+ # VLM_PRESETS/LLM_PRESETS(한글 라벨)는 다른 노드(VisionDirector 등)가 계속 쓰므로
128
+ # 건드리지 않고, 여기서만 쓰는 영어 라벨 버전을 따로 둔다.
129
+ VLM_PRESETS = {
130
+ "👁 Qwen2.5-VL 72B (special cases · recommended)": "qwen/qwen2.5-vl-72b-instruct",
131
+ "👁 Qwen2.5-VL 32B (balanced)": "qwen/qwen2.5-vl-32b-instruct",
132
+ "👁 Qwen2.5-VL 32B (free)": "qwen/qwen2.5-vl-32b-instruct:free",
133
+ "👁 Mistral Small 3.1 24B (Pixtral)": "mistralai/mistral-small-3.1-24b-instruct",
134
+ "👁 Gemma 3 27B (lightweight)": "google/gemma-3-27b-it",
135
+ "⚙️ custom (use text field below)": "__custom__",
136
+ }
137
+ VLM_PRESET_LABELS = list(VLM_PRESETS.keys())
138
+
139
+ LLM_PRESETS = {
140
+ "🔞 DeepSeek V3.2 (uncensored · stable JSON · recommended)": "deepseek/deepseek-v3.2",
141
+ "🔞 Magnum v4 72B (natural phrasing)": "anthracite-org/magnum-v4-72b",
142
+ "🔞 Cydonia 24B v4.1 (lightweight · fast)": "thedrummer/cydonia-24b-v4.1",
143
+ # 2026-09-03: :free 슬러그 404 - 유료로 교체.
144
+ "🔞 Dolphin Mistral Venice (uncensored)": "cognitivecomputations/dolphin-mistral-24b-venice-edition",
145
+ # 2026-09-03 추가 (모델 페이지로만 확인, chat completions 스모크테스트 미완).
146
+ "🔞 Hermes 4 405B (steerable · strict format)": "nousresearch/hermes-4-405b",
147
+ # 2026-09-03: grok-4 / grok-4-fast deprecated(404) - grok-4.3으로 교체.
148
+ "🔞 Grok 4.3 (edgy prose · $1.25/$2.5)": "x-ai/grok-4.3",
149
+ "🔞 Euryale 70B L3.3 (sensory prose · loose format)": "sao10k/l3.3-euryale-70b",
150
+ "⚙️ custom (use text field below)": "__custom__",
151
+ }
152
+ # 2026-08-28: Valkyrie 49B V1(thedrummer/valkyrie-49b-v1) 추가했다가 뺐다 - 실제
153
+ # API 호출이 404 "No endpoints found". 자세한 건 dolphin_vision_director.py의
154
+ # LLM_PRESETS 옆 주석 참고(이 딕셔너리는 그 파일의 복제본).
155
+ LLM_PRESET_LABELS = list(LLM_PRESETS.keys())
156
+
157
+
158
+ # ======================================================================
159
+ # MiniMax 공식 카메라 무��� 지시어
160
+ # Director 계열 모델(T2V-01-Director / I2V-01-Director / Hailuo 02)이
161
+ # 인식하는 건 아래 19개뿐(2026-08-10, 공식 SKILL.md 대조로 POV/Roll 추가,
162
+ # Shake를 slightly/strongly로 분리). 목록에 없는 표현은 그냥 일반 텍스트로
163
+ # 먹혀서 카메라 제어가 안 걸리므로, LLM이 뭘 뱉든 여기로 정규화해서 거른다.
164
+ # ======================================================================
165
+ MINIMAX_CAMERA_MOVES = [
166
+ "Truck left", "Truck right",
167
+ "Pan left", "Pan right",
168
+ "Push in", "Pull out",
169
+ "Pedestal up", "Pedestal down",
170
+ "Tilt up", "Tilt down",
171
+ "Zoom in", "Zoom out",
172
+ "Shake slightly", "Shake strongly",
173
+ "Tracking shot", "Static shot",
174
+ "POV", "Roll clockwise", "Roll counterclockwise",
175
+ ]
176
+ _CAMERA_LOOKUP = {m.lower(): m for m in MINIMAX_CAMERA_MOVES}
177
+
178
+ # H3 프롬프트 필드 하드 상한(문자 수). 넘으면 뒤가 잘린다.
179
+ MINIMAX_FIELD_LIMIT = 7000
180
+ # 권장 상한. 필드는 7000자까지 받지만 가이드는 훨씬 짧게(대략 50~120 단어) 쓰는 쪽을
181
+ # 권한다 - 길수록 모델이 지시를 평균내버려서 오히려 타임라인을 안 지킨다.
182
+ MINIMAX_SOFT_LIMIT = 1200
183
+
184
+ # 한 클립을 하나의 끊김 없는 테이크로 못 박는 제약 블록. H3는 멀티샷이 기본
185
+ # 지원이라 이 문장이 없으면 타임코드 경계마다 컷을 넣어버린다.
186
+ NO_CUT_CONSTRAINT = ("One continuous take. No cuts, no shot changes, no camera angle jumps, "
187
+ "no dissolves or transitions, no teleporting — the camera path and the "
188
+ "subject's motion stay unbroken from the first second to the last.")
189
+
190
+ # 어느 비트에도 대사를 안 넣기로 한 경우 붙이는 공식 부정 지시문. H3는 "말하는
191
+ # 상황처럼 보이는데 대사가 명시 안 되면" 의미불명의 음성을 지어내는 버릇이 있다
192
+ # (공식 HF 디스커션 #76에서 확인) - 이걸 명시적으로 막아야 한다.
193
+ DIALOGUE_NEGATIVE = ("No dialogue. No voiceover. No singing. No lyrics. No random voices. "
194
+ "No individual background speech.")
195
+
196
+ # "귀엽게/섹시하게" 같은 장식 부사가 들어오면 rule 13이 "뻔한 걸 피하라"고만 시켜서,
197
+ # LLM이 그래도 두세 번째로 뻔한 제스처(손하트 다음은 고개 갸웃 정도)로 계속 수렴한다 -
198
+ # 부정 지시만으로는 실제 다양성이 안 나온다는 게 반복 관찰됨(2026-08-23, "귀여운 포즈"
199
+ # 입력 시 매번 비슷한 동작만 나온다는 사용자 리포트). 그래서 카테고리별 제스처 풀을
200
+ # 만들어두고 seed로 하나를 미리 뽑아 "이걸 써라"고 프롬프트에 박아준다 - 시드가 바뀌면
201
+ # 힌트도 바뀌니 실제로 결과가 달라진다. 손하트/V사인/브이/머리카락 넘기기처럼 이미
202
+ # rule 13이 명시적으로 금지한 클리셰는 풀에서 아예 제외했다.
203
+ GESTURE_BANK = {
204
+ "cute": [
205
+ "tilts their head and scrunches their nose while grinning",
206
+ "hops once on the balls of their feet with elbows tucked in",
207
+ "presses both palms to their own cheeks",
208
+ "spins once and lands with knees bent, beaming",
209
+ "shimmies their shoulders side to side in a small bounce",
210
+ "puffs out both cheeks and crosses their eyes",
211
+ "curls both hands into paws up near their chin",
212
+ "bites their bottom lip and gives a small shrug",
213
+ "wiggles their fingers in a tiny wave close to their chest",
214
+ "claps both hands together once in delight",
215
+ "sticks out their tongue briefly and grins",
216
+ "hugs their own arms and rocks side to side",
217
+ "taps their own chin with one finger, feigning innocence",
218
+ "does a quick curtsy, hem pinched between two fingers",
219
+ "crouches briefly with both fists raised in a tiny cheer",
220
+ "leans forward with hands clasped behind their back, rocking on their heels",
221
+ "flutters both hands like tiny wings at shoulder height",
222
+ ],
223
+ "playful": [
224
+ "sticks out their tongue and darts to one side",
225
+ "does a mock bow then springs back up laughing",
226
+ "hops from foot to foot, beckoning teasingly",
227
+ "spins a nearby object between their fingers",
228
+ "throws a playful mock punch at the air and dodges back",
229
+ "sneaks a few tiptoe steps then bursts into a run",
230
+ "flicks their own hair and dashes off laughing",
231
+ "mimes tossing something invisible over their shoulder",
232
+ "does a little skip-step and spins around",
233
+ "wags a finger teasingly then turns away giggling",
234
+ ],
235
+ "sexy": [
236
+ "trails one hand slowly down their own side",
237
+ "rolls their hips in a slow figure-eight while stepping forward",
238
+ "arches their back and rolls their shoulders",
239
+ "bites their bottom lip while holding eye contact, stepping closer",
240
+ "drags fingertips slowly along their own collarbone",
241
+ "sinks down slowly, hands trailing down their own thighs",
242
+ "leans back and stretches, arching their spine",
243
+ "runs both hands up their own torso and pauses at the ribs",
244
+ ],
245
+ "shy": [
246
+ "tucks their chin down and peeks up through their lashes",
247
+ "hides half their face behind one raised hand",
248
+ "picks at their own sleeve, eyes lowered",
249
+ "takes a half-step back, one foot turning inward",
250
+ "hugs something to their chest and looks away",
251
+ "presses their lips together and rubs one arm",
252
+ "ducks briefly behind their own shoulder",
253
+ ],
254
+ "happy": [
255
+ "throws both arms up and spins once",
256
+ "jumps and pumps a fist",
257
+ "claps rapidly while bouncing on their toes",
258
+ "laughs with their head tipped back",
259
+ "skips forward two quick steps",
260
+ ],
261
+ "confident": [
262
+ "squares their shoulders and plants their feet wide",
263
+ "flips their hair back and strides forward",
264
+ "points decisively toward camera",
265
+ "crosses their arms and cocks an eyebrow",
266
+ "plants both hands on their hips, chin raised",
267
+ ],
268
+ }
269
+
270
+ _GESTURE_KEYWORDS = {
271
+ "cute": ["cute", "cutely", "adorable", "adorably", "귀여운", "귀엽게", "깜찍"],
272
+ "playful": ["playful", "playfully", "mischievous", "장난스럽게", "장난스런", "장난기"],
273
+ "sexy": ["sexy", "sexily", "seductive", "seductively", "섹시", "요염"],
274
+ "shy": ["shy", "shyly", "bashful", "bashfully", "수줍", "부끄럽"],
275
+ "happy": ["happy", "happily", "joyful", "joyfully", "기쁘게", "즐겁게", "신나게"],
276
+ "confident": ["confident", "confidently", "당당하게", "자신있게"],
277
+ }
278
+
279
+ # 2026-08-28 추가: 위 _GESTURE_KEYWORDS는 essential_actions에 사용자가 직접 "cutely"
280
+ # 같은 장식 부사를 타이핑했을 때만 발동한다. 요청: 이미지 자체의 표정을 읽어서(예:
281
+ # 웃고 있으면 cute쪽, 심각한 표정이면 confident/sexy쪽) 자동으로 유도해달라 - 그래서
282
+ # _vision_analyze()가 뽑는 anchors의 5번째 줄(표정/무드)을 같은 방식으로 스캔하는
283
+ # 별도 키워드셋. "serious"/"intense"는 사용자가 명시한 대로 confident와 sexy 둘 다에
284
+ # 걸어뒀다 - 심각한 표정이 상황에 따라 둘 중 어느 쪽으로도 자연스럽게 갈 수 있어서,
285
+ # 매칭되면 두 카테고리 힌트가 다 뜨고 LLM이 장면에 맞는 쪽을 고른다.
286
+ _MOOD_EXPRESSION_KEYWORDS = {
287
+ "cute": ["smiling", "smile", "grin", "grinning", "beaming", "sweet expression",
288
+ "warm smile", "giggl"],
289
+ "playful": ["smirk", "smirking", "mischievous", "teasing expression", "wink"],
290
+ "sexy": ["sultry", "seductive", "smoldering", "sensual", "half-lidded", "alluring",
291
+ "serious", "intense stare", "intense gaze"],
292
+ "shy": ["shy", "blushing", "bashful", "timid", "looking away", "averting"],
293
+ "happy": ["happy", "joyful", "laughing", "delighted", "elated", "cheerful"],
294
+ "confident": ["serious", "stern", "intense", "solemn", "unsmiling", "composed",
295
+ "focused", "determined", "cold expression"],
296
+ }
297
+
298
+
299
+ def _kw_hit(text, kw):
300
+ """단어 경계 매치 - 부분 문자열 `in` 검사로는 "unsmiling"이 "smiling"에 우연히
301
+ 걸려서 안 웃는(=정반대) 표정을 "cute"로 오인식하는 사고가 난다(2026-08-28, anchors
302
+ 스캔 추가 직후 테스트로 실제 발견함). kw가 여러 단어 구(phrase)여도 앞뒤 경계만
303
+ 잡으면 되므로 그대로 통한다."""
304
+ return re.search(r"\b" + re.escape(kw) + r"\b", text) is not None
305
+
306
+
307
+ def _gesture_hints(actions, anchors, seed):
308
+ """actions의 장식 부사 + anchors(이미지에서 읽은 표정/무드, rule 5c) 둘 다 스캔해서
309
+ 카테고리를 찾고, seed로 고정한 제스처 힌트를 만든다.
310
+
311
+ 같은 카테고리라도 seed가 다르면 GESTURE_BANK에서 다른 항목을 뽑으므로, "귀여운
312
+ 포즈" 같은 입력을 여러 번 돌려도(=시드가 바뀌면) 실제로 다른 동작이 나온다.
313
+ 같은 seed 키(f"{seed}:{cat}")를 두 소스가 공유하므로, 텍스트로 "cutely"라고
314
+ 썼든 이미지가 웃고 있어서 감지됐든 같은 카테고리면 같은 픽이 나와서 일관된다."""
315
+ act_text = (actions or "").lower()
316
+ anc_text = (anchors or "").lower()
317
+ hits = []
318
+ for cat, kws in _GESTURE_KEYWORDS.items():
319
+ from_action = any(_kw_hit(act_text, kw) for kw in kws)
320
+ from_image = any(_kw_hit(anc_text, kw) for kw in _MOOD_EXPRESSION_KEYWORDS.get(cat, []))
321
+ if from_action or from_image:
322
+ bank = GESTURE_BANK[cat]
323
+ pick = bank[random.Random(f"{seed}:{cat}").randrange(len(bank))]
324
+ source = "action wording" if from_action else "image expression"
325
+ hits.append(f' - for "{cat}" (from {source}): {pick}')
326
+ if not hits:
327
+ return ""
328
+ return ("SUGGESTED GESTURES (seed-specific, per rule 13 — use this AS your concrete "
329
+ "gesture for the matching mood instead of inventing the usual cliché; adapt the "
330
+ "wording naturally into the action, and only substitute something else of your "
331
+ "own invention if this one truly cannot fit the scene):\n"
332
+ + "\n".join(hits) + "\n\n")
333
+
334
+
335
+ # GESTURE_BANK/_gesture_hints는 "귀엽게/섹시하게" 같은 특정 수식어가 actions 텍스트에
336
+ # 있을 때만 발동한다. 근데 그 트리거 없이도 - 즉 essential_actions가 완전히 똑같아도 -
337
+ # LLM이 매번 "가장 그럴듯한" 동작·대사로 수렴하는 문제가 있었다(2026-08-28, "프롬프트가
338
+ # 같더라도 좀 더 다양한 행동을 할수 없어? 매번 같은 패턴에 같은 대사"). creativity/seed는
339
+ # 이미 정상 설정(0.7, 매 배치 randomize)이었는데도 반복됐다는 게 확인됨 - 온도만으론
340
+ # 이 수렴을 잘 못 깬다는 뜻이라, GESTURE_BANK와 같은 방식(seed로 고정 선택 -> 명시
341
+ # 주입)을 트리거 조건 없이 매번 강제로 적용하도록 일반화했다.
342
+ ACTION_VARIATION_BANK = [
343
+ "let a secondary, incidental body part move independently of the main motion (fingers "
344
+ "flex, toes curl, head tilts) — a small detail that isn't the point but grounds it",
345
+ "vary the pacing within the beat itself: start slower than expected, then let it "
346
+ "accelerate or snap partway through",
347
+ "have the character's weight or balance point visibly shift during the motion, not just "
348
+ "the primary limb",
349
+ "let breath be audible in the action itself — a caught breath, a held breath released, "
350
+ "breathing syncing to the rhythm of the motion",
351
+ "introduce one small hesitation, correction, or stumble before the motion fully commits",
352
+ "let something in the immediate environment react to the motion — fabric shifts, an "
353
+ "object gets nudged, hair falls or sways",
354
+ "make the motion asymmetric — one side of the body leads distinctly before the other "
355
+ "catches up",
356
+ "let the eyes or gaze do something specific and separate from the main physical action "
357
+ "(glancing away, locking on, closing briefly)",
358
+ "vary where the tension sits in the body — sometimes the hands, sometimes the jaw, "
359
+ "sometimes the shoulders",
360
+ "let the motion overshoot slightly before settling, rather than stopping cleanly",
361
+ ]
362
+
363
+ DIALOGUE_REGISTER_BANK = [
364
+ "teasing, drawing the words out slowly",
365
+ "breathless and urgent, words clipped short",
366
+ "mock-commanding, testing how it lands",
367
+ "quiet and half-embarrassed, almost muttered",
368
+ "defiant, chin raised, daring a reaction",
369
+ "worshipful and hushed, reverent",
370
+ "playfully mocking, biting back a laugh",
371
+ "pleading, voice cracking slightly at the edges",
372
+ "flat and matter-of-fact, almost surprising in how calm it is",
373
+ "sharp and startled, the words tumbling out before thought",
374
+ "low and deliberate, each word placed like a challenge",
375
+ "sing-song and light, almost taunting",
376
+ ]
377
+
378
+
379
+ def _variation_hints(seed):
380
+ """essential_actions가 완전히 같아도 seed가 다르면 동작 실행 스타일과 대사 톤이
381
+ 실제로 갈리도록, 매번(트리거 조건 없이) 한 쌍을 강제로 골라 주입한다.
382
+
383
+ _gesture_hints와 달리 특정 수식어 매치가 필요 없다 - "같은 프롬프트, 다른 결과"
384
+ 문제 자체가 목적이라 항상 켜져 있어야 한다."""
385
+ action_pick = ACTION_VARIATION_BANK[
386
+ random.Random(f"{seed}:action_variation").randrange(len(ACTION_VARIATION_BANK))]
387
+ dialogue_pick = DIALOGUE_REGISTER_BANK[
388
+ random.Random(f"{seed}:dialogue_register").randrange(len(DIALOGUE_REGISTER_BANK))]
389
+ return (
390
+ "TAKE VARIATION (seed-specific — this run's distinct angle on an otherwise identical "
391
+ "prompt, per rules 4c/15c):\n"
392
+ f" - execution style for this take: {action_pick}\n"
393
+ f" - if any dialogue line is used, its delivery register should lean toward: "
394
+ f"{dialogue_pick}\n"
395
+ " Weave these into the actual beats naturally - do not narrate them as separate "
396
+ "sentences, name them explicitly, or let them override what the action list or scene "
397
+ "anchors actually call for.\n\n")
398
+
399
+
400
+ # 비트 개수 상한. 긴 클립(예: 60초)에 1초 단위를 걸면 비트가 수십 개가 되어
401
+ # 프롬프트가 상한을 넘고, 모델도 그 정도 밀도의 지시는 못 따라온다.
402
+ # 넘으면 거부하는 대신 beat 길이를 자동으로 넓히고 경고만 찍는다.
403
+ MAX_BEATS = 24
404
+
405
+
406
+ def _norm_camera(raw):
407
+ """LLM/사용자가 준 카메라 문자열을 '[Push in, Zoom in]' 형태로 정규화.
408
+
409
+ 대괄호 유무·대소문자·순서 상관없이 받아서, 공식 15개에 매칭되는 것만 남긴다.
410
+ 매칭이 하나도 없으면 빈 문자열(=카메라 지시 없음)."""
411
+ if not raw:
412
+ return ""
413
+ text = str(raw).replace("[", " ").replace("]", " ")
414
+ hits, seen = [], set()
415
+ for token in re.split(r"[,;/|]+", text):
416
+ key = " ".join(token.split()).lower()
417
+ if key in _CAMERA_LOOKUP and key not in seen:
418
+ seen.add(key)
419
+ hits.append(_CAMERA_LOOKUP[key])
420
+ if not hits:
421
+ return ""
422
+ # MiniMax 문서 권장: 한 번에 3개까지. 그 이상은 서로 상쇄돼서 무의미해진다.
423
+ return "[" + ", ".join(hits[:3]) + "]"
424
+
425
+
426
+ # "순차 접속사" 스타일에서 쓰는 연결어. H3 가이드가 first/then/as/finally 같은
427
+ # 순차 표현으로 순서를 못 박으라고 권한다.
428
+ _SEQ_CONNECTIVES = ["then", "as", "then", "next", "then", "and then"]
429
+
430
+
431
+ def _fmt_sec(x):
432
+ """2.0 -> '2', 2.5 -> '2.5'. 타임코드가 지저분해지는 것만 막는 용도."""
433
+ return str(int(x)) if float(x) == int(x) else f"{float(x):g}"
434
+
435
+
436
+ _NUM_WORDS = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
437
+ "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
438
+ "seventeen", "eighteen", "nineteen", "twenty"]
439
+
440
+
441
+ def _dur_parts(seconds):
442
+ """2.0 -> ('two seconds', False), 2.4 -> ('two seconds', True).
443
+
444
+ 지속시간을 숫자 없이 말로만 쓰기 위한 것. H3에서 `0-2s` 같은 숫자 마커는
445
+ '그 시각에 컷하라'는 선언으로 읽히므로, 연속 테이크를 원하면 숫자를 쓰면 안 된다.
446
+ 근사 여부를 따로 돌려주는 건 'For the first about two seconds' 같은 어색한
447
+ 어순을 피하고 'For roughly the first two seconds'로 붙이기 위해서."""
448
+ s = float(seconds)
449
+ n = max(int(round(s)), 1)
450
+ word = _NUM_WORDS[n] if n < len(_NUM_WORDS) else str(n)
451
+ unit = "second" if n == 1 else "seconds"
452
+ return f"{word} {unit}", abs(s - n) > 0.05
453
+
454
+
455
+ def _fmt_timecode(sec):
456
+ """3.5 -> '00:03.500'. H3 멀티샷 문법이 쓰는 컷 시점 표기."""
457
+ return f"{int(sec) // 60:02d}:{int(sec) % 60:02d}.{int(round((sec % 1) * 1000)):03d}"
458
+
459
+
460
+ def _sentence(text):
461
+ """앞 글자를 대문자로 올리고 마침표를 하나만 붙인다. 연속 모드에서는 모든 블록이
462
+ 한 문단에 이어 붙으므로 문장처럼 보여야 한다."""
463
+ t = str(text).strip().rstrip(".").strip()
464
+ if not t:
465
+ return ""
466
+ return t[0].upper() + t[1:] + "."
467
+
468
+
469
+ def _strip_brackets(text):
470
+ """대괄호를 걷어낸다. H3는 `[...]`를 샷 마커로 읽기 때문에, 연속샷 포맷에서는
471
+ LLM이 액션 문장 안에 대괄호를 흘려도 그게 컷 신호가 되면 안 된다."""
472
+ return " ".join(str(text).replace("[", " ").replace("]", " ").split())
473
+
474
+
475
+ def _lower_first(text):
476
+ """문장을 이어붙일 때 첫 글자만 소문자로. 고유명사/대문자 약어는 건드리지 않는다."""
477
+ t = str(text).strip().rstrip(".")
478
+ if not t or not t[0].isupper():
479
+ return t
480
+ # "She"는 낮추고 "Yuna"/"NASA"는 그대로 — 두 번째 글자가 대문자면 약어로 본다.
481
+ if len(t) > 1 and t[1].isupper():
482
+ return t
483
+ head = t.split()[0].strip(",")
484
+ if head in _PRONOUN_STARTS:
485
+ return t[0].lower() + t[1:]
486
+ return t
487
+
488
+
489
+ # 이어붙일 때 소문자로 낮춰도 안전한 문장 시작 단어(대명사/관사). 이름으로 시작하는
490
+ # 문장은 그대로 둬야 해서 화이트리스트 방식으로 간다.
491
+ _PRONOUN_STARTS = {"She", "He", "They", "It", "The", "A", "An", "Her", "His", "Their", "Its"}
492
+
493
+
494
+ def _auto_beat_count(duration):
495
+ """beat_seconds=0(auto)일 때 쓸 비트 개수. 대략 2.5초에 하나, 3~8개로 제한."""
496
+ return max(3, min(8, int(round(duration / 2.5))))
497
+
498
+
499
+ def _beat_bounds(duration, beat_seconds):
500
+ """(start, end) 구간 리스트. beat_seconds<=0이면 None(=LLM이 직접 나눔)."""
501
+ duration = float(duration)
502
+ if beat_seconds is None or beat_seconds <= 0:
503
+ return None
504
+ step = float(beat_seconds)
505
+ if duration / step > MAX_BEATS:
506
+ widened = math.ceil(duration / MAX_BEATS * 10) / 10.0
507
+ print(f"⚠️ [MinimaxDirector] Splitting {_fmt_sec(duration)}s into {_fmt_sec(step)}s beats "
508
+ f"makes too many beats - widening to {_fmt_sec(widened)}s beats (max {MAX_BEATS} beats).")
509
+ step = widened
510
+
511
+ bounds, t = [], 0.0
512
+ while t < duration - 1e-6:
513
+ end = min(t + step, duration)
514
+ bounds.append((round(t, 2), round(end, 2)))
515
+ t = end
516
+
517
+ # 자투리 흡수: 20초를 3초로 나누면 마지막이 2초짜리로 남는데, 그게 한 비트의
518
+ # 절반도 안 되면 직전 비트에 합친다. 1초도 안 되는 비트에 동작을 하나 배정해봐야
519
+ # 모델이 못 따라오고 타임코드만 지저분해진다.
520
+ if len(bounds) > 1 and (bounds[-1][1] - bounds[-1][0]) < step * 0.5:
521
+ bounds = bounds[:-2] + [(bounds[-2][0], bounds[-1][1])]
522
+ return bounds
523
+
524
+
525
+ def _call_openrouter(messages, model, key, temp, seed=None, timeout=120, force_json=False,
526
+ usage_acc=None):
527
+ """DolphinVisionDirector._call_or와 같은 호출이지만 seed를 실제로 payload에 넣는다.
528
+
529
+ 기존 _plan_shots는 seed 인자를 받기만 하고 API에 안 넘겨서, 같은 입력·같은
530
+ 시���에도 매번 다른 프롬프트가 나왔다(A/B 비교가 불가능했던 원인). 여기서는
531
+ seed를 넘겨서 지원하는 프로바이더에서는 재현이 되게 한다."""
532
+ payload = {"model": model.strip(), "messages": messages, "temperature": temp}
533
+ if force_json:
534
+ payload["response_format"] = {"type": "json_object"}
535
+ if seed is not None:
536
+ # OpenRouter는 32bit 범위를 넘는 seed를 거부하는 프로바이더가 있어서 접는다.
537
+ payload["seed"] = int(seed) % (2 ** 31)
538
+ payload["usage"] = {"include": True}
539
+ req = urllib.request.Request(
540
+ "https://openrouter.ai/api/v1/chat/completions",
541
+ data=json.dumps(payload).encode("utf-8"),
542
+ headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"})
543
+ raw = json.loads(urllib.request.urlopen(req, timeout=timeout).read().decode("utf-8"))
544
+ u = raw.get("usage") or {}
545
+ if usage_acc is not None and u:
546
+ usage_acc["prompt"] += u.get("prompt_tokens", 0) or 0
547
+ usage_acc["completion"] += u.get("completion_tokens", 0) or 0
548
+ c = u.get("cost")
549
+ if c is None:
550
+ c = (u.get("cost_details") or {}).get("upstream_inference_cost")
551
+ usage_acc["cost"] += float(c or 0)
552
+ if "choices" not in raw:
553
+ err = raw.get("error") or raw
554
+ msg = err.get("message") if isinstance(err, dict) else str(err)
555
+ code = err.get("code") if isinstance(err, dict) else "?"
556
+ raise ValueError(f"API error (code={code}): {msg}")
557
+ return raw["choices"][0]["message"]["content"].strip()
558
+
559
+
560
+ class DolphinMinimaxDirector:
561
+ """
562
+ 이미지(선택) + 동작 서술 -> MiniMax/Hailuo용 초 단위 타임라인 프롬프트 1개.
563
+
564
+ 1) IMAGE -> VLM이 앵커(외형/자세/지지물/환경) 추출 (vision_caption 입력이 있으면 그걸 사용)
565
+ 2) 그 앵커 위에서 동작을 duration 안의 초 단위 비트로 분배 (LLM, 실패 시 로컬 폴백)
566
+ 3) MiniMax 규약대로 조립: 피사체 문장 + [0-2s] 동작 [카메라] ... + 분위기 문장
567
+
568
+ image를 안 꽂으면 T2V 모드로 동작한다(앵커 없이 텍스트만으로 플래닝).
569
+ """
570
+
571
+ @classmethod
572
+ def INPUT_TYPES(s):
573
+ req = {
574
+ "essential_actions": ("STRING", {"multiline": True,
575
+ "default": "stands up and walks toward the camera, stops and looks back",
576
+ "tooltip": "If left empty, the LLM directs the action on its own using only the "
577
+ "image anchors."}),
578
+ "duration_seconds": ("INT", {"default": 6, "min": 1, "max": 300, "step": 1,
579
+ "tooltip": "Total length of the clip to make, in seconds. The hosted Hailuo API "
580
+ "only accepts 6/10 seconds, but local weights have no such limit, so "
581
+ "20 or even 60 seconds gets distributed to match."}),
582
+ "beat_seconds": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 30.0, "step": 0.5,
583
+ "tooltip": "How many seconds each timeline beat spans. 0 = auto (the LLM splits "
584
+ f"it into 3-8 segments on its own). Going as fine as 1 second can make "
585
+ f"the motion feel choppy, and if the beat count would exceed "
586
+ f"{MAX_BEATS} it's widened automatically."}),
587
+ "prompt_style": (["Continuous prose (duration in words, no cuts — recommended)",
588
+ "Sequential connectives (no time mentioned, strongest cut prevention)",
589
+ "Second timeblocking (0-2s = a cut-point declaration in H3)",
590
+ "Multi-shot [Shot N] (intentional cuts)",
591
+ "Director brackets (for legacy T2V-01-Director)"],
592
+ {"default": "Continuous prose (duration in words, no cuts — recommended)",
593
+ "tooltip": "In H3, a numeric timecode is a declaration to 'cut at that moment' - "
594
+ "exact-second timing and no-cut are mutually exclusive, so you have "
595
+ "to pick a style.\n"
596
+ "Continuous prose: one paragraph like 'For the first two seconds ..., "
597
+ "then for the next three seconds ...'. No numbers, so no cut happens "
598
+ "while timing is still conveyed.\n"
599
+ "Sequential connectives: no time mentioned at all, just order via "
600
+ "first/then/finally. Use this if prose mode still produces cuts.\n"
601
+ "Second timeblocking: only when exact seconds must be honored. This "
602
+ "causes cuts.\n"
603
+ "Multi-shot: for when you want cuts on purpose. Official [Shot N] "
604
+ "syntax.\n"
605
+ "Director brackets: for legacy T2V-01-Director-family models only."}),
606
+ "no_cut_constraint": ("BOOLEAN", {"default": True,
607
+ "label_on": "NO CUT (force continuous take)", "label_off": "No cut constraint",
608
+ "tooltip": "Appends a 'One continuous take. No cuts...' constraint to the end of "
609
+ "the prompt. H3 supports multi-shot by default, so without this it "
610
+ "inserts cuts on its own. Automatically ignored in Multi-shot style."}),
611
+ "camera_mode": (["Single continuous camera (LLM, recommended)",
612
+ "off (no camera mentioned)",
613
+ "manual (fixed value below)",
614
+ "Per-segment brackets (Director only)"],
615
+ {"default": "Single continuous camera (LLM, recommended)",
616
+ "tooltip": "In H3, swapping the camera on every beat is itself a cut signal. "
617
+ "The default is a single camera move written in plain prose that "
618
+ "runs through the whole clip."}),
619
+ "camera_manual": ("STRING", {"default": "slow push in, small amplitude",
620
+ "tooltip": "Only used when set to manual. In continuous/prose mode the sentence "
621
+ "goes in as-is (H3's recommended notation: move + amplitude + speed, "
622
+ "e.g. 'pulls out with large amplitude at slow speed'). Only in "
623
+ "'Per-segment brackets' mode are segments split with | and only the "
624
+ f"following moves are recognized: {', '.join(MINIMAX_CAMERA_MOVES)}"}),
625
+ "character_name": ("STRING", {"default": "AUTO"}),
626
+ "artistic_vibe": ("STRING", {"multiline": True,
627
+ "default": "cinematic lighting, natural motion"}),
628
+ "detail_level": (["Short (concise)", "Essentials + anchors only", "Detailed"],
629
+ {"default": "Essentials + anchors only"}),
630
+ "use_vision": ("BOOLEAN", {"default": True,
631
+ "label_on": "VISION ON", "label_off": "VISION OFF (text only)"}),
632
+ "openrouter_api_key": ("STRING", {"default": ""}),
633
+ "vlm_preset": (VLM_PRESET_LABELS, {"default": "👁 Qwen2.5-VL 72B (special cases · recommended)"}),
634
+ "vlm_model": ("STRING", {"default": "qwen/qwen2.5-vl-72b-instruct"}),
635
+ "llm_preset": (LLM_PRESET_LABELS, {"default": "🔞 DeepSeek V3.2 (uncensored · stable JSON · recommended)"}),
636
+ "llm_model": ("STRING", {"default": "deepseek/deepseek-v3.2"}),
637
+ "creativity": ("FLOAT", {"default": 0.7, "min": 0.1, "max": 1.5, "step": 0.05}),
638
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
639
+ # 2026-08-10 추가분 - 기존 워크플로우의 widgets_values 포지션이 안 밀리도록
640
+ # 반드시 맨 끝에 붙인다(중간에 넣으면 이 뒤의 모든 위젯값이 밀린다 - 실제로
641
+ # 한 번 겪었음, [[dolphin-nodes-repo-features]] 참고).
642
+ "include_audio_fields": ("BOOLEAN", {"default": True,
643
+ "label_on": "Include audio fields (official format)",
644
+ "label_off": "Video only (legacy format)",
645
+ "tooltip": "Wraps the output in the official H3 3-field format "
646
+ "(integrated_multimodal_description / overall_soundscape / "
647
+ "non_diegetic_music). H3 is a model that generates video+audio "
648
+ "together, so leaving this field unfilled does not mean silence - the "
649
+ "model invents its own sound. Turn this on if you want intentional "
650
+ "sound. Without an LLM (local fallback) it can't write audio "
651
+ "sentences, so it's dropped automatically.\n"
652
+ "This toggle also manages dialogue: the LLM judges per-beat whether a "
653
+ "line fits, and inserts a short natural line only where it fits, or "
654
+ "appends the 'No dialogue...' negative directive if no beat got one - "
655
+ "this safeguard is needed because H3 tends to invent meaningless "
656
+ "speech if dialogue isn't explicitly specified (official HF discussion "
657
+ "#76)."}),
658
+ "i2v_alignment_prefix": ("BOOLEAN", {"default": False,
659
+ "label_on": "Include I2V alignment prefix (⚠️ cut risk)",
660
+ "label_off": "No prefix (default)",
661
+ "tooltip": "⚠️ Default OFF - when an image is connected (I2V), the official "
662
+ "alignment sentence 'For the target video, at 0.00 seconds..., "
663
+ "<Picture 1> (from [Shot 1]) is fully referenced.' gets prepended, "
664
+ "but that sentence contains both a '[Shot 1]' bracket AND a '0.00 "
665
+ "seconds' numeric timecode - exactly the H3 cut-triggering pattern "
666
+ "confirmed by testing ([Shot N] At timestamp = the official multi-shot "
667
+ "syntax), which directly conflicts with no_cut_constraint. Turning it "
668
+ "on was reproduced to cause random cuts in practice (2026-08-13) - "
669
+ "root cause unconfirmed but a prime suspect, so the default was "
670
+ "reverted to OFF. Don't turn it on unless you're doing strict-spec "
671
+ "experiments like FL2VA/Ref2VA."}),
672
+ }
673
+ opt = {
674
+ # I2V면 연결, T2V면 비워둠.
675
+ "image": ("IMAGE",),
676
+ # JoyCaption 등 로컬 캡션 노드 출력. 있으면 OpenRouter VLM 호출을 건너뛴다.
677
+ "vision_caption": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
678
+ # 앞 클립의 프롬프트/동작을 연결하면 이번 클립이 그 끝 자세에서 이어진다.
679
+ "previous_action": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
680
+ }
681
+ return {"required": req, "optional": opt}
682
+
683
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
684
+ RETURN_NAMES = ("prompt", "timeline_json", "debug_info", "resolved_character_name",
685
+ "soundscape", "non_diegetic_music")
686
+ FUNCTION = "direct"
687
+ CATEGORY = "Dolphin"
688
+
689
+ # ------------------------------------------------------------------
690
+ # LLM 플래닝
691
+ # ------------------------------------------------------------------
692
+ def _plan_beats(self, anchors, actions, name, vibe, detail, duration, bounds,
693
+ camera_style, no_cut, llm_model, key, temp, seed, previous_action="",
694
+ include_audio=True):
695
+ auto_direct = not actions.strip()
696
+ dur_s = _fmt_sec(duration)
697
+
698
+ # 대사도 오디오 스펙의 일부라 별도 토글 없이 include_audio에 묶는다.
699
+ # manner(전달 방식 묘사)와 line(실제 발화)을 필드 단계에서부터 분리한다 -
700
+ # 예전엔 한 필드에 같이 넣고 조립 시 이어붙였더니 "moaning softly" 같은
701
+ # 톤 묘사 단어까지 H3가 그대로 발화하는 문제가 있었다(2026-08-25, 실사용
702
+ # 리포트). line만 <d>...</d> 태그로 감싸서 발화 대상임을 명시하고, manner는
703
+ # 산문 서술로만 쓰이게 분리해서 고쳤다.
704
+ dialogue_field = (',"dialogue_manner":"<optional short delivery description for THIS '
705
+ 'beat, or empty>",'
706
+ '"dialogue_line":"<optional exact spoken line for THIS beat, or empty>"'
707
+ if include_audio else "")
708
+
709
+ if bounds is None:
710
+ n_hint = _auto_beat_count(duration)
711
+ slot_block = (f" Split the {dur_s}-second clip into {n_hint} consecutive beats "
712
+ f"yourself. They must tile 0 to {dur_s} seconds with no gaps and no "
713
+ f"overlap: the first starts at 0, the last ends at {dur_s}.")
714
+ schema_beats = ('"beats":[{"start":<number>,"end":<number>,"action":"<...>",'
715
+ '"camera":"<...>"' + dialogue_field + '}, ...]')
716
+ else:
717
+ slot_block = "\n".join(
718
+ f" beat {i}: start={_fmt_sec(a)} end={_fmt_sec(b)}"
719
+ for i, (a, b) in enumerate(bounds, 1))
720
+ schema_beats = ('"beats":[' + ",".join(
721
+ f'{{"start":{_fmt_sec(a)},"end":{_fmt_sec(b)},"action":"<what happens in this '
722
+ f'{_fmt_sec(b - a)}-second window>","camera":"<...>"' + dialogue_field + '}'
723
+ for a, b in bounds) + "]")
724
+
725
+ n_beats = len(bounds) if bounds is not None else n_hint
726
+ act_parts = split_action_parts(actions)
727
+
728
+ if auto_direct:
729
+ action_rule = (
730
+ "1) INVENT THE ACTION. No action list is given. Design one coherent, continuously "
731
+ "developing movement that plausibly follows from the character's exact pose and "
732
+ f"situation in the SCENE ANCHORS, and that genuinely fills all {dur_s} seconds — "
733
+ "what would they naturally do next, starting right now from that position? Do not "
734
+ "invent new characters, new props, or a change of location.\n")
735
+ assign_label = "ACTION LIST: (none — invent it per rule 1)"
736
+ assign_body = ""
737
+ else:
738
+ # 20초짜리 클립에 동작 4개처럼 "비트 수 >> 동작 수"가 흔한데, 그냥 두면
739
+ # LLM이 앞쪽 비트에서 동작을 다 소진하고 남은 시간을 반복/정지로 때운다.
740
+ # 그래서 배분 비율을 계산해서 명시적으로 못 박는다.
741
+ if len(act_parts) and len(act_parts) < n_beats:
742
+ span = n_beats / len(act_parts)
743
+ ratio_rule = (
744
+ f" You have {len(act_parts)} action(s) for {n_beats} beats, so each action must "
745
+ f"occupy roughly {span:.1f} consecutive beats. Do NOT finish the whole list early "
746
+ "and then pad the remaining beats with idling, repetition, or a held pose. Break "
747
+ "each action into its own sub-phases — wind-up, execution, follow-through, "
748
+ "recovery — so it genuinely spans its share of the timeline as one continuous "
749
+ "movement rather than being restated.\n")
750
+ elif len(act_parts) > n_beats:
751
+ ratio_rule = (
752
+ f" You have {len(act_parts)} actions for {n_beats} beats, so group consecutive "
753
+ "actions together — roughly "
754
+ f"{len(act_parts) / max(n_beats, 1):.1f} per beat — keeping the given order and "
755
+ "dropping none of them.\n")
756
+ else:
757
+ ratio_rule = (
758
+ f" You have {len(act_parts)} actions for {n_beats} beats — one action per beat, "
759
+ "in order.\n")
760
+ action_rule = (
761
+ "1) FOLLOW THE ACTION LIST. Distribute the listed actions across the beats IN THE "
762
+ "GIVEN ORDER — earlier actions in earlier beats, the last action in the last beat. "
763
+ "Never move a later action into an earlier beat, and never skip one.\n"
764
+ + ratio_rule)
765
+ assign_label = "ACTION LIST (in order — distribute across the beats per rule 1):"
766
+ # 원본 텍스트를 통짜로 넘기면 LLM이 구분자를 못 알아채고 전부 한 동작으로
767
+ # 읽어서 첫 비트에 몰아넣는다(실제로 겪은 버그) - 미리 쪼갠 항목을 번호 매겨
768
+ # 넘겨야 "몇 개의 동작인지"와 "경계가 어디인지"가 명확해진다.
769
+ assign_body = "\n".join(f" {i}. {p}" for i, p in enumerate(act_parts, 1))
770
+
771
+ # 긴 클립일수록 "한 동작을 계속 반복" 실패 모드가 심해진다. 짧은 클립엔
772
+ # 불필요한 잔소리라 길이 기준으로만 붙인다.
773
+ arc_rule = ""
774
+ if duration >= 12:
775
+ arc_rule = (
776
+ f"1b) LONG CLIP ({dur_s}s). This is long enough that a single repeated motion will "
777
+ "read as a stuttering loop. The timeline needs a visible beginning, middle and end: "
778
+ "each beat must show the body in a measurably DIFFERENT position or phase from the "
779
+ "beat before it. Before writing, check that no two beats could be swapped without "
780
+ "the sequence making less sense — if they could, they are too similar.\n")
781
+
782
+ if camera_style == "perbeat":
783
+ camera_rule = (
784
+ "6) CAMERA (per beat). For every beat, pick the camera instruction that best serves "
785
+ "that beat's motion, chosen ONLY from this exact list:\n"
786
+ f" {', '.join(MINIMAX_CAMERA_MOVES)}\n"
787
+ " Write it in that beat's \"camera\" field as bracketed text, e.g. \"[Push in]\". "
788
+ "At most 3 combined in one bracket. Use the EXACT wording above. Do not change "
789
+ "camera on every beat — hold one move across neighbouring beats and leave "
790
+ "\"camera\" empty for beats that simply continue the previous one. Leave the "
791
+ "top-level \"camera\" field empty.\n")
792
+ cam_schema = '"camera":""'
793
+ elif camera_style == "continuous":
794
+ camera_rule = (
795
+ "6) CAMERA — ONE UNBROKEN MOVE FOR THE WHOLE CLIP. Put a single camera description "
796
+ "in the TOP-LEVEL \"camera\" field and leave every per-beat \"camera\" field empty. "
797
+ "Write it as plain English prose, NOT as a bracketed tag, and give it a move, an "
798
+ "amplitude and a speed — e.g. 'the camera pushes in with small amplitude at slow "
799
+ "speed, drifting to the left as she rises'. Usable moves: push in, pull out, truck "
800
+ "left/right, pan left/right, tilt up/down, pedestal up/down, arc, tracking shot, "
801
+ "static. It must be ONE continuous path that survives the entire clip: it may "
802
+ "accelerate, ease, or change direction smoothly, but it must never cut, jump to a "
803
+ "new angle, or restart from a different framing. If the subject's own motion is "
804
+ "the point, say the camera holds a static frame — that is also a valid answer.\n")
805
+ cam_schema = ('"camera":"<one continuous camera move for the whole clip, plain prose '
806
+ 'with amplitude and speed>"')
807
+ else:
808
+ camera_rule = (
809
+ "6) CAMERA. Leave the top-level \"camera\" field and every per-beat \"camera\" "
810
+ "field as empty strings. Camera is handled elsewhere — do not mention lenses, "
811
+ "shots, angles, zooms or any camera movement anywhere in the action text either.\n")
812
+ cam_schema = '"camera":""'
813
+
814
+ # H3는 한 클립에 여러 샷을 넣는 �� 기본 지원 기능이라, 막는 지시가 없으면
815
+ # 비트 경계마다 컷을 넣어버린다(실제로 겪은 버그). 그래서 규칙으로 못 박는다.
816
+ no_cut_rule = (
817
+ "0) ONE CONTINUOUS TAKE — THIS OVERRIDES EVERYTHING ELSE. The whole clip is a single "
818
+ "unbroken shot filmed in one go. The beats below are moments WITHIN that one shot, not "
819
+ "separate shots in an edit. Never write a cut, a shot change, a new angle, a "
820
+ "reframing, a dissolve, a transition, 'meanwhile', or 'we then see'. If the viewpoint "
821
+ "needs to change, the CAMERA MOVES there continuously — it never cuts there. The "
822
+ "subject must remain physically present and continuously visible the entire time; they "
823
+ "cannot teleport, reappear elsewhere, or be replaced by a different framing of "
824
+ "themselves.\n"
825
+ # 이게 실제로 컷을 유발한 범인이었다: 이 렌더러는 숫자 시각 표기를
826
+ # "그 시각에 컷하라"는 선언으로 읽는다. 슬롯 경계는 우리가 배분용으로만
827
+ # 쓰고, 모델에 넘어가는 문장에는 숫자가 한 개도 없어야 한다.
828
+ "0b) NEVER WRITE A TIME MARKER. Do not put any numeric time reference inside "
829
+ "\"action\", \"subject\", \"camera\" or \"atmosphere\" — no '0-2s', no '00:03', no "
830
+ "'at 5 seconds', no 'after two seconds', no 'by the 10-second mark'. In this renderer "
831
+ "a numeric time marker declares a CUT POINT, which is exactly what we are trying to "
832
+ "avoid. The beat boundaries you are given below are for pacing only: use them to "
833
+ "decide HOW MUCH motion goes in each beat, never quote them back.\n"
834
+ if no_cut else "")
835
+
836
+ continuity_rule = ""
837
+ if previous_action.strip():
838
+ continuity_rule = (
839
+ "2b) CONTINUES A PRIOR CLIP. The action in PREVIOUS CLIP ACTION below already "
840
+ "happened. The first beat must begin from the exact physical end-state that action "
841
+ "would leave the body in — same position, orientation, momentum — not from a reset "
842
+ "neutral stance, and it must not repeat that action. Do not narrate the hand-off "
843
+ "(no 'continuing', 'still', 'as before').\n")
844
+
845
+ # 공식 H3 포맷의 오디오 2필드(overall_soundscape/non_diegetic_music). H3는
846
+ # 비디오+오디오를 동시생성하는 모델이라 이 지시가 없으면 모델이 사운드를 알아서
847
+ # 지어낸다 - 의도한 사운드를 원하면 명시해야 한다.
848
+ if include_audio:
849
+ audio_rule = (
850
+ "14) AUDIO (official H3 fields). Also write two short audio-direction fields "
851
+ "matching the mood of \"atmosphere\" and the physical intensity of the beats:\n"
852
+ " \"soundscape\": 1 to 4 sentences of DIEGETIC sound only — ambience, physical "
853
+ "action noise (footsteps, fabric, contact, mechanical/environmental sound), "
854
+ "non-verbal human sound (breathing, effort). Never mention dialogue or music here.\n"
855
+ " \"music\": 1 to 3 sentences of NON-diegetic score — instruments, tempo, rhythm, "
856
+ "and how the dynamics change across the clip. If the scene genuinely calls for no "
857
+ "score, write exactly \"None.\" instead of inventing one.\n"
858
+ "15) DIALOGUE (per-beat \"dialogue_manner\" and \"dialogue_line\" fields). This "
859
+ "renderer invents meaningless gibberish speech on its own whenever a moment looks "
860
+ "like talking but no exact line was given — so silence must be deliberate, not "
861
+ "accidental. For EACH beat, decide independently whether that specific instant "
862
+ "plausibly calls for a short spoken or vocal line (a reaction, a name, a request, "
863
+ "a taunt, a moan, a gasp with words). Most beats will NOT need one — do not force "
864
+ "a line into every beat. When a beat does get one, fill BOTH fields: "
865
+ "\"dialogue_manner\" is a short prose phrase describing HOW it is delivered (e.g. "
866
+ "'moaning softly', 'breathlessly', 'sharply, as a startled curse', 'teasing') — "
867
+ "this is narration that is never spoken aloud, so plain English prose is fine. "
868
+ "\"dialogue_line\" is ONLY the exact spoken text: '[Language] <exact short line, "
869
+ "under 8 words>' — e.g. [Korean] 거기, 거기예요, [English] don't stop, [Korean] "
870
+ "아 시발, or [English] is that all you've got. Those four only show the FORMAT — "
871
+ "never reuse one verbatim or default to the single most obvious line for the "
872
+ "moment (reflex filler like 'yes/more/don't stop' for pleasure, a bare gasp of "
873
+ "the character's name for surprise). "
874
+ "15c) The TAKE VARIATION block below (if present) names a delivery register for "
875
+ "this take — let dialogue_manner and the actual wording/rhythm of dialogue_line "
876
+ "lean into that register instead of defaulting to the flattest obvious phrasing. "
877
+ "Write a line specific to what THIS beat's "
878
+ "action and delivery manner actually are — a taunt sounds different from a plea, a "
879
+ "startled reaction sounds different from a satisfied one. If PREVIOUS CLIP ACTION "
880
+ "(rule 2b) already contains a dialogue line, this clip's lines must be different "
881
+ "wording, not a repeat or a trivial rephrase of it. Pick whichever language "
882
+ "actually fits the character and setting (default to English unless the "
883
+ "name/scene clearly calls for another one), and write the line itself in that "
884
+ "language's OWN NATIVE SCRIPT — never a romanized/phonetic spelling of it (write "
885
+ "안녕하세요, not 'annyeonghaseyo'; write ありがとう, not 'arigatou'; write 谢谢, "
886
+ "not 'xiexie'). The \"[Language]\" tag stays in English; only the text after it "
887
+ "switches script. Do NOT put quote marks around the line and do NOT repeat any of "
888
+ "the delivery-manner words inside \"dialogue_line\" — this renderer speaks the "
889
+ "exact text of \"dialogue_line\" aloud verbatim, so anything written in that field "
890
+ "is heard exactly as written, while \"dialogue_manner\" is only ever narrated as "
891
+ "prose and is never itself spoken (this is why the two are separate fields — "
892
+ "putting a delivery description inside \"dialogue_line\" makes it get spoken too). "
893
+ "Never put a numeric timestamp in either field. Leave BOTH as empty strings for "
894
+ "every beat with no line; if none of the beats end up with a line, that is "
895
+ "normal and expected for most short clips — do not manufacture one just to fill "
896
+ "the fields.\n")
897
+ audio_schema = ',"soundscape":"<1-4 sentences, diegetic sound only>","music":"<1-3 sentences, or \\"None.\\">"'
898
+ else:
899
+ audio_rule, audio_schema = "", ""
900
+
901
+ sys = (
902
+ f"You are a shot director writing a prompt for the MiniMax (Hailuo) video model. "
903
+ f"The output is ONE {dur_s}-second clip, and you are choreographing it second by "
904
+ f"second so the model never idles, loops, or repeats a beat to fill time.\n"
905
+ "\n"
906
+ "ABSOLUTE RULES:\n"
907
+ + no_cut_rule + action_rule + arc_rule +
908
+ "2) PHYSICAL CONTINUITY. The beats are one unbroken performance, not separate poses. "
909
+ "Each beat starts from the exact body position, orientation and momentum the previous "
910
+ "beat ended in. Never restart from a neutral stance mid-clip.\n"
911
+ + continuity_rule +
912
+ "3) STARTING PHYSICS. Read the character's CURRENT pose and what they are supported by "
913
+ "or in contact with from the SCENE ANCHORS (seated on something, kneeling, gripping an "
914
+ "object, leaning on a wall). The first beat must start FROM that exact situation and "
915
+ "honour its constraints — do not have them standing if the anchors show them seated "
916
+ "without first getting up. Spend at most one short clause on this grounding, and make "
917
+ "that clause itself an active verb, never a static description of the pose.\n"
918
+ "4) ONE PRIMARY MOTION PER BEAT. Each beat gets exactly one clear physical movement "
919
+ "that genuinely fills its time window — not a list of three things crammed into two "
920
+ "seconds, and not a held pose. Write plain physical movement: what the body does. "
921
+ "Present tense, third person.\n"
922
+ "4c) TAKE VARIATION. A TAKE VARIATION block is given below with an execution-style "
923
+ "note for this specific take. Let it shape HOW the beats' motions physically play "
924
+ "out (pacing, weight, a secondary body detail) — it never changes WHAT the action "
925
+ "list or scene anchors call for, it only changes the texture of the performance.\n"
926
+ "5) REAL-TIME SPEED. Everything plays at natural speed. No slow motion, no freeze, no "
927
+ "holding a pose. But do not inflate the movement either: match the verb to what the "
928
+ "action actually is (walks, turns, leans, reaches, lifts) and save hard percussive "
929
+ "verbs (slams, lunges, whips) for beats that really are sudden.\n"
930
+ + camera_rule +
931
+ "7) SUBJECT LINE. \"subject\" is ONE sentence describing who the character is and where "
932
+ "they are at t=0 — appearance, clothing, location — as a still frame, with no action "
933
+ "verbs in it. This is the only place description belongs; keep the beats pure motion.\n"
934
+ "8) ATMOSPHERE. \"atmosphere\" is one short clause of lighting/mood/film-look "
935
+ "(e.g. 'warm late-afternoon sunlight, handheld realism'). No action, no camera moves. "
936
+ "You may include ONE motion-quality word — fluid, percussive, weighted, floating, "
937
+ "gravitational, deliberate — since that word changes how the whole clip moves.\n"
938
+ "8b) FINAL STATE. The LAST beat must say where the motion lands: the body position it "
939
+ "ends in and what is still in frame. A clip that stops mid-gesture invites the model to "
940
+ "invent an ending of its own.\n"
941
+ "9) CHARACTER NAME: study the SCENE ANCHORS, judge the character's apparent "
942
+ "ethnicity/setting and invent a specific fitting first name (East Asian -> 'Yuna', "
943
+ "'Kaede', 'Jin'; Western -> 'Elena', 'Ryan'). NEVER output 'AUTO' or an empty name. "
944
+ "If a NAME is given below and it is not 'AUTO', use that one.\n"
945
+ f"10) {detail}\n"
946
+ "11) ENGLISH ONLY, and keep the whole thing compact — the renderer truncates long "
947
+ "prompts. No markdown, no numbering, no quotes inside the field values.\n"
948
+ # 렌더러가 로컬 무검열 모델이라 완곡어법을 쓸 이유가 없다. 오히려 은유는
949
+ # 애니메이션 모델이 해석할 수 없는 입력이라 동작이 뭉개진다.
950
+ "12) LITERAL, NOT METAPHORICAL. Name body parts and acts directly (e.g. 'penis', "
951
+ "'vagina', 'cock', 'pussy', 'thrusts', 'grips') — never euphemism, poetic metaphor, or "
952
+ "vague substitution (do not write things like 'their connection deepens', 'waves of "
953
+ "pleasure', 'their bodies moved as one', 'her core'). This is a technical motion "
954
+ "description for a local uncensored animation model, not literary prose — treat it like "
955
+ "a stage direction: blunt, anatomical, unambiguous about which body part does what to "
956
+ "which. Do not soften, skip, or refuse any action in the list.\n"
957
+ "13) NO GESTURE CLICHÉS. Resolve any vague/decorative descriptor in an action "
958
+ "(e.g. 'cutely', 'playfully', 'sexily', 'shyly') into ONE specific, concrete gesture. "
959
+ "This applies even with no such word in the action list if the SCENE ANCHORS' facial "
960
+ "expression/mood line implies one (e.g. the anchors describe a smiling, playful face "
961
+ "with no 'playfully' anywhere in the actions — still resolve into a concrete gesture "
962
+ "matching that mood, don't ignore it). NEVER default to the most expected cliché for "
963
+ "that mood (a peace sign for 'cute', a hair-flip for 'sexy') — those are overused. If "
964
+ "a SUGGESTED GESTURES block is given below, use the listed gesture for the matching "
965
+ "mood (adapted naturally into the action) instead of inventing your own; otherwise "
966
+ "invent one, and pick a DIFFERENT one from PREVIOUS CLIP ACTION if that already used "
967
+ "a specific gesture.\n"
968
+ + audio_rule +
969
+ "\n"
970
+ 'OUTPUT ONLY JSON: {"character":"<a real name, never AUTO>","subject":"<one sentence>",'
971
+ + cam_schema + "," + schema_beats + ',"atmosphere":"<one short clause>"' + audio_schema + "}"
972
+ )
973
+
974
+ usr = (f"NAME: {name}\n"
975
+ f"CLIP LENGTH: {dur_s} seconds\n\n"
976
+ f"STYLE CUE (use only to calibrate how the movement feels and for the atmosphere "
977
+ f"line — do not quote it): {vibe.strip() or '(none)'}\n\n"
978
+ f"SCENE ANCHORS (from the input image — use the pose to ground the first beat and "
979
+ f"the overall look for the subject line):\n"
980
+ f"{anchors or '(none — no image given; invent a plausible subject from the actions)'}\n\n"
981
+ + (f"PREVIOUS CLIP ACTION (per rule 2b):\n{previous_action.strip()}\n\n"
982
+ if previous_action.strip() else "")
983
+ + _gesture_hints(actions, anchors, seed)
984
+ + _variation_hints(seed)
985
+ + f"{assign_label}\n{assign_body}\n\n"
986
+ f"BEAT SLOTS (fill every one, in order):\n{slot_block}\n")
987
+
988
+ messages = [{"role": "system", "content": sys}, {"role": "user", "content": usr}]
989
+ t = temp
990
+ last = "⚠️ plan failed (no attempts ran)"
991
+ for attempt in (1, 2):
992
+ try:
993
+ content = _call_openrouter(messages, llm_model, key, t, seed=seed, force_json=True,
994
+ usage_acc=getattr(self, "_usage_acc", None))
995
+ parsed = self._validate_plan(self._extract(content), bounds, duration)
996
+ if parsed:
997
+ return parsed, f"✅ plan ok (attempt {attempt})"
998
+ last = f"⚠️ incomplete JSON (attempt {attempt}): {content[:150]!r}"
999
+ print(f"⚠️ [MinimaxDirector] plan attempt {attempt} incomplete JSON: {content[:300]}")
1000
+ except urllib.error.HTTPError as e:
1001
+ body = ""
1002
+ try:
1003
+ body = e.read().decode("utf-8")[:200]
1004
+ except Exception:
1005
+ pass
1006
+ last = f"❌ plan HTTP {e.code} (attempt {attempt}): {body}"
1007
+ print(f"❌ [MinimaxDirector] {last}")
1008
+ except (urllib.error.URLError, TimeoutError, KeyError, ValueError) as e:
1009
+ last = f"❌ plan error (attempt {attempt}): {type(e).__name__}: {e}"
1010
+ print(f"❌ [MinimaxDirector] {last}")
1011
+ t = min(temp, 0.4)
1012
+ return None, last
1013
+
1014
+ def _extract(self, text):
1015
+ # JSON 파싱은 검증된 기존 구현을 그대로 씀(코드펜스/트레일링 콤마 처리 포함).
1016
+ return DolphinVisionDirector()._extract_json(text)
1017
+
1018
+ def _validate_plan(self, parsed, bounds, duration):
1019
+ """LLM 응답에서 beats를 꺼내 구간·동작이 실제로 채워졌는지 확인하고 정규화."""
1020
+ if not isinstance(parsed, dict):
1021
+ return None
1022
+ beats = parsed.get("beats")
1023
+ if not isinstance(beats, list) or not beats:
1024
+ return None
1025
+ if bounds is not None and len(beats) != len(bounds):
1026
+ # 슬롯 수가 안 맞으면 재시도. (개수가 맞아야 초 단위 지정이 의미가 있다.)
1027
+ return None
1028
+ clean = []
1029
+ for i, b in enumerate(beats):
1030
+ if not isinstance(b, dict):
1031
+ return None
1032
+ action = str(b.get("action", "")).strip()
1033
+ if not action:
1034
+ return None
1035
+ if bounds is not None:
1036
+ start, end = bounds[i]
1037
+ else:
1038
+ try:
1039
+ start, end = float(b.get("start")), float(b.get("end"))
1040
+ except (TypeError, ValueError):
1041
+ return None
1042
+ if end <= start or end > duration + 1e-6:
1043
+ return None
1044
+ clean.append({"start": float(start), "end": float(end), "action": action,
1045
+ "camera": _norm_camera(b.get("camera")),
1046
+ "dialogue_manner": str(b.get("dialogue_manner", "") or "").strip(),
1047
+ "dialogue_line": str(b.get("dialogue_line", "") or "").strip()})
1048
+ parsed["beats"] = clean
1049
+ return parsed
1050
+
1051
+ # ------------------------------------------------------------------
1052
+ # LLM 없이(키 없음/실패) 쓰는 로컬 폴백
1053
+ # ------------------------------------------------------------------
1054
+ # 폴백 전용 위상 라벨. 한 동작이 여러 비트에 걸칠 때 똑같은 문장을 그대로 반복하는
1055
+ # 대신 어느 단계인지라도 구분해준다. LLM 없이 할 수 있는 건 여기까지.
1056
+ _PHASES = ["(wind-up)", "(committing)", "(mid-motion)", "(peak effort)", "(follow-through)"]
1057
+
1058
+ def _local_beats(self, actions, bounds, duration):
1059
+ """API 키가 없거나 LLM 플래닝이 실패했을 때 쓰는 로컬 분배.
1060
+
1061
+ DolphinVisionDirector._split_actions_to_clips는 클립 4개 전제라 비트가 10개쯤
1062
+ 되면 남는 비트를 전부 "(move toward) <마지막동작>"으로 채워버린다 — 20초 클립에서
1063
+ 같은 줄이 6번 반복되는 꼴이라 여기서는 쓰지 않고 직접 나눈다."""
1064
+ if bounds is None:
1065
+ bounds = _beat_bounds(duration, float(duration) / _auto_beat_count(duration))
1066
+ if not actions.strip():
1067
+ actions = "몸을 움직이기 시작한다, 동작에 힘이 실린다, 자세를 마무리한다"
1068
+ parts = split_action_parts(actions) or [actions.strip()]
1069
+
1070
+ n = len(bounds)
1071
+ phrases = []
1072
+ if len(parts) >= n:
1073
+ # 동작이 비트보다 많으면 연속된 것끼리 묶어서 한 비트에 넣는다.
1074
+ k, m = divmod(len(parts), n)
1075
+ idx = 0
1076
+ for i in range(n):
1077
+ take = k + (1 if i < m else 0)
1078
+ phrases.append(", ".join(parts[idx:idx + take]))
1079
+ idx += take
1080
+ else:
1081
+ # 동작이 적으면 각 동작에 연속된 비트 구간을 통째로 배정하고,
1082
+ # 그 안에서는 위상 라벨로 구분한다.
1083
+ k, m = divmod(n, len(parts))
1084
+ for i, phrase in enumerate(parts):
1085
+ span = k + (1 if i < m else 0)
1086
+ for j in range(span):
1087
+ if span == 1:
1088
+ phrases.append(phrase)
1089
+ else:
1090
+ pos = int(round(j * (len(self._PHASES) - 1) / (span - 1)))
1091
+ phrases.append(f"{self._PHASES[pos]} {phrase}")
1092
+
1093
+ # 로컬 폴백은 LLM이 없어서 맥락에 맞는 대사를 지어낼 수 없다 - 항상 빈 채로
1094
+ # 두고, _assemble이 DIALOGUE_NEGATIVE로 무대사를 명시하게 맡긴다.
1095
+ return [{"start": a, "end": b, "action": phrases[i], "camera": "",
1096
+ "dialogue_manner": "", "dialogue_line": ""}
1097
+ for i, (a, b) in enumerate(bounds)]
1098
+
1099
+ # ------------------------------------------------------------------
1100
+ # 최종 프롬프트 조립
1101
+ # ------------------------------------------------------------------
1102
+ def _resolve_camera(self, beats, camera_mode, camera_manual, camera_overall):
1103
+ """카메라를 (구간별 대괄호) 또는 (클립 전체를 관통하는 문장 하나) 중 하나로 확정.
1104
+
1105
+ H3에서는 비트마다 카메라를 갈아끼우는 것 자체가 컷 신호라서, 기본값은
1106
+ '전체 하나'다. 구간별 대괄호는 구형 Director 모델용으로만 남겨둔다."""
1107
+ for b in beats:
1108
+ b.setdefault("camera", "")
1109
+ if camera_mode.startswith("Per-segment"):
1110
+ manual = str(camera_manual or "")
1111
+ if "|" in manual:
1112
+ groups = [_norm_camera(g) for g in manual.split("|")]
1113
+ for i, b in enumerate(beats):
1114
+ b["camera"] = groups[i] if i < len(groups) else ""
1115
+ return beats, ""
1116
+
1117
+ for b in beats:
1118
+ b["camera"] = ""
1119
+ if camera_mode.startswith("off"):
1120
+ return beats, ""
1121
+ # 전체 카메라 문장에 대괄호가 남으면 그것도 샷 마커로 읽힌다.
1122
+ if camera_mode.startswith("manual"):
1123
+ return beats, _strip_brackets(camera_manual or "")
1124
+ return beats, _strip_brackets(camera_overall or "")
1125
+
1126
+ def _assemble(self, subject, beats, atmosphere, camera_overall, style, name, no_cut,
1127
+ include_audio):
1128
+ head = subject.strip().strip(",. ")
1129
+ if name and head and name.lower() not in head.lower():
1130
+ head = f"{name}, {head[0].lower() + head[1:]}"
1131
+ elif name and not head:
1132
+ head = name
1133
+ blocks = []
1134
+ if head:
1135
+ blocks.append(head.rstrip(".") + ".")
1136
+
1137
+ # 어느 비트든 대사가 하나라도 있으면 DIALOGUE_NEGATIVE를 안 붙인다 - 명시된
1138
+ # 대사와 "대사 없음" 선언이 동시에 들어가면 모순이라서.
1139
+ any_dialogue = any(str(b.get("dialogue_line", "")).strip() for b in beats)
1140
+
1141
+ def _dialogue_sentence(b):
1142
+ """비트의 dialogue_manner/dialogue_line에서 화자 절을 만든다.
1143
+ manner(전달 방식 묘사)는 산문으로 서술하고, 실제 발화 내용만 공식 H3
1144
+ <d>...</d> 태그로 감싼다 - 한 필드에 같이 넣었더니 톤 묘사 단어까지
1145
+ H3가 그대로 발화하는 문제가 있어서 필드부터 분리했다(2026-08-25)."""
1146
+ line = str(b.get("dialogue_line", "")).strip()
1147
+ if not line:
1148
+ return ""
1149
+ manner = str(b.get("dialogue_manner", "")).strip()
1150
+ if manner:
1151
+ return f"{name}, {manner}, says <d>{line}</d>"
1152
+ return f"{name} says <d>{line}</d>"
1153
+
1154
+ def _dlg_clause(b):
1155
+ """비트에 대사가 있으면 이어붙일 절(선행 콤마 포함)을 반환. 없으면 빈 문자열."""
1156
+ s = _dialogue_sentence(b)
1157
+ return f", {s}" if s else ""
1158
+
1159
+ if style.startswith("Continuous prose"):
1160
+ # 숫자 타임코드를 한 개도 쓰지 않고 지속시간을 말로만 표현한다.
1161
+ # H3는 `0-2s` 같은 숫자 마커를 컷 지점 선언으로 읽기 때문 - 타이밍은
1162
+ # 전달하면서 컷 신호는 안 주는 유일한 방법.
1163
+ segs = []
1164
+ for i, b in enumerate(beats):
1165
+ d, approx = _dur_parts(b["end"] - b["start"])
1166
+ fuzzy = "roughly " if approx else ""
1167
+ body = _lower_first(_strip_brackets(b["action"]))
1168
+ if i == 0:
1169
+ lead = f"For {fuzzy}the first {d}"
1170
+ elif i == len(beats) - 1:
1171
+ lead = f"and through {fuzzy}the final {d}"
1172
+ else:
1173
+ lead = f"then for {fuzzy}the next {d}"
1174
+ segs.append(f"{lead} {body}{_dlg_clause(b)}")
1175
+ blocks.append(", ".join(segs).rstrip(".") + ".")
1176
+ elif style.startswith("Second timeblocking"):
1177
+ # 정확한 초를 지켜야 할 때만. H3에선 이게 곧 멀티샷 블로킹이라 컷이 난다.
1178
+ segs = [f"{_fmt_sec(b['start'])}-{_fmt_sec(b['end'])}s "
1179
+ f"{_lower_first(_strip_brackets(b['action']))}{_dlg_clause(b)}"
1180
+ for b in beats]
1181
+ blocks.append(", ".join(segs).rstrip(".") + ".")
1182
+ elif style.startswith("Sequential connectives"):
1183
+ segs = []
1184
+ for i, b in enumerate(beats):
1185
+ body = _lower_first(_strip_brackets(b["action"]))
1186
+ if i == 0:
1187
+ lead = "First,"
1188
+ elif i == len(beats) - 1:
1189
+ lead = "finally"
1190
+ else:
1191
+ lead = _SEQ_CONNECTIVES[(i - 1) % len(_SEQ_CONNECTIVES)]
1192
+ segs.append(f"{lead} {body}{_dlg_clause(b)}")
1193
+ blocks.append(", ".join(segs).rstrip(".") + ".")
1194
+ elif style.startswith("Multi-shot"):
1195
+ # 컷을 '일부러' 낼 때만. H3 공식 문법: [Shot 1]은 타임코드 없이,
1196
+ # 이후 샷은 [Shot N] At mm:ss.mmm, ... 로 컷 시점을 못 박는다.
1197
+ for i, b in enumerate(beats, 1):
1198
+ body = b["action"].strip().rstrip(".")
1199
+ if i == 1:
1200
+ line = f"[Shot 1] {body}."
1201
+ else:
1202
+ # 마커 자체가 컷을 뜻하므로 "the camera cuts to"를 덧붙이지 않는다
1203
+ # (붙이면 "cuts to she strides"처럼 비문이 된다).
1204
+ line = f"[Shot {i}] At {_fmt_timecode(b['start'])}, {_lower_first(body)}."
1205
+ if b["camera"]:
1206
+ line = f"{line} {b['camera']}"
1207
+ s = _dialogue_sentence(b)
1208
+ if s:
1209
+ line = f"{line} {s}"
1210
+ blocks.append(line)
1211
+ else: # Director 대괄호
1212
+ for b in beats:
1213
+ s, e = _fmt_sec(b["start"]), _fmt_sec(b["end"])
1214
+ line = f"[{s}-{e}s] {b['action'].strip().rstrip('.')}."
1215
+ if b["camera"]:
1216
+ line = f"{line} {b['camera']}"
1217
+ s2 = _dialogue_sentence(b)
1218
+ if s2:
1219
+ line = f"{line} {s2}"
1220
+ blocks.append(line)
1221
+
1222
+ # 한 문단으로 붙는 모드에서는 이것들이 문장 중간에 오므로 첫 글자를 올린다.
1223
+ if camera_overall.strip():
1224
+ blocks.append(_sentence(camera_overall))
1225
+ if atmosphere.strip():
1226
+ blocks.append(_sentence(atmosphere))
1227
+ # 대사가 어디에도 안 들어갔으면 "무대사"를 명시한다 - 공식 HF 디스커션
1228
+ # #76에서 확인된 대로, 명시 안 하면 H3가 의미불명의 음성을 지어낸다.
1229
+ if include_audio and not any_dialogue:
1230
+ blocks.append(DIALOGUE_NEGATIVE)
1231
+ # 멀티샷은 컷이 목적이라 제약을 붙이면 자기모순이 된다.
1232
+ if no_cut and not style.startswith("Multi-shot"):
1233
+ blocks.append(NO_CUT_CONSTRAINT)
1234
+
1235
+ # 줄바꿈도 "여기서 끊긴다"는 구분자 신호로 읽힌다. 연속 테이크를 노리는
1236
+ # 스타일은 전부 한 문단으로 붙이고, 샷을 나누는 스타일만 줄로 분리한다.
1237
+ if style.startswith("Continuous prose") or style.startswith("Sequential connectives"):
1238
+ return " ".join(blocks)
1239
+ return "\n".join(blocks)
1240
+
1241
+ def _finalize_output(self, body, soundscape, music, image_present,
1242
+ include_audio, i2v_prefix):
1243
+ """공식 H3 포맷으로 최종 감싸기. `body`(기존 _assemble 결과, 컷 회피 로직
1244
+ 검증 완료)는 절대 건드리지 않고 앞뒤로 라벨/프리픽스만 붙인다.
1245
+
1246
+ i2v_prefix는 include_audio와 무관하게 독립 토글 - 정렬 문장은 오디오
1247
+ 유무와 상관없이 이미지가 있으면 붙는 게 공식 스펙."""
1248
+ parts = []
1249
+ if i2v_prefix and image_present:
1250
+ parts.append("For the target video, at 0.00 seconds into the target video, "
1251
+ "<Picture 1> (from [Shot 1]) is fully referenced.")
1252
+ if include_audio:
1253
+ parts.append(f"integrated_multimodal_description: {body}")
1254
+ if soundscape.strip():
1255
+ parts.append(f"overall_soundscape: {soundscape.strip()}")
1256
+ if music.strip():
1257
+ parts.append(f"non_diegetic_music: {music.strip()}")
1258
+ else:
1259
+ parts.append(body)
1260
+ return "\n\n".join(parts)
1261
+
1262
+ # ------------------------------------------------------------------
1263
+ def direct(self, essential_actions, duration_seconds, beat_seconds, prompt_style,
1264
+ no_cut_constraint, camera_mode, camera_manual, character_name, artistic_vibe,
1265
+ detail_level, use_vision, openrouter_api_key, vlm_preset, vlm_model, llm_preset,
1266
+ llm_model, creativity, seed, include_audio_fields=True,
1267
+ i2v_alignment_prefix=False, **kw):
1268
+
1269
+ engine = DolphinVisionDirector()
1270
+ # 비전(engine._call_or)과 플래닝(_call_openrouter)이 같은 dict에 토큰/비용 누적.
1271
+ self._usage_acc = {"prompt": 0, "completion": 0, "cost": 0.0}
1272
+ engine._usage_acc = self._usage_acc
1273
+ image = kw.get("image", None)
1274
+ vision_caption = str(kw.get("vision_caption", "") or "").strip()
1275
+ previous_action = str(kw.get("previous_action", "") or "").strip()
1276
+
1277
+ dur = float(duration_seconds)
1278
+ bounds = _beat_bounds(dur, float(beat_seconds))
1279
+ vlm = resolve_slug(VLM_PRESETS, vlm_preset, vlm_model, "qwen/qwen2.5-vl-72b-instruct")
1280
+ llm = resolve_slug(LLM_PRESETS, llm_preset, llm_model, "deepseek/deepseek-v3.2")
1281
+ user_name = "" if character_name.strip().upper() in ["AUTO", ""] else character_name.strip()
1282
+ key = resolve_api_key(openrouter_api_key)
1283
+
1284
+ # ---- STEP 1: 비전 앵커 ----
1285
+ anchors, dbg_v = "", ""
1286
+ if vision_caption:
1287
+ anchors, dbg_v = vision_caption, "vision ok (local caption node)"
1288
+ elif not use_vision:
1289
+ dbg_v = "vision OFF (check toggle)"
1290
+ elif image is None:
1291
+ dbg_v = "no image (T2V mode)"
1292
+ elif not key:
1293
+ dbg_v = "no api key"
1294
+ print("❌ [MinimaxDirector] openrouter_api_key missing (neither widget nor OPENROUTER_API_KEY env var set) - vision/LLM unavailable")
1295
+ else:
1296
+ data_uri = tensor_to_base64(image)
1297
+ if data_uri:
1298
+ anchors, err = engine._vision_analyze(data_uri, vlm, key, min(creativity, 0.5))
1299
+ dbg_v = err or "vision ok"
1300
+ else:
1301
+ dbg_v = "image encode failed / PIL missing"
1302
+
1303
+ # ---- STEP 2: 초 단위 비트 플래닝 ----
1304
+ if camera_mode.startswith("Per-segment"):
1305
+ camera_style = "perbeat"
1306
+ elif camera_mode.startswith("off") or camera_mode.startswith("manual"):
1307
+ camera_style = "none"
1308
+ else:
1309
+ camera_style = "continuous"
1310
+ # 멀티샷 스타일은 컷을 내는 게 목적이라 연속 테이크 제약을 걸면 자기모순.
1311
+ no_cut = bool(no_cut_constraint) and not prompt_style.startswith("Multi-shot")
1312
+ subject, atmosphere, camera_overall = "", artistic_vibe.strip(), ""
1313
+ if key:
1314
+ parsed, dbg_p = self._plan_beats(
1315
+ anchors, essential_actions, character_name, artistic_vibe,
1316
+ engine._detail_hint(detail_level), dur, bounds, camera_style, no_cut,
1317
+ llm, key, creativity, seed, previous_action=previous_action,
1318
+ include_audio=bool(include_audio_fields))
1319
+ else:
1320
+ parsed, dbg_p = None, "no api key"
1321
+
1322
+ if parsed:
1323
+ beats = parsed["beats"]
1324
+ subject = str(parsed.get("subject", "")).strip()
1325
+ atmosphere = str(parsed.get("atmosphere", "")).strip() or artistic_vibe.strip()
1326
+ camera_overall = str(parsed.get("camera", "")).strip()
1327
+ cand = str(parsed.get("character", "")).strip()
1328
+ if not cand or cand.upper() == "AUTO":
1329
+ cand = engine._fallback_name(anchors)
1330
+ final_name = user_name or cand
1331
+ soundscape = str(parsed.get("soundscape", "")).strip() if include_audio_fields else ""
1332
+ music = str(parsed.get("music", "")).strip() if include_audio_fields else ""
1333
+ else:
1334
+ beats = self._local_beats(essential_actions, bounds, dur)
1335
+ # 폴백에선 앵커를 그대로 피사체 문장으로 쓴다(한 줄로 눌러서).
1336
+ subject = " ".join(anchors.split())[:200]
1337
+ final_name = user_name or (engine._fallback_name(anchors) if anchors else "")
1338
+ # 로컬 폴백은 LLM이 없어서 오디오 문장을 지어낼 수 없다 - 빈 채로 둔다
1339
+ # (_finalize_output이 빈 문자열이면 해당 라벨 섹션 자체를 생략함).
1340
+ soundscape, music = "", ""
1341
+
1342
+ # ---- STEP 3: 카메라 확정 + 조립 ----
1343
+ beats, camera_overall = self._resolve_camera(beats, camera_mode, camera_manual,
1344
+ camera_overall)
1345
+ body = self._assemble(subject, beats, atmosphere, camera_overall, prompt_style,
1346
+ final_name, no_cut, bool(include_audio_fields))
1347
+
1348
+ # 연속 테이크를 노리는데 숫자 시각 표기가 새어나갔으면 잡아준다(LLM 규칙 0b 위반).
1349
+ # 초 타임블로킹/Director는 숫자를 내는 게 정상이라 검사 대상이 아니다. 오디오
1350
+ # 필드(soundscape/music)는 별도 텍스트라 이 검사 대상이 아니다.
1351
+ if no_cut and not prompt_style.startswith(("Second timeblocking", "Director brackets")):
1352
+ leaked = re.findall(r"\d+\s*(?:-\s*\d+\s*)?(?:s\b|sec|seconds?\b)|\d{2}:\d{2}", body)
1353
+ if leaked:
1354
+ print(f"⚠️ [MinimaxDirector] Numeric time markers leaked into the prompt {leaked[:5]} - "
1355
+ f"H3 reads these as cut points. This means the LLM violated rule 0b; "
1356
+ f"re-run, or use the 'Sequential connectives' style.")
1357
+
1358
+ i2v_prefix_applied = bool(i2v_alignment_prefix) and image is not None
1359
+ prompt = self._finalize_output(body, soundscape, music, image is not None,
1360
+ bool(include_audio_fields), bool(i2v_alignment_prefix))
1361
+
1362
+ # 하드 상한은 실제로 필드가 잘리는 기준이라 오디오 라벨까지 합친 최종
1363
+ # prompt(전체 전송분) 기준. 소프트 상한("길면 지시를 평균낸다")은 원래
1364
+ # 타임라인/동작 지시가 있는 body 분량에 대한 경험칙이라, 오디오 라벨/
1365
+ # soundscape/music/I2V 프리픽스처럼 body 뒤에 붙는 별개 콘텐츠까지 합쳐서
1366
+ # 재면 오디오 필드를 켜기만 해도 항상 걸려서 무의미한 경고��� 된다.
1367
+ if len(prompt) > MINIMAX_FIELD_LIMIT:
1368
+ print(f"❌ [MinimaxDirector] Prompt exceeds the field limit ({MINIMAX_FIELD_LIMIT} chars) "
1369
+ f"by {len(prompt) - MINIMAX_FIELD_LIMIT} chars - the tail will be truncated.")
1370
+ elif len(body) > MINIMAX_SOFT_LIMIT:
1371
+ print(f"⚠️ [MinimaxDirector] The video description (body) is {len(body)} chars "
1372
+ f"(recommended under {MINIMAX_SOFT_LIMIT}, full prompt is {len(prompt)} chars). "
1373
+ f"If it's too long the model averages out the instructions and follows the "
1374
+ f"timeline less closely - lower detail_level or reduce the beat count. "
1375
+ f"(soundscape/music/I2V prefix are not counted against this limit.)")
1376
+ if prompt_style.startswith("Director brackets"):
1377
+ print("⚠️ [MinimaxDirector] 'Director brackets' is for legacy T2V-01-Director-family "
1378
+ "models only. Using it with H3 makes [0-2s] read as a shot marker, causing a "
1379
+ "cut at every timecode.")
1380
+ elif prompt_style.startswith("Second timeblocking") and no_cut:
1381
+ print("⚠️ [MinimaxDirector] 'Second timeblocking' is effectively H3's multi-shot "
1382
+ "blocking syntax - numeric timecodes are read as cut-point declarations, which "
1383
+ "conflicts with the no_cut constraint. To avoid cuts, use 'Continuous prose' "
1384
+ "or 'Sequential connectives'.")
1385
+ if include_audio_fields and key and not (soundscape or music):
1386
+ print("⚠️ [MinimaxDirector] include_audio_fields is ON but the LLM didn't fill "
1387
+ "soundscape/music - they will be omitted from the output.")
1388
+
1389
+ timeline_json = json.dumps(
1390
+ {"duration": dur, "character": final_name, "subject": subject,
1391
+ "camera": camera_overall, "atmosphere": atmosphere, "beats": beats,
1392
+ "soundscape": soundscape, "non_diegetic_music": music,
1393
+ "i2v_alignment_prefix_applied": i2v_prefix_applied},
1394
+ ensure_ascii=False, indent=2)
1395
+ # llm/vlm 슬러그를 debug_info에 노출한다 - preset 드롭다운을 바꿔도 실제로
1396
+ # 어떤 모델이 호출됐는지 확인할 화면 요소가 없어서, JS 프리셋 동기화가
1397
+ # 깨졌을 때("모델이 안 바뀐다" 리포트, 2026-08-28) 눈으로 검증할 방법이
1398
+ # 아예 없었다. resolve_slug()가 실제로 고른 값을 그대로 찍어서 다음엔
1399
+ # 여기만 봐도 바로 확인되게 한다.
1400
+ debug = (f"llm={llm} | vlm={vlm} | vision={dbg_v} | plan={dbg_p} | char={final_name} | "
1401
+ f"{_fmt_sec(dur)}s / {len(beats)} beats | "
1402
+ f"{'no-cut' if no_cut else 'cuts allowed'} | "
1403
+ f"audio={'on' if include_audio_fields else 'off'} | "
1404
+ f"i2v_prefix={'on' if i2v_prefix_applied else 'off'} | {len(prompt)} chars")
1405
+ _pt = _price_tag(llm, self._usage_acc, key)
1406
+ if _pt:
1407
+ debug += f" | {_pt}"
1408
+
1409
+ print(f"\n🎬🅜 [Dolphin Minimax Director] {debug}")
1410
+ for b in beats:
1411
+ dl = b.get("dialogue_line", "")
1412
+ dm = b.get("dialogue_manner", "")
1413
+ dlg = f" 💬{dm + ' ' if dm else ''}{dl}" if dl else ""
1414
+ print(f" {_fmt_sec(b['start'])}-{_fmt_sec(b['end'])}s {b['action'][:80]} {b['camera']}{dlg}")
1415
+ if camera_overall:
1416
+ print(f" 📷 {camera_overall}")
1417
+ if soundscape:
1418
+ print(f" 🔊 {soundscape}")
1419
+ if music:
1420
+ print(f" 🎵 {music}")
1421
+
1422
+ return (prompt, timeline_json, debug, final_name, soundscape, music)
1423
+
1424
+
1425
+ NODE_CLASS_MAPPINGS = {
1426
+ "DolphinMinimaxDirector": DolphinMinimaxDirector,
1427
+ }
1428
+ NODE_DISPLAY_NAME_MAPPINGS = {
1429
+ "DolphinMinimaxDirector": "🎬🅜 Dolphin Minimax Director (second-by-second timeline → Hailuo prompt)",
1430
+ }
dolphin_prompt_director.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ DolphinVisionDirector와 완전히 동일한 비전+LLM 프롬프트 엔진을 쓰되,
4
+ MODEL 입력/LoRA 적용/트리거워드 주입을 전부 뺀 "프롬프트 분배 전용" 버전.
5
+
6
+ 로라/모델 로딩은 별도 노드(Architect V16 등)에 맡기고, 이 노드는
7
+ 이미지+필수동작 -> N개 클립 프롬프트 문자열만 뽑는 역할에 집중한다.
8
+
9
+ 엔진 로직(비전 분석/샷 플래닝/로컬 폴백/프롬프트 조립)은 DolphinVisionDirector의
10
+ 메서드를 그대로 재사용한다(중복 구현하면 프롬프트 정책이 두 파일에서 따로
11
+ 놀게 되므로, 여기서는 새로 만들지 않고 위임한다). 그 메서드들은 로라/모델
12
+ 상태를 전혀 건드리지 않는 순수 로직이라 안전하게 재사용 가능.
13
+ """
14
+ from .dolphin_vision_director import (
15
+ DolphinVisionDirector,
16
+ resolve_slug,
17
+ resolve_api_key,
18
+ _price_tag,
19
+ tensor_to_base64,
20
+ VLM_PRESETS,
21
+ VLM_PRESET_LABELS,
22
+ LLM_PRESETS,
23
+ LLM_PRESET_LABELS,
24
+ )
25
+
26
+
27
+ class DolphinPromptDirector:
28
+ """
29
+ 이미지 하나와 '필수 동작'만 넣으면 N개 연속 클립의 프롬프트 문자열이
30
+ 완성되는 노드. DolphinVisionDirector에서 MODEL/LoRA 관련 부분을 전부
31
+ 뺀 버전 — 모델/로라는 별도 노드(Architect V16 등)에서 처리한다는 전제.
32
+
33
+ 1) IMAGE -> 비전 VLM(또는 vision_caption 입력)이 앵커(자세/지지물/환경) 파악
34
+ 2) 그 맥락 위에서 essential_actions를 N샷으로 분배 (LLM, 실패 시 로컬 폴백)
35
+ 3) 클립별 최종 프롬프트 문자열 조립 (로라 트리거 없음)
36
+ """
37
+
38
+ @classmethod
39
+ def INPUT_TYPES(s):
40
+ req = {
41
+ "image": ("IMAGE",),
42
+ "essential_actions": ("STRING", {"multiline": True,
43
+ "default": ("charges forward, swings a sword to strike down the enemy, deflects "
44
+ "a bullet, finishes them off")}),
45
+ "num_clips": (["4 (20s)", "3 (15s)", "2 (10s)"], {"default": "4 (20s)",
46
+ "tooltip": "Number of clips actually used. Remaining clip slots repeat the last "
47
+ "clip's content."}),
48
+ "character_name": ("STRING", {"default": "AUTO"}),
49
+ "artistic_vibe": ("STRING", {"multiline": True,
50
+ "default": "cinematic lighting, dynamic motion"}),
51
+ "pacing": (["auto", "slow-burn (1 beat)", "balanced (multi beat)", "rapid (4 distinct)"],
52
+ {"default": "auto"}),
53
+ "detail_level": (["Short (concise)", "Essentials + anchors only", "Detailed"],
54
+ {"default": "Essentials + anchors only"}),
55
+ "use_vision": ("BOOLEAN", {"default": True,
56
+ "label_on": "VISION ON", "label_off": "VISION OFF (text only)"}),
57
+ "openrouter_api_key": ("STRING", {"default": ""}),
58
+ "vlm_preset": (VLM_PRESET_LABELS, {"default": "👁 Qwen2.5-VL 72B (special cases · recommended)"}),
59
+ "vlm_model": ("STRING", {"default": "qwen/qwen2.5-vl-72b-instruct"}),
60
+ "llm_preset": (LLM_PRESET_LABELS, {"default": "🔞 DeepSeek V3.2 (uncensored · stable JSON · recommended)"}),
61
+ "llm_model": ("STRING", {"default": "deepseek/deepseek-v3.2"}),
62
+ "creativity": ("FLOAT", {"default": 0.7, "min": 0.1, "max": 1.5, "step": 0.05}),
63
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
64
+ }
65
+ opt = {
66
+ # 로컬 캡션 노드(JoyCaption 등)의 STRING 출력을 연결하면 그걸 앵커로 쓰고
67
+ # OpenRouter VLM 호출은 건너뜀. 비워두면 OpenRouter VLM 경로로 폴백.
68
+ "vision_caption": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
69
+ }
70
+ return {"required": req, "optional": opt}
71
+
72
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
73
+ RETURN_NAMES = ("c1_prompt", "c2_prompt", "c3_prompt", "c4_prompt")
74
+ FUNCTION = "direct"
75
+ CATEGORY = "Dolphin"
76
+
77
+ def direct(self, image, essential_actions, num_clips, character_name, artistic_vibe,
78
+ pacing, detail_level, use_vision, openrouter_api_key, vlm_preset, vlm_model,
79
+ llm_preset, llm_model, creativity, seed, **kw):
80
+
81
+ # 비전/플래닝 엔진은 DolphinVisionDirector 것을 그대로 위임한다 (로직 중복 방지).
82
+ # 이 인스턴스로는 _vision_analyze/_plan_shots/_local_split/_build_final 같은
83
+ # 순수 메서드만 호출하고, MODEL/LoRA를 만지는 _patch_clip 등은 아예 안 씀.
84
+ engine = DolphinVisionDirector()
85
+ engine._usage_acc = {"prompt": 0, "completion": 0, "cost": 0.0}
86
+
87
+ n_clips = int(str(num_clips).split()[0])
88
+ vlm = resolve_slug(VLM_PRESETS, vlm_preset, vlm_model, "qwen/qwen2.5-vl-72b-instruct")
89
+ llm = resolve_slug(LLM_PRESETS, llm_preset, llm_model, "deepseek/deepseek-v3.2")
90
+ user_name = "" if character_name.strip().upper() in ["AUTO", ""] else character_name.strip()
91
+ key = resolve_api_key(openrouter_api_key)
92
+
93
+ # ---- STEP 1: 비전 ----
94
+ vision_caption = str(kw.get("vision_caption", "") or "").strip()
95
+ anchors, dbg_v = "", ""
96
+ if vision_caption:
97
+ anchors = vision_caption
98
+ dbg_v = "vision ok (local caption node)"
99
+ print(f"👁 [PromptDirector] Using local caption input (JoyCaption etc.):\n{anchors[:300]}")
100
+ elif not use_vision:
101
+ dbg_v = "vision OFF (check toggle)"
102
+ print("ℹ️ [PromptDirector] use_vision=OFF - skipping vision")
103
+ elif not key:
104
+ dbg_v = "no api key"
105
+ print("❌ [PromptDirector] openrouter_api_key missing (neither widget nor OPENROUTER_API_KEY env var set) - vision/LLM unavailable")
106
+ elif image is None:
107
+ dbg_v = "no image"
108
+ print("❌ [PromptDirector] image input missing - check the IMAGE connection")
109
+ else:
110
+ data_uri = tensor_to_base64(image)
111
+ if data_uri:
112
+ anchors, err = engine._vision_analyze(data_uri, vlm, key, min(creativity, 0.5))
113
+ dbg_v = err or "vision ok"
114
+ else:
115
+ dbg_v = "image encode failed / PIL missing"
116
+ if not vision_caption and use_vision and key and image is not None and not anchors:
117
+ print(f"⚠️ [PromptDirector] Vision anchors are empty -> no basis for name/scene. Cause: {dbg_v}")
118
+
119
+ # ---- STEP 2: N샷 분배 ----
120
+ if key:
121
+ parsed, dbg_p = engine._plan_shots(anchors, essential_actions, character_name,
122
+ artistic_vibe, pacing, engine._detail_hint(detail_level),
123
+ llm, key, creativity, seed, num_clips=n_clips)
124
+ if parsed:
125
+ clips = [str(parsed[f"clip_{i}"]).strip() for i in range(1, n_clips + 1)]
126
+ master = str(parsed.get("master_scene", "")).strip()
127
+ cand = str(parsed.get("character", "")).strip()
128
+ if not cand or cand.upper() == "AUTO":
129
+ cand = engine._fallback_name(anchors)
130
+ final_name = user_name or cand
131
+ else:
132
+ clips = engine._local_split(essential_actions, num_clips=n_clips)
133
+ master = anchors.replace("\n", " ")[:160]
134
+ final_name = user_name or engine._fallback_name(anchors)
135
+ else:
136
+ parsed, dbg_p = None, "no api key"
137
+ clips = engine._local_split(essential_actions, num_clips=n_clips)
138
+ master = anchors.replace("\n", " ")[:160]
139
+ final_name = user_name
140
+
141
+ if len(clips) < 4:
142
+ clips = clips + [clips[-1]] * (4 - len(clips))
143
+
144
+ # ---- STEP 3: 프롬프트 조립 (로라/트리거 없음) ----
145
+ outs = []
146
+ for c in range(1, 5):
147
+ if c > n_clips:
148
+ prompt = engine._build_final(clips[c - 1], master, final_name, "")
149
+ outs.append(prompt)
150
+ print(f" [clip{c}] SKIPPED (num_clips={n_clips}) - text only, ignored downstream")
151
+ continue
152
+ prompt = engine._build_final(clips[c - 1], master, final_name, "")
153
+ outs.append(prompt)
154
+
155
+ debug = f"vlm={dbg_v} | plan={dbg_p} | char={final_name} | clips={n_clips} ({n_clips*5}s)"
156
+ _pt = _price_tag(llm, getattr(engine, "_usage_acc", None), key)
157
+ if _pt:
158
+ debug += f" | {_pt}"
159
+ print(f"\n🎬 [Dolphin Prompt Director] {debug}\nANCHORS: {anchors[:120]}")
160
+ for i, p in enumerate(outs, 1):
161
+ print(f" CLIP {i}: {p[:90]}")
162
+
163
+ return tuple(outs)
164
+
165
+
166
+ class DolphinActionSplitter:
167
+ """
168
+ essential_actions 텍스트 하나를 클립별 액션 조각으로 나누기만 하는 노드.
169
+ LLM/비전 호출이 전혀 없는 순수 로컬 로직(정규식 기반)이라 비용이 0에 가깝고,
170
+ 시드나 렌더링 개수와 무관하게 항상 같은 입력에 같은 출력을 낸다.
171
+
172
+ DolphinVisionDirector처럼 "몇 클립 낼지"와 "플래닝"이 한 노드에 묶여있으면,
173
+ 렌더링 개수만 바꿔도(예: 2클립->4클립 연장) 전체 재플래닝이 발생해서 이미 만든
174
+ 클립까지 캐시가 깨진다. 이 노드는 그 분배 단계만 따로 떼어내서 - total_clips를
175
+ 항상 고정해두면(보통 4) 이 노드의 출력은 절대 안 바뀌고, 그 뒤에 물리는 클립별
176
+ DolphinPromptDirectorSingle들도 자기 몫 액션 조각이 그대로면 캐시가 유지된다.
177
+ """
178
+
179
+ @classmethod
180
+ def INPUT_TYPES(s):
181
+ req = {
182
+ "essential_actions": ("STRING", {"multiline": True,
183
+ "default": ("charges forward, swings a sword to strike down the enemy, deflects "
184
+ "a bullet, finishes them off")}),
185
+ "total_clips": (["4", "3", "2"], {"default": "4",
186
+ "tooltip": "How many beats to split into. If you later increase the render count "
187
+ "without changing this value, the action chunks of clips you've "
188
+ "already made stay the same."}),
189
+ }
190
+ return {"required": req}
191
+
192
+ RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
193
+ RETURN_NAMES = ("c1_action", "c2_action", "c3_action", "c4_action")
194
+ FUNCTION = "split"
195
+ CATEGORY = "Dolphin"
196
+
197
+ def split(self, essential_actions, total_clips):
198
+ engine = DolphinVisionDirector()
199
+ n = int(total_clips)
200
+ assign = engine._split_actions_to_clips(essential_actions, n)
201
+ outs = [", ".join(assign[c]) for c in range(1, n + 1)]
202
+ if len(outs) < 4:
203
+ outs = outs + [""] * (4 - len(outs))
204
+ print(f"✂️ [ActionSplitter] {n} clips: " + " | ".join(f"c{i+1}={o[:40]}" for i, o in enumerate(outs) if o))
205
+ return tuple(outs)
206
+
207
+
208
+ class DolphinPromptDirectorSingle:
209
+ """
210
+ DolphinPromptDirector의 단일 프롬프트 버전. num_clips 개념 없이
211
+ 이미지 하나 + '동작' 하나로 프롬프트 문자열 1개만 뽑는다.
212
+
213
+ 5초 단발 테스트(로라 테스트 워크플로우 등)처럼 클립 분할/연속성 로직이
214
+ 전혀 필요 없는 경우를 위한 것 - 엔진 내부적으로 num_clips=1로 호출하면
215
+ _plan_shots/_local_split가 이미 단일 클립 케이스를 지원하므로 그대로 위임.
216
+ """
217
+
218
+ @classmethod
219
+ def INPUT_TYPES(s):
220
+ req = {
221
+ "image": ("IMAGE",),
222
+ "action": ("STRING", {"multiline": True,
223
+ "default": "charges forward, swings a sword to strike down the enemy"}),
224
+ "character_name": ("STRING", {"default": "AUTO"}),
225
+ "artistic_vibe": ("STRING", {"multiline": True,
226
+ "default": "cinematic lighting, dynamic motion"}),
227
+ "detail_level": (["Short (concise)", "Essentials + anchors only", "Detailed"],
228
+ {"default": "Essentials + anchors only"}),
229
+ "use_vision": ("BOOLEAN", {"default": True,
230
+ "label_on": "VISION ON", "label_off": "VISION OFF (text only)"}),
231
+ "openrouter_api_key": ("STRING", {"default": ""}),
232
+ "vlm_preset": (VLM_PRESET_LABELS, {"default": "👁 Qwen2.5-VL 72B (special cases · recommended)"}),
233
+ "vlm_model": ("STRING", {"default": "qwen/qwen2.5-vl-72b-instruct"}),
234
+ "llm_preset": (LLM_PRESET_LABELS, {"default": "🔞 DeepSeek V3.2 (uncensored · stable JSON · recommended)"}),
235
+ "llm_model": ("STRING", {"default": "deepseek/deepseek-v3.2"}),
236
+ "creativity": ("FLOAT", {"default": 0.7, "min": 0.1, "max": 1.5, "step": 0.05}),
237
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "forceInput": True}),
238
+ }
239
+ opt = {
240
+ "vision_caption": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
241
+ # 이전 클립이 만든 prompt(또는 그 액션 부분)를 연결하면, 이번 클립이 그
242
+ # 동작이 끝난 지점부터 자연스럽게 이어지도록 LLM에 문맥으로 넘긴다.
243
+ # 비워두면(체인의 첫 클립) 그냥 단독 클립처럼 취급.
244
+ "previous_action": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
245
+ # DolphinTripleLoraMatrix 등 로라 패처의 trigger_words 출력을 연결하면
246
+ # 최종 프롬프트 상단(이름 옆)에 붙는다. DolphinVisionDirector의
247
+ # inject_triggers 기능과 동일한 자리.
248
+ "trigger_words": ("STRING", {"default": "", "forceInput": True}),
249
+ }
250
+ return {"required": req, "optional": opt}
251
+
252
+ RETURN_TYPES = ("STRING", "STRING", "STRING")
253
+ RETURN_NAMES = ("prompt", "debug_info", "resolved_character_name")
254
+ FUNCTION = "direct"
255
+ CATEGORY = "Dolphin"
256
+
257
+ def direct(self, image, action, character_name, artistic_vibe, detail_level, use_vision,
258
+ openrouter_api_key, vlm_preset, vlm_model, llm_preset, llm_model, creativity, seed, **kw):
259
+
260
+ engine = DolphinVisionDirector()
261
+ engine._usage_acc = {"prompt": 0, "completion": 0, "cost": 0.0}
262
+ previous_action = str(kw.get("previous_action", "") or "").strip()
263
+ trigger_words = str(kw.get("trigger_words", "") or "").strip()
264
+ vlm = resolve_slug(VLM_PRESETS, vlm_preset, vlm_model, "qwen/qwen2.5-vl-72b-instruct")
265
+ llm = resolve_slug(LLM_PRESETS, llm_preset, llm_model, "deepseek/deepseek-v3.2")
266
+ user_name = "" if character_name.strip().upper() in ["AUTO", ""] else character_name.strip()
267
+ key = resolve_api_key(openrouter_api_key)
268
+
269
+ # ---- STEP 1: 비전 ----
270
+ vision_caption = str(kw.get("vision_caption", "") or "").strip()
271
+ anchors, dbg_v = "", ""
272
+ if vision_caption:
273
+ anchors = vision_caption
274
+ dbg_v = "vision ok (local caption node)"
275
+ elif not use_vision:
276
+ dbg_v = "vision OFF (check toggle)"
277
+ elif not key:
278
+ dbg_v = "no api key"
279
+ elif image is None:
280
+ dbg_v = "no image"
281
+ else:
282
+ data_uri = tensor_to_base64(image)
283
+ if data_uri:
284
+ anchors, err = engine._vision_analyze(data_uri, vlm, key, min(creativity, 0.5))
285
+ dbg_v = err or "vision ok"
286
+ else:
287
+ dbg_v = "image encode failed / PIL missing"
288
+
289
+ # ---- STEP 2: 단일 샷 플래닝 ----
290
+ if key:
291
+ parsed, dbg_p = engine._plan_shots(anchors, action, character_name, artistic_vibe,
292
+ "auto", engine._detail_hint(detail_level),
293
+ llm, key, creativity, seed, num_clips=1,
294
+ continuity_context=previous_action)
295
+ if parsed:
296
+ clip = str(parsed["clip_1"]).strip()
297
+ master = str(parsed.get("master_scene", "")).strip()
298
+ cand = str(parsed.get("character", "")).strip()
299
+ if not cand or cand.upper() == "AUTO":
300
+ cand = engine._fallback_name(anchors)
301
+ final_name = user_name or cand
302
+ else:
303
+ clip = engine._local_split(action, num_clips=1)[0]
304
+ master = ""
305
+ final_name = user_name or engine._fallback_name(anchors)
306
+ else:
307
+ parsed, dbg_p = None, "no api key"
308
+ clip = engine._local_split(action, num_clips=1)[0]
309
+ master = ""
310
+ final_name = user_name
311
+
312
+ prompt = engine._build_final(clip, master, final_name, trigger_words)
313
+ debug = f"vlm={dbg_v} | plan={dbg_p} | char={final_name}"
314
+ _pt = _price_tag(llm, getattr(engine, "_usage_acc", None), key)
315
+ if _pt:
316
+ debug += f" | {_pt}"
317
+ print(f"\n🎬 [Dolphin Prompt Director Single] {debug}\nPROMPT: {prompt[:200]}")
318
+
319
+ return (prompt, debug, final_name)
320
+
321
+
322
+ NODE_CLASS_MAPPINGS = {
323
+ "DolphinPromptDirector": DolphinPromptDirector,
324
+ "DolphinPromptDirectorSingle": DolphinPromptDirectorSingle,
325
+ "DolphinActionSplitter": DolphinActionSplitter,
326
+ }
327
+ NODE_DISPLAY_NAME_MAPPINGS = {
328
+ "DolphinPromptDirector": "🎬📝 Dolphin Prompt Director (Image→N Prompts, no model/LoRA)",
329
+ "DolphinPromptDirectorSingle": "🎬📝 Dolphin Prompt Director (Single, Image→1 Prompt)",
330
+ "DolphinActionSplitter": "✂️ Dolphin Action Splitter (deterministic, no LLM)",
331
+ }
dolphin_seed_controller.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class DolphinBatchSeedController:
2
+ """
3
+ 클립 4개짜리 파이프라인(First + Extend×3)의 영상 샘플러 시드를 한 곳에서
4
+ 스위칭하기 위한 노드. 기존에는 클립마다 서로 다른 서브그래프 안에 묻힌
5
+ SeedGenerator 위젯을 4번 따로 조작해야 "매 배치 랜덤"과 "특정 클립부터
6
+ 재생성"을 오갈 수 있었다.
7
+
8
+ "seed"라는 이름의 INT 위젯에는 ComfyUI 프론트엔드가 자동으로
9
+ control_after_generate(fixed/increment/decrement/randomize) 드롭다운을
10
+ 붙여준다 - 그래서 "전체 랜덤"/"특정 클립부터 재생성"에 필요한 새 시드는
11
+ 이 위젯 하나의 randomize/increment만으로 나오고, 노드 자체는 매 실행
12
+ 무작위를 호출할 필요 없는 순수 함수로 남는다.
13
+
14
+ 주의: "특정 클립부터 재생성"이지 "그 클립만 재생성"이 아니다. Extend
15
+ 서브그래프는 이전 클립의 실제 디코딩된 프레임을 입력으로 받는 continuity
16
+ 구조라, N번 클립 시드를 바꾸면 N+1, N+2... 클립은 자기 시드값이 그대로여도
17
+ 입력 latent가 달라져서 캐시가 깨지고 무조건 다시 렌더링된다. N보다 앞
18
+ 클립들만 그대로 캐시된다.
19
+ """
20
+
21
+ MODE_FIXED = "🔒 고정 (재현)"
22
+ MODE_RANDOM_ALL = "🎲 전체 랜덤 (매 배치)"
23
+ MODE_REGEN_FROM = "♻️ 특정 클립부터 재생성 (그 뒤 클립도 연쇄 재생성됨)"
24
+
25
+ GOLDEN = 0x9E3779B97F4A7C15
26
+ MASK = 0xFFFFFFFFFFFFFFFF
27
+
28
+ @classmethod
29
+ def INPUT_TYPES(s):
30
+ req = {
31
+ "mode": ([s.MODE_FIXED, s.MODE_RANDOM_ALL, s.MODE_REGEN_FROM], {"default": s.MODE_FIXED}),
32
+ "regen_clip": (["1", "2", "3", "4"], {"default": "1",
33
+ "tooltip": "'특정 클립부터 재생성' 모드에서 몇 번 클립부터 새 시드를 줄지. "
34
+ "그 클립 이후 클립들도 continuity 때문에 같이 다시 렌더링된다 "
35
+ "(이전 클립들만 캐시 유지)."}),
36
+ "seed": ("INT", {"default": 0, "min": 0, "max": s.MASK}),
37
+ "c1_seed": ("INT", {"default": 196010661098274, "min": 0, "max": s.MASK}),
38
+ "c2_seed": ("INT", {"default": 316514109857979, "min": 0, "max": s.MASK}),
39
+ "c3_seed": ("INT", {"default": 732840722629067, "min": 0, "max": s.MASK}),
40
+ "c4_seed": ("INT", {"default": 599880917933127, "min": 0, "max": s.MASK}),
41
+ }
42
+ return {"required": req}
43
+
44
+ RETURN_TYPES = ("INT", "INT", "INT", "INT", "STRING")
45
+ RETURN_NAMES = ("c1_seed", "c2_seed", "c3_seed", "c4_seed", "debug_info")
46
+ FUNCTION = "get_seeds"
47
+ CATEGORY = "Dolphin"
48
+
49
+ def get_seeds(self, mode, regen_clip, seed, c1_seed, c2_seed, c3_seed, c4_seed):
50
+ base = [c1_seed, c2_seed, c3_seed, c4_seed]
51
+
52
+ if mode == self.MODE_RANDOM_ALL:
53
+ out = [(seed + i * self.GOLDEN) & self.MASK for i in range(4)]
54
+ elif mode == self.MODE_REGEN_FROM:
55
+ out = list(base)
56
+ out[int(regen_clip) - 1] = seed
57
+ else:
58
+ out = list(base)
59
+
60
+ debug = f"mode={mode} | c1={out[0]} c2={out[1]} c3={out[2]} c4={out[3]}"
61
+ print(f"🎲 [BatchSeedController] {debug}")
62
+ return (*out, debug)
63
+
64
+
65
+ NODE_CLASS_MAPPINGS = {
66
+ "DolphinBatchSeedController": DolphinBatchSeedController,
67
+ }
68
+ NODE_DISPLAY_NAME_MAPPINGS = {
69
+ "DolphinBatchSeedController": "🎲 Dolphin Batch Seed Controller (random-all / regen-from-clip)",
70
+ }
dolphin_vision_director.py ADDED
@@ -0,0 +1,1028 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ import re
4
+ import io
5
+ import json
6
+ import base64
7
+ import hashlib
8
+ import sqlite3
9
+ import urllib.request
10
+ import urllib.error
11
+
12
+ import numpy as np
13
+ import folder_paths
14
+ import comfy.sd
15
+ import comfy.lora
16
+ import comfy.utils
17
+ from safetensors import safe_open
18
+ from comfy_execution.graph_utils import ExecutionBlocker
19
+
20
+ try:
21
+ from PIL import Image
22
+ _HAS_PIL = True
23
+ except Exception:
24
+ _HAS_PIL = False
25
+
26
+ CACHE_FILE = os.path.join(os.path.dirname(__file__), "dolphin_tags_cache.json")
27
+
28
+
29
+ # ======================================================================
30
+ # 모델 프리셋 (텍스트 LLM = 4샷 분배 / 비전 VLM = 이미지 인식)
31
+ # ======================================================================
32
+ LLM_PRESETS = {
33
+ "🔞 DeepSeek V3.2 (uncensored · stable JSON · recommended)": "deepseek/deepseek-v3.2",
34
+ "🔞 Magnum v4 72B (natural phrasing)": "anthracite-org/magnum-v4-72b",
35
+ "🔞 Cydonia 24B v4.1 (lightweight · fast)": "thedrummer/cydonia-24b-v4.1",
36
+ # 2026-09-03: :free 슬러그가 404 됨("unavailable for free, use paid slug"). 유료로 교체.
37
+ "🔞 Dolphin Mistral Venice (uncensored)": "cognitivecomputations/dolphin-mistral-24b-venice-edition",
38
+ # 2026-09-03 추가. OpenRouter 모델 페이지로 슬러그/서빙 여부만 확인했고
39
+ # 실제 chat completions 호출로는 아직 스모크테스트 안 함 - 처음 쓸 때 확인할 것.
40
+ "🔞 Hermes 4 405B (steerable · strict format)": "nousresearch/hermes-4-405b",
41
+ # 2026-09-03: grok-4 / grok-4-fast 둘 다 deprecated(404) - grok-4.3으로 교체.
42
+ "🔞 Grok 4.3 (edgy prose · $1.25/$2.5)": "x-ai/grok-4.3",
43
+ "🔞 Euryale 70B L3.3 (sensory prose · loose format)": "sao10k/l3.3-euryale-70b",
44
+ "⚙️ custom (use text field below)": "__custom__",
45
+ }
46
+ # 2026-08-28: Valkyrie 49B V1(thedrummer/valkyrie-49b-v1)을 여기 넣었다가 뺐다 -
47
+ # OpenRouter 마케팅 페이지는 정상으로 보였지만 실제 chat completions 호출은
48
+ # 404 "No endpoints found"(프로바이더가 하나도 없음). 페이지가 살아있다고 실제
49
+ # 서빙 중이란 보장이 없다 - 프리셋에 새 모델을 넣기 전엔 반드시 실제 API 호출로
50
+ # 확인할 것(스크레이핑만으로는 부족했음, 실측: debug_info의 plan= 필드에 뜬
51
+ # HTTP 404가 증거).
52
+ LLM_PRESET_LABELS = list(LLM_PRESETS.keys())
53
+
54
+ # 비전 모델: 특수 상황 이해 + 다국어. Qwen2.5-VL 계열이 포즈/맥락 파악에 강함.
55
+ VLM_PRESETS = {
56
+ "👁 Qwen2.5-VL 72B (special cases · recommended)": "qwen/qwen2.5-vl-72b-instruct",
57
+ "👁 Qwen2.5-VL 32B (balanced)": "qwen/qwen2.5-vl-32b-instruct",
58
+ "👁 Qwen2.5-VL 32B (free)": "qwen/qwen2.5-vl-32b-instruct:free",
59
+ "👁 Mistral Small 3.1 24B (Pixtral)": "mistralai/mistral-small-3.1-24b-instruct",
60
+ "👁 Gemma 3 27B (lightweight)": "google/gemma-3-27b-it",
61
+ "⚙️ custom (use text field below)": "__custom__",
62
+ }
63
+ VLM_PRESET_LABELS = list(VLM_PRESETS.keys())
64
+
65
+
66
+ def resolve_slug(table, label, custom_text, default):
67
+ slug = table.get(label, "__custom__")
68
+ if slug == "__custom__":
69
+ return custom_text.strip() or default
70
+ return slug
71
+
72
+
73
+ # ----------------------------------------------------------------------
74
+ # OpenRouter 토큰 단가 조회 (debug_info 표시용)
75
+ # /api/v1/models 는 무인증 공개 엔드포인트. 프로세스 살아있는 동안 1회만 받는다.
76
+ # pricing.prompt/completion 은 "토큰 1개당 USD" 문자열이라 1e6 곱해 1M당 단가로.
77
+ # ----------------------------------------------------------------------
78
+ _OR_PRICE_CACHE = {}
79
+
80
+
81
+ def _or_model_prices(key=None):
82
+ if _OR_PRICE_CACHE:
83
+ return _OR_PRICE_CACHE
84
+ try:
85
+ hdr = {'Authorization': f'Bearer {key}'} if key else {}
86
+ req = urllib.request.Request("https://openrouter.ai/api/v1/models", headers=hdr)
87
+ raw = json.loads(urllib.request.urlopen(req, timeout=15).read().decode('utf-8'))
88
+ for m in raw.get("data", []):
89
+ p = m.get("pricing") or {}
90
+ try:
91
+ _OR_PRICE_CACHE[m["id"]] = (float(p.get("prompt", 0) or 0) * 1e6,
92
+ float(p.get("completion", 0) or 0) * 1e6)
93
+ except (TypeError, ValueError, KeyError):
94
+ pass
95
+ except Exception as e:
96
+ print(f"⚠️ [Director] OpenRouter 단가표 조회 실패(무시): {e}")
97
+ return _OR_PRICE_CACHE
98
+
99
+
100
+ def _price_tag(slug, usage=None, key=None):
101
+ """'slug $0.28/$0.88 per 1M | spent ~$0.0031 (1450→820 tok)' 형태."""
102
+ parts = []
103
+ pc = _or_model_prices(key).get((slug or "").strip())
104
+ if pc:
105
+ parts.append(f"{slug} ${pc[0]:.3g}/${pc[1]:.3g} per 1M")
106
+ if usage:
107
+ pt, ct, cost = usage.get("prompt", 0), usage.get("completion", 0), usage.get("cost", 0.0)
108
+ if pt or ct or cost:
109
+ parts.append(f"spent ~${cost:.4f} ({pt}→{ct} tok)")
110
+ return " | ".join(parts)
111
+
112
+
113
+ def resolve_api_key(widget_value):
114
+ """위젯에 키�� 있으면 그대로 사용. 비어있으면 OPENROUTER_API_KEY 환경변수로 폴백.
115
+ -> 워크플로우 JSON(공유/백업 시 유출 위험)에 실키를 박아둘 필요가 없어짐."""
116
+ key = (widget_value or "").strip()
117
+ if key:
118
+ return key
119
+ return os.environ.get("OPENROUTER_API_KEY", "").strip()
120
+
121
+
122
+ # ======================================================================
123
+ # 캐시 & 트리거 (검증된 로직 재사용)
124
+ # ======================================================================
125
+ def _load_cache():
126
+ if os.path.exists(CACHE_FILE):
127
+ try:
128
+ with open(CACHE_FILE, "r", encoding="utf-8") as f:
129
+ return json.load(f)
130
+ except (json.JSONDecodeError, OSError):
131
+ return {}
132
+ return {}
133
+
134
+
135
+ def _save_cache(cache):
136
+ try:
137
+ with open(CACHE_FILE, "w", encoding="utf-8") as f:
138
+ json.dump(cache, f, indent=4, ensure_ascii=False)
139
+ except OSError:
140
+ pass
141
+
142
+
143
+ def _file_sig(path):
144
+ st = os.stat(path)
145
+ return f"{os.path.basename(path)}::{st.st_size}::{int(st.st_mtime)}"
146
+
147
+
148
+ def _sha256(path):
149
+ h = hashlib.sha256()
150
+ with open(path, "rb") as f:
151
+ for block in iter(lambda: f.read(4096 * 1024), b""):
152
+ h.update(block)
153
+ return h.hexdigest()
154
+
155
+
156
+ # CivitAI 및 미러(civitaired) 해시 조회 엔드포인트. 앞에서부터 순서대로 시도.
157
+ TRIGGER_HASH_APIS = [
158
+ "https://civitai.com/api/v1/model-versions/by-hash/{h}",
159
+ "https://civitaired.com/api/v1/model-versions/by-hash/{h}",
160
+ ]
161
+
162
+ # LoRA Manager(comfyui-lora-manager)가 스캔 시 만들어두는 로컬 SQLite 캐시.
163
+ # sha256으로 trained_words를 즉시 조회 가능 -> 네트워크 호출 없이 트리거워드 확보.
164
+ LORA_MANAGER_DB = os.path.join(
165
+ os.environ.get("LOCALAPPDATA", ""), "ComfyUI-LoRA-Manager", "cache", "model", "comfyui.sqlite")
166
+
167
+
168
+ def _query_lora_manager_db(digest):
169
+ """LoRA Manager SQLite 캐시에서 sha256으로 trained_words 조회. 실패/미발견 시 None."""
170
+ if not digest or not os.path.exists(LORA_MANAGER_DB):
171
+ return None
172
+ try:
173
+ uri = f"file:{LORA_MANAGER_DB}?mode=ro"
174
+ con = sqlite3.connect(uri, uri=True, timeout=3)
175
+ try:
176
+ cur = con.cursor()
177
+ cur.execute(
178
+ "SELECT trained_words FROM models WHERE model_type='lora' AND sha256=? LIMIT 1",
179
+ (digest,))
180
+ row = cur.fetchone()
181
+ finally:
182
+ con.close()
183
+ if not row or not row[0]:
184
+ return None
185
+ tw = json.loads(row[0])
186
+ return tw if isinstance(tw, list) else None
187
+ except (sqlite3.Error, OSError, ValueError, json.JSONDecodeError) as e:
188
+ print(f"⚠️ [Director] LoRA Manager DB lookup failed: {type(e).__name__}: {e}")
189
+ return None
190
+
191
+
192
+ def _query_trigger_api(url):
193
+ """단일 해시 조회 엔드포인트에서 trainedWords 리스트를 반환. 실패 시 ([], 사유)."""
194
+ try:
195
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
196
+ with urllib.request.urlopen(req, timeout=8) as r:
197
+ data = json.loads(r.read().decode('utf-8'))
198
+ return (data.get("trainedWords", []) or []), None
199
+ except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError,
200
+ OSError, ValueError, json.JSONDecodeError) as e:
201
+ return [], f"{type(e).__name__}: {e}"
202
+
203
+
204
+ # 트리거 워드가 "penis, bounce, swing"처럼 캐릭터 이름 옆에 콤마로 그냥 나열되면
205
+ # (_build_final 참고) 어느 신체 부위에 국한된 디테일인지 모델이 알 방법이 없어서,
206
+ # bounce/swing 같은 범용 동작 동사가 전신 모션으로 새버리는 게 실측으로 확인됨
207
+ # (예: "penis, bounce, swing"의 flaccid LoRA 트리거 때문에 캐릭터가 무슨 동작을
208
+ # 하든 콩콩 뛰었음). 문법적 문맥이 없는 "맨몸 단어"(공백 없는 한 단어)로만 등장하는
209
+ # 범용 동작 동사는 걸러낸다 - "penis becomes erect"처럼 이미 문장으로 묶여 들어온
210
+ # 여러 단어짜리 구는 건드리지 않음(이미 anchoring이 있어서 안전).
211
+ _GENERIC_MOTION_VERBS = {
212
+ "bounce", "bouncing", "swing", "swinging", "shake", "shaking",
213
+ "sway", "swaying", "wiggle", "wiggling", "jiggle", "jiggling",
214
+ "wobble", "wobbling", "jump", "jumping", "hop", "hopping",
215
+ "bob", "bobbing", "quiver", "quivering", "tremble", "trembling",
216
+ "spin", "spinning", "twitch", "twitching",
217
+ }
218
+
219
+
220
+ def _filter_generic_motion_verbs(words):
221
+ kept, dropped = [], []
222
+ for w in words:
223
+ if w.strip().lower() in _GENERIC_MOTION_VERBS:
224
+ dropped.append(w)
225
+ else:
226
+ kept.append(w)
227
+ if dropped:
228
+ print(f"🚫 [Director] Excluded generic motion-verb triggers: {dropped}")
229
+ return kept
230
+
231
+
232
+ def get_triggers(lora_name, cache):
233
+ return _filter_generic_motion_verbs(_get_triggers_raw(lora_name, cache))
234
+
235
+
236
+ def _get_triggers_raw(lora_name, cache):
237
+ if lora_name == "None":
238
+ return []
239
+ path = folder_paths.get_full_path("loras", lora_name)
240
+ if not path or not os.path.exists(path):
241
+ return []
242
+ try:
243
+ with safe_open(path, framework="pt", device="cpu") as f:
244
+ m = f.metadata()
245
+ if m and "modelspec.trigger_words" in m:
246
+ tw = json.loads(m["modelspec.trigger_words"])
247
+ if isinstance(tw, list):
248
+ return tw
249
+ if isinstance(tw, str):
250
+ return [tw]
251
+ except (OSError, ValueError, json.JSONDecodeError):
252
+ pass
253
+ try:
254
+ sig = _file_sig(path)
255
+ except OSError:
256
+ sig = lora_name
257
+ # 캐시에 '비어있지 않은' 결과가 있을 때만 신뢰. 과거에 실패로 저장된 빈 값은
258
+ # 무시하고 재조회한다 (빈 값 영구화 회귀 방지).
259
+ cached = cache.get(sig)
260
+ if cached:
261
+ return cached
262
+
263
+ # 해시는 1회만 계산해서 LoRA Manager DB -> CivitAI -> civitaired 순으로 조회.
264
+ result, base = [], os.path.basename(path)
265
+ try:
266
+ digest = _sha256(path)
267
+ except OSError as e:
268
+ print(f"⚠️ [Director] Hash computation failed ({base}): {e}")
269
+ digest = None
270
+ if digest:
271
+ found = _query_lora_manager_db(digest)
272
+ if found:
273
+ print(f"✅ [Director] Trigger lookup succeeded ({base}) @ LoRA Manager DB: {found}")
274
+ result = found
275
+ if digest and not result:
276
+ for tmpl in TRIGGER_HASH_APIS:
277
+ host = tmpl.split('/')[2]
278
+ found, err = _query_trigger_api(tmpl.format(h=digest))
279
+ if found:
280
+ print(f"✅ [Director] Trigger lookup succeeded ({base}) @ {host}: {found}")
281
+ result = found
282
+ break
283
+ print(f"⚠️ [Director] Trigger lookup failed ({base}) @ {host}: {err or 'no trainedWords'}")
284
+
285
+ # 성공(비어있지 않음)일 때만 캐싱. 빈 결과는 저장하지 않아 다음 실행에서 재시도됨.
286
+ if result:
287
+ cache[sig] = result
288
+ _save_cache(cache)
289
+ return result
290
+
291
+
292
+ def apply_model_lora(model, lora_data, weight):
293
+ try:
294
+ return comfy.sd.load_lora_for_models(model, None, lora_data, weight, 0)[0]
295
+ except (AttributeError, TypeError):
296
+ new_model = model.clone()
297
+ key_map = comfy.lora.model_lora_keys_unet(new_model.model)
298
+ loaded = comfy.lora.load_lora(lora_data, key_map)
299
+ new_model.add_patches(loaded, weight)
300
+ return new_model
301
+
302
+
303
+ def tensor_to_base64(image_tensor, max_side=1024):
304
+ """ComfyUI IMAGE 텐서(B,H,W,C, 0~1 float) 첫 프레임을 JPEG base64 data URI로."""
305
+ if not _HAS_PIL:
306
+ print("❌ [Director] PIL (Pillow) missing - vision unavailable. Run `pip install Pillow`.")
307
+ return None
308
+ if image_tensor is None:
309
+ print("❌ [Director] image input is None - check the IMAGE connection.")
310
+ return None
311
+ try:
312
+ img = image_tensor
313
+ if hasattr(img, "cpu"):
314
+ img = img.cpu().numpy()
315
+ img = np.asarray(img)
316
+ if img.ndim == 4:
317
+ img = img[0]
318
+ arr = np.clip(img * 255.0, 0, 255).astype(np.uint8)
319
+ pil = Image.fromarray(arr)
320
+ w, h = pil.size
321
+ if max(w, h) > max_side:
322
+ scale = max_side / float(max(w, h))
323
+ pil = pil.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
324
+ buf = io.BytesIO()
325
+ pil.convert("RGB").save(buf, format="JPEG", quality=90)
326
+ b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
327
+ print(f"🖼 [Director] image encoded ok: {w}x{h} -> {pil.size}, {len(b64)//1024}KB base64")
328
+ return f"data:image/jpeg;base64,{b64}"
329
+ except Exception as e:
330
+ print(f"❌ [Director] image encode failed: {type(e).__name__}: {e}")
331
+ return None
332
+
333
+
334
+ def split_action_parts(actions):
335
+ """essential_actions를 조각으로 나눈다.
336
+
337
+ '/'가 있으면 그건 사용자가 클립/비트 경계를 명시적으로 못 박은 것이므로
338
+ '/'만 경계로 쓰고 그 안의 쉼표는 한 덩어리로 유지한다 - 안 그러면 "동작A,
339
+ 디테일1, 디테일2 / 동작B, 디테일3" 처럼 '/'로 묶은 한 클립 분량이 쉼표
340
+ 때문에 다시 여러 조각으로 쪼개져서 클립 경계가 어긋난다(실제로 겪은 버그).
341
+ '/'가 없을 때만 기존처럼 쉼표/문장부호/줄바꿈으로 나눈다."""
342
+ if '/' in actions:
343
+ parts = actions.split('/')
344
+ else:
345
+ parts = re.split(r'(?<=[.!?。])\s+|[,、]|\n+', actions)
346
+ return [p.strip() for p in parts if len(p.strip()) > 1]
347
+
348
+
349
+ # ======================================================================
350
+ # Vision Director: 이미지 인식 + 필수동작 실행 + 클립당 4로라
351
+ # ======================================================================
352
+ class DolphinVisionDirector:
353
+ """
354
+ 이미지 하나와 '필수 동작'과 로라만 넣으면 4개 연속 클립이 완성되는 올인원 노드.
355
+
356
+ 1) IMAGE -> 비전 VLM 이 상황(캐릭터 상태/자세/환경)만 앵커로 파악
357
+ 2) 그 맥락 위에서 '내가 준 필수 동작(essential_actions)'만 4샷으로 분배
358
+ 3) 클립마다 로라 4개씩 개별 적용(웨이트도 개별) + 트리거 자동 주입
359
+ """
360
+
361
+ @classmethod
362
+ def INPUT_TYPES(s):
363
+ loras = ["None"] + (folder_paths.get_filename_list("loras") or [])
364
+ req = {
365
+ "image": ("IMAGE",),
366
+ "model_base": ("MODEL",),
367
+ "model_high": ("MODEL",),
368
+ "model_low": ("MODEL",),
369
+ "essential_actions": ("STRING", {"multiline": True,
370
+ "default": ("charges forward, swings a sword to strike down the enemy, deflects "
371
+ "a bullet, finishes them off"),
372
+ "tooltip": "Essential actions, comma-separated. If left empty (empty string), "
373
+ "'auto-direct' mode kicks in - the LLM imagines a natural action "
374
+ "sequence on its own using only the vision anchors (character "
375
+ "state/pose/context) and artistic_vibe. This only works meaningfully "
376
+ "if image recognition is on via use_vision/vision_caption (without "
377
+ "vision, the anchors are empty and the result is vague)."}),
378
+ "num_clips": (["4 (20s)", "3 (15s)", "2 (10s)"], {"default": "4 (20s)",
379
+ "tooltip": "The number of clips to actually render/save (this node is the single "
380
+ "control - other bypass toggles in the graph are not used). Remaining "
381
+ "clip slots repeat the last clip's content."}),
382
+ "artistic_vibe": ("STRING", {"multiline": True,
383
+ "default": "cinematic lighting, dynamic motion"}),
384
+ "pacing": (["auto", "slow-burn (1 beat)", "balanced (multi beat)", "rapid (4 distinct)"],
385
+ {"default": "auto"}),
386
+ "detail_level": (["Short (concise)", "Essentials + anchors only", "Detailed"],
387
+ {"default": "Essentials + anchors only"}),
388
+ "inject_triggers": ("BOOLEAN", {"default": True,
389
+ "label_on": "TRIGGERS ON", "label_off": "TRIGGERS OFF"}),
390
+ "use_vision": ("BOOLEAN", {"default": True,
391
+ "label_on": "VISION ON", "label_off": "VISION OFF (text only)"}),
392
+ "openrouter_api_key": ("STRING", {"default": ""}),
393
+ "character_name": ("STRING", {"default": "AUTO"}),
394
+ "vlm_preset": (VLM_PRESET_LABELS, {"default": "👁 Qwen2.5-VL 72B (special cases · recommended)"}),
395
+ "vlm_model": ("STRING", {"default": "qwen/qwen2.5-vl-72b-instruct"}),
396
+ "llm_preset": (LLM_PRESET_LABELS, {"default": "🔞 DeepSeek V3.2 (uncensored · stable JSON · recommended)"}),
397
+ "llm_model": ("STRING", {"default": "deepseek/deepseek-v3.2"}),
398
+ "creativity": ("FLOAT", {"default": 0.7, "min": 0.1, "max": 1.5, "step": 0.05}),
399
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
400
+ }
401
+ opt = {}
402
+ for c in range(1, 5):
403
+ for n in range(1, 5):
404
+ opt[f"c{c}_lora_{n}"] = (loras, {"default": "None"})
405
+ opt[f"c{c}_lora_{n}_on"] = ("BOOLEAN", {"default": True,
406
+ "label_on": "ON", "label_off": "OFF"})
407
+ opt[f"c{c}_w_{n}"] = ("STRING", {"default": "1.0, 1.0, 1.0", "multiline": False})
408
+ opt["manual_triggers"] = ("STRING", {"default": "", "multiline": True})
409
+ opt["vision_caption"] = ("STRING", {"multiline": True, "default": "",
410
+ "forceInput": True})
411
+ return {"required": req, "optional": opt}
412
+
413
+ RETURN_TYPES = (
414
+ "MODEL", "MODEL", "MODEL", "STRING",
415
+ "MODEL", "MODEL", "MODEL", "STRING",
416
+ "MODEL", "MODEL", "MODEL", "STRING",
417
+ "MODEL", "MODEL", "MODEL", "STRING",
418
+ "STRING", "STRING", "STRING",
419
+ "BOOLEAN", "BOOLEAN", "BOOLEAN", "BOOLEAN",
420
+ )
421
+ RETURN_NAMES = (
422
+ "c1_base", "c1_high", "c1_low", "c1_prompt",
423
+ "c2_base", "c2_high", "c2_low", "c2_prompt",
424
+ "c3_base", "c3_high", "c3_low", "c3_prompt",
425
+ "c4_base", "c4_high", "c4_low", "c4_prompt",
426
+ "triggers", "scene_analysis", "debug_info",
427
+ "c1_is_final", "c2_is_final", "c3_is_final", "c4_is_final",
428
+ )
429
+ FUNCTION = "direct"
430
+ CATEGORY = "Dolphin"
431
+
432
+ def _build_final(self, action, master_scene, name, trigger_str):
433
+ head = ", ".join([p for p in [name, trigger_str] if p])
434
+ body = " ".join([p for p in [master_scene.strip().strip(",. "), action.strip()] if p])
435
+ if head and body:
436
+ return f"{head}\n{body}"
437
+ return head or body
438
+
439
+ def _pacing_hint(self, p):
440
+ return {
441
+ "auto": "Decide pacing yourself based on how many distinct beats the actions contain.",
442
+ "slow-burn (1 beat)": "One climactic beat: clips 1-3 build tension, clip 4 detonates.",
443
+ "balanced (multi beat)": "Early clips build, later clips deliver; keep motion continuous.",
444
+ "rapid (4 distinct)": "Four distinct consecutive actions, one per clip, each kinetic.",
445
+ }.get(p, "Decide pacing yourself.")
446
+
447
+ def _detail_hint(self, d):
448
+ # 영어 키(Short/Essentials/Detailed)는 DolphinMinimaxDirector처럼 영어 UI를 쓰는
449
+ # 노드용 - 값은 한글 키와 동일, 이 함수를 공유하는 다른 노드(한글 드롭다운)를
450
+ # 깨지 않기 위해 새로 추가만 했다.
451
+ detailed_hint = (
452
+ "Keep the physical motion itself simple and singular — exactly the one action "
453
+ "given, no extra movements, no additional beats. Instead add rich VISUAL detail "
454
+ "around that single motion: skin/fabric texture, muscle tension, facial expression, "
455
+ "breathing, sweat, lighting, camera framing. Do NOT make the motion more complex or "
456
+ "add new movements to fill time.")
457
+ return {
458
+ "짧게(간결)": "Keep each clip to a short punchy motion phrase. Pure movement verbs only.",
459
+ "Short (concise)": "Keep each clip to a short punchy motion phrase. Pure movement verbs only.",
460
+ "필수+앵커만": "Each clip is one concise continuous motion. No description, only movement.",
461
+ "Essentials + anchors only": "Each clip is one concise continuous motion. No description, only movement.",
462
+ # 예전 문구("동작을 더 자세히 묘사")는 "동작 자체를 더 복잡하게"로 읽혀서, 모션을
463
+ # 계속 밀어붙이는 다른 튜닝(NAG/CFG/LoRA)과 겹치면 깡총거리기 같은 의미없는
464
+ # 필러 동작을 만들어냈다. 동작은 하나로 고정하고 시각적 디테일(질감/표정/조명/
465
+ # 카메라 등)만 늘리는 쪽으로 반전 - 새 동작/추가 비트를 유발하면 안 됨.
466
+ "상세": detailed_hint,
467
+ "Detailed": detailed_hint,
468
+ }.get(d, "Motion only, no description.")
469
+
470
+ def _extract_json(self, text):
471
+ if not text:
472
+ return None
473
+ text = re.sub(r'```(?:json)?', '', text).strip()
474
+ a, b = text.find('{'), text.rfind('}')
475
+ if a == -1 or b <= a:
476
+ return None
477
+ blob = text[a:b + 1]
478
+ try:
479
+ return json.loads(blob)
480
+ except json.JSONDecodeError:
481
+ try:
482
+ return json.loads(re.sub(r',\s*([}\]])', r'\1', blob))
483
+ except json.JSONDecodeError:
484
+ return None
485
+
486
+ def _call_or(self, messages, model, key, temp, timeout=120, force_json=False):
487
+ payload = {"model": model.strip(), "messages": messages, "temperature": temp,
488
+ "usage": {"include": True}}
489
+ if force_json:
490
+ payload["response_format"] = {"type": "json_object"}
491
+ req = urllib.request.Request(
492
+ "https://openrouter.ai/api/v1/chat/completions",
493
+ data=json.dumps(payload).encode('utf-8'),
494
+ headers={'Authorization': f'Bearer {key}', 'Content-Type': 'application/json'})
495
+ raw = json.loads(urllib.request.urlopen(req, timeout=timeout).read().decode('utf-8'))
496
+ acc = getattr(self, "_usage_acc", None)
497
+ u = raw.get("usage") or {}
498
+ if acc is not None and u:
499
+ acc["prompt"] += u.get("prompt_tokens", 0) or 0
500
+ acc["completion"] += u.get("completion_tokens", 0) or 0
501
+ c = u.get("cost")
502
+ if c is None:
503
+ c = (u.get("cost_details") or {}).get("upstream_inference_cost")
504
+ acc["cost"] += float(c or 0)
505
+ if "choices" not in raw:
506
+ err = raw.get("error") or raw
507
+ msg = err.get("message") if isinstance(err, dict) else str(err)
508
+ code = err.get("code") if isinstance(err, dict) else "?"
509
+ raise ValueError(f"API error (code={code}): {msg}")
510
+ return raw['choices'][0]['message']['content'].strip()
511
+
512
+ def _vision_analyze(self, data_uri, vlm_model, key, temp):
513
+ sys = ("You are a visual scene analyst for a video pipeline. "
514
+ "Look at the image and extract ONLY concrete anchors, no storytelling. "
515
+ "Report exactly these lines:\n"
516
+ "(1) character appearance & clothing.\n"
517
+ "(2) current pose/posture (sitting, crouched, standing, balance, limb positions).\n"
518
+ "(3) SUPPORT & CONTACT: what the character is on top of, sitting on, holding, "
519
+ "leaning against, or touching (e.g. 'seated balanced on a yoga ball', "
520
+ "'gripping a sword', 'kneeling on the floor'). This is critical for physics — "
521
+ "state it explicitly even if obvious.\n"
522
+ "(4) environment & lighting (brief).\n"
523
+ "(5) facial expression & apparent mood/vibe (e.g. 'smiling, playful', 'serious, "
524
+ "intense stare', 'shy, looking away', 'sultry, half-lidded eyes', 'laughing, "
525
+ "cheerful'). Read this from the actual face in the image, not guessed from pose.\n"
526
+ "Be terse, one short line each. English only. "
527
+ "Do not invent actions or narrative. If content is mature, describe it plainly and factually.")
528
+ messages = [
529
+ {"role": "system", "content": sys},
530
+ {"role": "user", "content": [
531
+ {"type": "text", "text": "Extract the anchors from this image."},
532
+ {"type": "image_url", "image_url": {"url": data_uri}},
533
+ ]},
534
+ ]
535
+ import time
536
+ last_err = "vision error"
537
+ for attempt in range(1, 4):
538
+ try:
539
+ resp = self._call_or(messages, vlm_model, key, temp, timeout=90).strip()
540
+ if not resp:
541
+ print(f"⚠️ [Director] VLM({vlm_model}) response was empty (refusal or empty return)")
542
+ return "", "vision empty response"
543
+ print(f"👁 [Director] VLM({vlm_model}) anchors (attempt {attempt}):\n{resp[:300]}")
544
+ return resp, None
545
+ except urllib.error.HTTPError as e:
546
+ body = ""
547
+ try:
548
+ body = e.read().decode('utf-8')[:200]
549
+ except Exception:
550
+ pass
551
+ print(f"❌ [Director] VLM HTTP {e.code} (attempt {attempt}): {body}")
552
+ last_err = f"vision HTTP {e.code}"
553
+ if e.code in (429, 503) and attempt < 3:
554
+ wait = 2 ** attempt
555
+ print(f"⏳ [Director] rate limited, retrying in {wait}s...")
556
+ time.sleep(wait)
557
+ continue
558
+ return "", last_err
559
+ except (urllib.error.URLError, TimeoutError, KeyError, ValueError) as e:
560
+ print(f"❌ [Director] VLM error (attempt {attempt}): {type(e).__name__}: {e}")
561
+ last_err = f"vision error: {e}"
562
+ if attempt < 3:
563
+ time.sleep(2 ** attempt)
564
+ continue
565
+ return "", last_err
566
+ return "", last_err
567
+
568
+ def _fallback_name(self, anchors):
569
+ import random
570
+ a = (anchors or "").lower()
571
+ east = ["asian", "east asian", "japanese", "korean", "chinese", "anime",
572
+ "kimono", "hanbok", "qipao", "oriental"]
573
+ western = ["western", "european", "caucasian", "american", "blonde", "redhead"]
574
+ east_names = ["Yuna", "Kaede", "Jin", "Mei", "Haruka", "Rin", "Sora", "Aoi"]
575
+ west_names = ["Elena", "Ryan", "Ava", "Lucas", "Mila", "Ethan", "Nora", "Leo"]
576
+ if any(k in a for k in east):
577
+ pool = east_names
578
+ elif any(k in a for k in western):
579
+ pool = west_names
580
+ else:
581
+ pool = east_names + west_names
582
+ return random.choice(pool)
583
+
584
+ def _split_actions_to_clips(self, actions, num_clips=4):
585
+ parts = split_action_parts(actions)
586
+ if not parts:
587
+ parts = [actions.strip()]
588
+ n = len(parts)
589
+ last = num_clips
590
+ assign = {c: [] for c in range(1, num_clips + 1)}
591
+ if num_clips == 1:
592
+ assign[1] = parts
593
+ return assign
594
+ build_labels = ["(build-up toward)", "(begin)", "(escalate)", "(continue escalating)"]
595
+ if n == 1:
596
+ for c in range(1, last):
597
+ label = build_labels[min(c - 1, len(build_labels) - 1)]
598
+ assign[c] = [f"{label} {parts[0]}"]
599
+ assign[last] = [parts[0]]
600
+ elif n <= num_clips:
601
+ assign[last] = [parts[-1]]
602
+ head = parts[:-1]
603
+ for i, ph in enumerate(head):
604
+ assign[min(i + 1, last - 1)].append(ph)
605
+ for c in range(1, last):
606
+ if not assign[c]:
607
+ nxt = parts[min(c, n - 1)]
608
+ assign[c] = [f"(move toward) {nxt}"]
609
+ else:
610
+ assign[last] = [parts[-1]]
611
+ rest = parts[:-1]
612
+ k, m = divmod(len(rest), last - 1)
613
+ idx = 0
614
+ for c in range(1, last):
615
+ end = idx + k + (1 if (c - 1) < m else 0)
616
+ assign[c] = rest[idx:end]
617
+ idx = end
618
+ return assign
619
+
620
+ def _plan_shots(self, anchors, actions, name, vibe, pacing, detail, llm_model, key, temp, seed,
621
+ num_clips=4, continuity_context=""):
622
+ auto_direct = not actions.strip()
623
+ if auto_direct:
624
+ assign_block = (
625
+ " (none given — INVENT the full action sequence yourself per rule 1 below, "
626
+ "grounded in the SCENE ANCHORS and MOTION STYLE)")
627
+ else:
628
+ assign = self._split_actions_to_clips(actions, num_clips)
629
+ assign_block = "\n".join(
630
+ f" clip_{c}: {', '.join(assign[c]) if assign[c] else '(continue previous motion)'}"
631
+ for c in range(1, num_clips + 1))
632
+
633
+ last_clip = num_clips
634
+ total_seconds = num_clips * 5
635
+ if num_clips >= 3:
636
+ chain = " and ".join(f"clip_{c}<-clip_{c-1}" for c in range(3, num_clips + 1))
637
+ continuity_middle = (f". The same applies {chain}: each clip picks up exactly where the "
638
+ f"previous clip's body position left off")
639
+ else:
640
+ continuity_middle = ""
641
+
642
+ continuity_rule = ""
643
+
644
+ # 클립을 여러 개 나눠서 순차적으로 별도 호출할 때(예: 클립마다 독립된 노드로
645
+ # 쪼갠 경우) 쓰는 "호출 간" 연속성 규칙. num_clips>=2의 "호출 내" 연속성 규칙과는
646
+ # 별개 - 이번 호출이 만드는 clip_1이 이전 호출에서 이미 확정된 동작 뒤를 잇게 한다.
647
+ if continuity_context.strip():
648
+ continuity_rule += (
649
+ "3b) CONTINUES A PRIOR CLIP. The action just before this one already happened "
650
+ "(see PREVIOUS CLIP ACTION below) and clip_1 here MUST begin from the exact physical "
651
+ "end-state that action would leave the body in — same position, orientation, momentum. "
652
+ "Do not restart from a neutral stance and do not repeat the previous action. Do NOT "
653
+ "narrate the hand-off explicitly (no 'continuing', 'still', 'as before').\n"
654
+ )
655
+ if num_clips >= 2:
656
+ continuity_rule += (
657
+ "3) INTER-CLIP CONTINUITY. clip_2 MUST begin from the exact physical end-state of "
658
+ "clip_1's action (same position, body orientation, and momentum) — it continues the "
659
+ "motion, it does not restart from a neutral stance" + continuity_middle +
660
+ f", as one unbroken {total_seconds}-second performance rather than {num_clips} separate poses. Do "
661
+ "NOT explicitly narrate the hand-off (no phrases like 'continuing from', 'still', 'as "
662
+ "before', 'picking up where') — simply choreograph the new action so it is kinematically "
663
+ "consistent with how the previous action would have left the body positioned. This is a "
664
+ "physical-continuity constraint, not a scripted transition line.\n"
665
+ )
666
+
667
+ json_clip_fields = ",".join(
668
+ f'"clip_{i}":"<only clip_{i} action{", the finisher" if i == last_clip else ""}, '
669
+ f'one continuous real-time motion>"' for i in range(1, num_clips + 1))
670
+
671
+ if auto_direct:
672
+ intro = (
673
+ f"You are an elite movement choreographer AND director for a {total_seconds}-second "
674
+ f"video made of {num_clips} consecutive 5-second clip{'s' if num_clips != 1 else ''}. "
675
+ "No action list is given — you invent the entire beat-by-beat sequence yourself, using "
676
+ "the SCENE ANCHORS (the character's starting look/pose/place from an image) and the "
677
+ "MOTION STYLE cue as your only creative brief.\n"
678
+ )
679
+ rule1 = (
680
+ "1) INVENT THE SEQUENCE. Design a self-contained, progressively developing action "
681
+ f"sequence of exactly "
682
+ f"{num_clips} beat{'s' if num_clips != 1 else ''} that plausibly follows from the "
683
+ "character's exact pose/state/context in the SCENE ANCHORS — what would this character "
684
+ "naturally do next, right now, from exactly that position? Build it so the sequence "
685
+ f"develops beat by beat and the LAST beat (clip_{last_clip}) is its clear payoff — "
686
+ "'develops' means the situation moves forward, NOT that each beat must be more violent "
687
+ "than the last. Do not invent new characters, props, or a change of "
688
+ "location — stay grounded in what the anchors describe. "
689
+ "(ONE exception: clip_1 may prepend a brief transition OUT of the starting physics from "
690
+ "rule 2 — e.g. leaving the yoga ball — before its invented action. This is not skipping "
691
+ "ahead; it is grounding the first beat.)\n"
692
+ )
693
+ else:
694
+ intro = (
695
+ f"You are an elite movement choreographer for a {total_seconds}-second video made of "
696
+ f"{num_clips} consecutive 5-second clip{'s' if num_clips != 1 else ''}. You are given SCENE "
697
+ "ANCHORS (the character's starting look/pose/place from an image) and a STRICT per-clip "
698
+ "action assignment.\n"
699
+ )
700
+ rule1 = (
701
+ "1) FOLLOW THE ASSIGNMENT EXACTLY. Each clip animates ONLY the action(s) assigned "
702
+ "to it below. You MUST NOT move a later clip's action into an earlier clip. "
703
+ f"clip_1 must NOT contain clip_{last_clip}'s action. The final action happens ONLY in "
704
+ f"clip_{last_clip}. "
705
+ "This is the most important rule - violating the order is a failure. "
706
+ "(ONE exception: clip_1 may prepend a brief transition OUT of the starting physics from "
707
+ "rule 2 — e.g. leaving the yoga ball — before its assigned action. This is not borrowing "
708
+ "a later action; it is grounding the first one.)\n"
709
+ )
710
+
711
+ sys = (
712
+ intro +
713
+ "\n"
714
+ "ABSOLUTE RULES:\n"
715
+ + rule1 +
716
+ "2) STARTING PHYSICS (use the anchors here). Read the character's CURRENT pose AND "
717
+ "what they are supported by / in contact with from the SCENE ANCHORS (e.g. seated on a "
718
+ "yoga ball, kneeling, gripping a weapon, leaning on a wall). clip_1 MUST begin the first "
719
+ "action FROM that exact physical situation and honor its constraints, but spend AT MOST "
720
+ "one short clause on it — e.g. anchor 'seated balanced on a yoga ball' -> clip_1 = "
721
+ "'pushes off the wobbling ball, rising fast to their feet as it rolls away, then...' — "
722
+ "the grounding clause itself must already be fast, active motion (a verb), never a static "
723
+ "description of the pose. Never ignore or contradict the starting support (do not stand "
724
+ "if seated on the ball without first leaving it; do not assume a weapon is drawn if the "
725
+ "anchor shows it sheathed). Reference the support ONLY to ground how the motion STARTS — "
726
+ "do NOT describe its appearance, color, or the wider environment.\n"
727
+ + continuity_rule +
728
+ "4) ACTION ONLY. Write physical body movement. NO scenery, NO lighting, NO clothing, "
729
+ "NO mood, NO camera talk. If a word is not a movement or body part, delete it. "
730
+ "(Referring to the starting pose in clip_1 per rule 2, or to the prior clip's ending "
731
+ "position per rule 3, is allowed since it describes how the body moves — but keep it to "
732
+ "one short clause, then move straight into the clip's own action.)\n"
733
+ "4b) LITERAL, NOT METAPHORICAL. Name body parts and acts directly (e.g. 'penis', "
734
+ "'vagina', 'cock', 'pussy', 'thrusts', 'grips') — never euphemism, poetic metaphor, or "
735
+ "vague substitution (do not write things like 'their connection deepens', 'waves of "
736
+ "pleasure', 'their bodies moved as one', 'her core'). This is a technical motion "
737
+ "description for an animation model, not literary prose — treat it like a stage "
738
+ "direction: blunt, anatomical, unambiguous about which body part does what to which.\n"
739
+ "4c) NO GESTURE CLICHÉS. Resolve any vague/decorative descriptor in the action "
740
+ "(e.g. 'cutely', 'playfully', 'sexily', 'shyly') into ONE specific, concrete gesture or "
741
+ "body movement of your own invention. NEVER default to the single most common/expected "
742
+ "cliché for that word (e.g. a peace sign / V-sign / finger-heart for 'cute', a hair-flip "
743
+ "for 'sexy') — these are overused and must be avoided. If PREVIOUS CLIP ACTION below "
744
+ "already used a specific gesture, invent a DIFFERENT one now instead of repeating it.\n"
745
+ "5) FILL 5 SECONDS. Expand the assigned action into a single continuous flow of motion "
746
+ "that occupies the whole clip (use 'then', 'without pausing', 'carrying straight into'). "
747
+ "Do not borrow the next action to fill time - elaborate the CURRENT action instead.\n"
748
+ # rule 6은 원래 "모든 클립에 폭발적 부사(swiftly/explosively/in a split second)를 박고
749
+ # gently/slowly/softly는 금지"였다. 그 규칙이 진짜 막으려던 건 "비디오 모델이 가만 두면
750
+ # 슬로모션으로 흐르는 것"인데, 실제로는 조용한 장면까지 전부 찌르고 휘두르는 날카로운
751
+ # 동작으로 만들어버렸다. 그래서 "실시간 속도 유지"(=슬로모션/정지 금지)만 남기고
752
+ # "무조건 격렬하게"는 뺐다 - 동사의 세기는 배정된 동작 자체가 정한다.
753
+ "6) REAL-TIME SPEED, NOT FORCED VIOLENCE: every clip plays at natural real-time "
754
+ "speed — NO slow motion, NO freeze, NO holding a pose, the body keeps moving "
755
+ "throughout. But do NOT inflate the movement: match the force of the verb to what "
756
+ "the action actually is. Reach for the plain accurate verb first (walks, turns, "
757
+ "leans, reaches, lifts, pushes, presses) and use hard percussive verbs (slams, "
758
+ "whips, snaps, lunges, explodes) or urgency adverbs (explosively, in a split "
759
+ "second) ONLY where the assigned action is itself sudden or violent. A quiet "
760
+ "action stays quiet while still moving continuously. 'Build-up' in rule 1 refers "
761
+ "ONLY to which action-beat is assigned to a clip — it does NOT mean a clip should "
762
+ "move slower, look hesitant, or read as a held state. Every clip is deliberate and "
763
+ "physically committed, but 'committed' means clear and purposeful, not aggressive.\n"
764
+ "7) CHARACTER NAME: study the SCENE ANCHORS. Judge the character's apparent ethnicity/"
765
+ "setting (East Asian, Western, etc.) and INVENT a specific fitting first name that "
766
+ "matches it (e.g. East Asian -> 'Yuna','Kaede','Jin'; Western -> 'Elena','Ryan'). "
767
+ "NEVER output 'AUTO' or an empty name. If a NAME is given below (not 'AUTO'), use it.\n"
768
+ f"8) {detail}\n"
769
+ "9) MOTION ENERGY: a MOTION STYLE cue is given below (e.g. 'cinematic lighting, dynamic "
770
+ "motion'). Use it ONLY to calibrate how forceful/graceful/frantic the movement FEELS "
771
+ "(word choice, verb intensity). Do NOT quote it and do NOT let any scenery/lighting/mood "
772
+ "words from it leak into the output — rule 4 still applies. Where the cue and rule 6 "
773
+ "disagree about how hard the movement should be, RULE 6 WINS.\n"
774
+ "\n"
775
+ "master_scene: leave it EMPTY.\n"
776
+ "\n"
777
+ 'OUTPUT ONLY JSON: {"character":"<a real name, never AUTO>","master_scene":"",'
778
+ + json_clip_fields + "}"
779
+ )
780
+ assign_label = (
781
+ f"INVENT THE SEQUENCE (no actions given — design {num_clips} progressively developing "
782
+ f"beats yourself per rule 1, finisher in clip_{last_clip}):"
783
+ if auto_direct else
784
+ f"STRICT PER-CLIP ACTION ASSIGNMENT (animate ONLY what is listed per clip, "
785
+ f"in this order, finisher in clip_{last_clip}):"
786
+ )
787
+ continuity_block = (
788
+ f"PREVIOUS CLIP ACTION (per rule 3b — clip_1 continues from this end-state):\n"
789
+ f"{continuity_context.strip()}\n\n"
790
+ if continuity_context.strip() else ""
791
+ )
792
+ usr = (f"NAME: {name}\nPACING: {self._pacing_hint(pacing)}\n\n"
793
+ f"MOTION STYLE (kinetic energy cue only, per rule 9 — do not describe scenery/"
794
+ f"lighting/mood from this): {vibe.strip() or '(none)'}\n\n"
795
+ f"SCENE ANCHORS (use the POSE to start clip_1's motion per rule 2; "
796
+ f"use overall look only for naming — do NOT copy look into the clips):\n"
797
+ f"{anchors or '(none — assume a neutral ready stance)'}\n\n"
798
+ f"{continuity_block}"
799
+ f"{assign_label}\n{assign_block}\n")
800
+ messages = [{"role": "system", "content": sys}, {"role": "user", "content": usr}]
801
+ t = temp
802
+ last = "⚠️ plan failed (no attempts ran)"
803
+ for attempt in (1, 2):
804
+ try:
805
+ content = self._call_or(messages, llm_model, key, t, force_json=True)
806
+ parsed = self._extract_json(content)
807
+ if parsed and all(parsed.get(f"clip_{i}") for i in range(1, num_clips + 1)):
808
+ return parsed, f"✅ plan ok (attempt {attempt})"
809
+ last = f"⚠️ incomplete JSON (attempt {attempt}): {content[:150]!r}"
810
+ print(f"⚠️ [Director] plan attempt {attempt} returned incomplete JSON: {content[:300]}")
811
+ except urllib.error.HTTPError as e:
812
+ body = ""
813
+ try:
814
+ body = e.read().decode('utf-8')[:200]
815
+ except Exception:
816
+ pass
817
+ last = f"❌ plan HTTP {e.code} (attempt {attempt}): {body}"
818
+ print(f"❌ [Director] plan {last}")
819
+ except (urllib.error.URLError, TimeoutError, KeyError, ValueError) as e:
820
+ last = f"❌ plan error (attempt {attempt}): {type(e).__name__}: {e}"
821
+ print(f"❌ [Director] {last}")
822
+ t = min(temp, 0.4)
823
+ return None, last
824
+
825
+ def _local_split(self, actions, num_clips=4):
826
+ if not actions.strip():
827
+ # 자동 연출 모드인데 LLM(키)이 없어서 실제로 "연출"은 못 함 - 이미지 근거 없이
828
+ # 그럴듯한 즉흥 동작이라도 채워서 그래프가 안 깨지게 함(무의미한 빈 문구 방지).
829
+ actions = ("starts moving, the motion gains force, reaches its peak")
830
+ parts = split_action_parts(actions)
831
+ if not parts:
832
+ parts = [actions.strip()]
833
+ n = len(parts)
834
+ last = num_clips - 1
835
+
836
+ def fast(phrase, lead="swiftly"):
837
+ p = phrase.strip()
838
+ return f"{lead} {p}, continuous real-time motion, no slow motion" if p else ""
839
+
840
+ if num_clips == 1:
841
+ return [fast(", ".join(parts), "explosively")]
842
+
843
+ if n == 1:
844
+ a = parts[0]
845
+ templates = [
846
+ f"rapidly moves into position toward {a}, no pause",
847
+ f"immediately begins {a}, brisk continuous motion, no slow motion",
848
+ f"presses {a} without stopping, fast decisive movement",
849
+ ]
850
+ out = [templates[min(i, len(templates) - 1)] for i in range(num_clips - 1)]
851
+ out.append(f"explosively completes {a} at real-time speed, no slow motion, no freeze")
852
+ elif n <= num_clips:
853
+ out = [""] * num_clips
854
+ out[last] = fast(parts[-1], "explosively")
855
+ head = parts[:-1]
856
+ for i, ph in enumerate(head):
857
+ s = min(i, num_clips - 2)
858
+ out[s] = (out[s] + ", then " + fast(ph)).strip(", ") if out[s] else fast(ph)
859
+ for i in range(num_clips - 1):
860
+ if not out[i]:
861
+ key = parts[min(i, n - 1)]
862
+ out[i] = f"rapidly moves toward {key}, brisk continuous motion, no slow motion"
863
+ else:
864
+ k, m = divmod(n - 1, num_clips - 1)
865
+ idx, chunks = 0, []
866
+ for i in range(num_clips - 1):
867
+ end = idx + k + (1 if i < m else 0)
868
+ seg = parts[idx:end]
869
+ idx = end
870
+ chunks.append(", then ".join(fast(p) for p in seg) if seg else "")
871
+ out = chunks + [fast(parts[-1], "explosively")]
872
+ return out[:num_clips]
873
+
874
+ def _load(self, name):
875
+ return comfy.utils.load_torch_file(folder_paths.get_full_path("loras", name))
876
+
877
+ @staticmethod
878
+ def _parse_weights(text):
879
+ try:
880
+ nums = [float(x) for x in re.split(r'[,\s]+', text.strip()) if x != ""]
881
+ except (ValueError, AttributeError):
882
+ nums = []
883
+ if not nums:
884
+ return 1.0, 1.0, 1.0
885
+ if len(nums) == 1:
886
+ return nums[0], nums[0], nums[0]
887
+ if len(nums) == 2:
888
+ return nums[0], nums[1], nums[1]
889
+ return nums[0], nums[1], nums[2]
890
+
891
+ def _patch_clip(self, mb, mh, ml, slots, cache, inject):
892
+ cb, ch, cl = mb, mh, ml
893
+ used = []
894
+ for (name, w_str) in slots:
895
+ if name == "None":
896
+ continue
897
+ w_b, w_h, w_l = self._parse_weights(w_str)
898
+ if w_b == 0.0 and w_h == 0.0 and w_l == 0.0:
899
+ continue
900
+ data = self._load(name)
901
+ if w_b != 0.0:
902
+ cb = apply_model_lora(cb, data, w_b)
903
+ if w_h != 0.0:
904
+ ch = apply_model_lora(ch, data, w_h)
905
+ if w_l != 0.0:
906
+ cl = apply_model_lora(cl, data, w_l)
907
+ used.append(name)
908
+ trig = []
909
+ trig_dbg = []
910
+ for n in dict.fromkeys(used):
911
+ found = get_triggers(n, cache)
912
+ trig.extend(found)
913
+ trig_dbg.append(f"{os.path.basename(n)}:{len(found)}")
914
+ self._last_trig_dbg = trig_dbg
915
+ trig_str = ", ".join(dict.fromkeys(t for t in trig if t))
916
+ prompt_trig = trig_str if inject else ""
917
+ return cb, ch, cl, prompt_trig, trig_str
918
+
919
+ def direct(self, image, model_base, model_high, model_low, essential_actions, num_clips,
920
+ artistic_vibe, pacing, detail_level, inject_triggers,
921
+ use_vision, openrouter_api_key, character_name, vlm_preset, vlm_model, llm_preset, llm_model,
922
+ creativity, seed, **kw):
923
+
924
+ n_clips = int(str(num_clips).split()[0])
925
+ self._usage_acc = {"prompt": 0, "completion": 0, "cost": 0.0}
926
+ vlm = resolve_slug(VLM_PRESETS, vlm_preset, vlm_model, "qwen/qwen2.5-vl-72b-instruct")
927
+ llm = resolve_slug(LLM_PRESETS, llm_preset, llm_model, "deepseek/deepseek-v3.2")
928
+ user_name = "" if character_name.strip().upper() in ["AUTO", ""] else character_name.strip()
929
+ key = resolve_api_key(openrouter_api_key)
930
+
931
+ vision_caption = str(kw.get("vision_caption", "") or "").strip()
932
+ anchors, dbg_v = "", ""
933
+ if vision_caption:
934
+ anchors = vision_caption
935
+ dbg_v = "vision ok (local caption node)"
936
+ print(f"👁 [Director] Using local caption input (JoyCaption etc.):\n{anchors[:300]}")
937
+ elif not use_vision:
938
+ dbg_v = "vision OFF (check toggle)"
939
+ print("ℹ️ [Director] use_vision=OFF - skipping vision")
940
+ elif not key:
941
+ dbg_v = "no api key"
942
+ print("❌ [Director] openrouter_api_key missing (neither widget nor OPENROUTER_API_KEY env var set) - vision/LLM unavailable")
943
+ elif image is None:
944
+ dbg_v = "no image"
945
+ print("❌ [Director] image input missing - check the IMAGE connection")
946
+ else:
947
+ data_uri = tensor_to_base64(image)
948
+ if data_uri:
949
+ anchors, err = self._vision_analyze(data_uri, vlm, key, min(creativity, 0.5))
950
+ dbg_v = err or "vision ok"
951
+ else:
952
+ dbg_v = "image encode failed / PIL missing"
953
+ if not vision_caption and use_vision and key and image is not None and not anchors:
954
+ print(f"⚠️ [Director] Vision anchors are empty -> no basis for name/scene. Cause: {dbg_v}")
955
+
956
+ if key:
957
+ parsed, dbg_p = self._plan_shots(anchors, essential_actions, character_name,
958
+ artistic_vibe, pacing, self._detail_hint(detail_level),
959
+ llm, key, creativity, seed, num_clips=n_clips)
960
+ if parsed:
961
+ clips = [str(parsed[f"clip_{i}"]).strip() for i in range(1, n_clips + 1)]
962
+ master = str(parsed.get("master_scene", "")).strip()
963
+ cand = str(parsed.get("character", "")).strip()
964
+ if not cand or cand.upper() == "AUTO":
965
+ cand = self._fallback_name(anchors)
966
+ final_name = user_name or cand
967
+ else:
968
+ clips = self._local_split(essential_actions, num_clips=n_clips)
969
+ # master_scene은 LLM 경로와 동일하게 비움 - 안 그러면 VLM이 뽑은 정적 사진
970
+ # 묘사(모델/조명/배경 등)가 매 클립 프롬프트 앞에 그대로 붙어서 "동작만" 규칙이
971
+ # 깨지고, 결과가 그냥 비전 캡션을 반복하는 것처럼 보임 (버그였음, 2026-07-03 수정).
972
+ master = ""
973
+ final_name = user_name or self._fallback_name(anchors)
974
+ else:
975
+ parsed = None
976
+ dbg_p = ("no api key (auto-direct unavailable, generic filler used)"
977
+ if not essential_actions.strip() else "no api key")
978
+ clips = self._local_split(essential_actions, num_clips=n_clips)
979
+ master = ""
980
+ final_name = user_name
981
+
982
+ if len(clips) < 4:
983
+ clips = clips + [clips[-1]] * (4 - len(clips))
984
+
985
+ cache = _load_cache()
986
+ manual_list = [t.strip() for t in kw.get("manual_triggers", "").split(',') if t.strip()]
987
+ outs = []
988
+ all_trigs_collected = []
989
+ for c in range(1, 5):
990
+ if c > n_clips:
991
+ cb = ch = cl = ExecutionBlocker(None)
992
+ prompt = self._build_final(clips[c - 1], master, final_name, "")
993
+ outs.extend([cb, ch, cl, prompt])
994
+ print(f" [clip{c}] SKIPPED (num_clips={n_clips}) - returning ExecutionBlocker")
995
+ continue
996
+ slots = [(kw.get(f"c{c}_lora_{n}", "None"),
997
+ kw.get(f"c{c}_w_{n}", "1.0, 1.0, 1.0")) for n in range(1, 5)
998
+ if kw.get(f"c{c}_lora_{n}_on", True)]
999
+ cb, ch, cl, prompt_trig, always_trig = self._patch_clip(
1000
+ model_base, model_high, model_low, slots, cache, inject_triggers)
1001
+ all_trig = ", ".join(dict.fromkeys(
1002
+ ([prompt_trig] if prompt_trig else []) + manual_list)) \
1003
+ if (prompt_trig or manual_list) else ""
1004
+ if always_trig:
1005
+ all_trigs_collected.extend(t.strip() for t in always_trig.split(',') if t.strip())
1006
+ prompt = self._build_final(clips[c - 1], master, final_name, all_trig)
1007
+ outs.extend([cb, ch, cl, prompt])
1008
+ print(f" [clip{c}] loras/triggers: {getattr(self, '_last_trig_dbg', [])} "
1009
+ f"inject={inject_triggers}")
1010
+
1011
+ triggers_out = ", ".join(dict.fromkeys(
1012
+ [t for t in all_trigs_collected if t] + manual_list))
1013
+
1014
+ debug = f"vlm={dbg_v} | plan={dbg_p} | char={final_name} | clips={n_clips} ({n_clips*5}s)"
1015
+ _pt = _price_tag(llm, self._usage_acc, key)
1016
+ if _pt:
1017
+ debug += f" | {_pt}"
1018
+ print(f"\n🎬 [Dolphin Vision Director] {debug}\nANCHORS: {anchors[:120]}")
1019
+ for i in range(4):
1020
+ print(f" CLIP {i+1}: {outs[i*4+3][:90]}")
1021
+
1022
+ is_final = tuple(c == n_clips for c in range(1, 5))
1023
+
1024
+ return tuple(outs) + (triggers_out, anchors, debug) + is_final
1025
+
1026
+
1027
+ NODE_CLASS_MAPPINGS = {"DolphinVisionDirector": DolphinVisionDirector}
1028
+ NODE_DISPLAY_NAME_MAPPINGS = {"DolphinVisionDirector": "🎬👁 Dolphin Vision Director (Image→4Clips)"}
h3_0824_wanrefine.json ADDED
The diff for this file is too large to render. See raw diff
 
h3_wan_refine.json ADDED
@@ -0,0 +1,866 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "b7f1a2c4-h3wan-refine-0001",
3
+ "revision": 0,
4
+ "last_node_id": 15,
5
+ "last_link_id": 14,
6
+ "nodes": [
7
+ {
8
+ "id": 1,
9
+ "type": "VHS_LoadVideoPath",
10
+ "pos": [
11
+ 40,
12
+ 380
13
+ ],
14
+ "size": [
15
+ 400,
16
+ 240
17
+ ],
18
+ "flags": {},
19
+ "order": 0,
20
+ "mode": 0,
21
+ "inputs": [
22
+ {
23
+ "name": "meta_batch",
24
+ "type": "VHS_BatchManager",
25
+ "link": null,
26
+ "shape": 7
27
+ },
28
+ {
29
+ "name": "vae",
30
+ "type": "VAE",
31
+ "link": null,
32
+ "shape": 7
33
+ }
34
+ ],
35
+ "outputs": [
36
+ {
37
+ "name": "IMAGE",
38
+ "type": "IMAGE",
39
+ "links": [
40
+ 9
41
+ ],
42
+ "slot_index": 0
43
+ },
44
+ {
45
+ "name": "frame_count",
46
+ "type": "INT",
47
+ "links": [],
48
+ "slot_index": 1
49
+ },
50
+ {
51
+ "name": "audio",
52
+ "type": "AUDIO",
53
+ "links": [
54
+ 10
55
+ ],
56
+ "slot_index": 2
57
+ },
58
+ {
59
+ "name": "video_info",
60
+ "type": "VHS_VIDEOINFO",
61
+ "links": [],
62
+ "slot_index": 3
63
+ }
64
+ ],
65
+ "properties": {
66
+ "Node name for S&R": "VHS_LoadVideoPath"
67
+ },
68
+ "widgets_values": {
69
+ "video": "D:/sd/output/H3/여기에_H3_출력_mp4_경로.mp4",
70
+ "force_rate": 0,
71
+ "custom_width": 0,
72
+ "custom_height": 0,
73
+ "frame_load_cap": 0,
74
+ "skip_first_frames": 0,
75
+ "select_every_nth": 1
76
+ },
77
+ "title": "1. H3 출력 영상 (force_rate=0 유지!)",
78
+ "color": "#232",
79
+ "bgcolor": "#353"
80
+ },
81
+ {
82
+ "id": 2,
83
+ "type": "UNETLoader",
84
+ "pos": [
85
+ 40,
86
+ 40
87
+ ],
88
+ "size": [
89
+ 400,
90
+ 60
91
+ ],
92
+ "flags": {},
93
+ "order": 1,
94
+ "mode": 0,
95
+ "inputs": [],
96
+ "outputs": [
97
+ {
98
+ "name": "MODEL",
99
+ "type": "MODEL",
100
+ "links": [
101
+ 1
102
+ ],
103
+ "slot_index": 0
104
+ }
105
+ ],
106
+ "properties": {
107
+ "Node name for S&R": "UNETLoader"
108
+ },
109
+ "widgets_values": [
110
+ "wan2.2_i2v_low_noise_14B_fp16.safetensors",
111
+ "fp8_e4m3fn_fast"
112
+ ],
113
+ "title": "2. Wan 2.2 I2V LOW noise (fp8 fast)",
114
+ "color": "#223",
115
+ "bgcolor": "#335"
116
+ },
117
+ {
118
+ "id": 3,
119
+ "type": "LoraLoaderModelOnly",
120
+ "pos": [
121
+ 40,
122
+ 150
123
+ ],
124
+ "size": [
125
+ 400,
126
+ 90
127
+ ],
128
+ "flags": {},
129
+ "order": 2,
130
+ "mode": 4,
131
+ "inputs": [
132
+ {
133
+ "name": "model",
134
+ "type": "MODEL",
135
+ "link": 1
136
+ }
137
+ ],
138
+ "outputs": [
139
+ {
140
+ "name": "MODEL",
141
+ "type": "MODEL",
142
+ "links": [
143
+ 2
144
+ ],
145
+ "slot_index": 0
146
+ }
147
+ ],
148
+ "properties": {
149
+ "Node name for S&R": "LoraLoaderModelOnly"
150
+ },
151
+ "widgets_values": [
152
+ "Wan2.2-Fun-A14B-InP-low-noise-MPS.safetensors",
153
+ 1.0
154
+ ],
155
+ "title": "3. 캐릭터 LoRA — 교체 후 bypass 해제(Ctrl+B)",
156
+ "color": "#432",
157
+ "bgcolor": "#653"
158
+ },
159
+ {
160
+ "id": 4,
161
+ "type": "WanContextWindowsManual",
162
+ "pos": [
163
+ 40,
164
+ 280
165
+ ],
166
+ "size": [
167
+ 400,
168
+ 250
169
+ ],
170
+ "flags": {},
171
+ "order": 3,
172
+ "mode": 0,
173
+ "inputs": [
174
+ {
175
+ "name": "model",
176
+ "type": "MODEL",
177
+ "link": 2
178
+ }
179
+ ],
180
+ "outputs": [
181
+ {
182
+ "name": "MODEL",
183
+ "type": "MODEL",
184
+ "links": [
185
+ 3
186
+ ],
187
+ "slot_index": 0
188
+ }
189
+ ],
190
+ "properties": {
191
+ "Node name for S&R": "WanContextWindowsManual"
192
+ },
193
+ "widgets_values": [
194
+ 81,
195
+ 8,
196
+ "standard_uniform",
197
+ 1,
198
+ false,
199
+ "pyramid",
200
+ true,
201
+ false,
202
+ false
203
+ ],
204
+ "title": "4. 컨텍스트 윈도우 (81프레임 이하 클립이면 bypass)",
205
+ "color": "#223",
206
+ "bgcolor": "#335"
207
+ },
208
+ {
209
+ "id": 5,
210
+ "type": "CLIPLoader",
211
+ "pos": [
212
+ 40,
213
+ 660
214
+ ],
215
+ "size": [
216
+ 400,
217
+ 110
218
+ ],
219
+ "flags": {},
220
+ "order": 4,
221
+ "mode": 0,
222
+ "inputs": [],
223
+ "outputs": [
224
+ {
225
+ "name": "CLIP",
226
+ "type": "CLIP",
227
+ "links": [
228
+ 4,
229
+ 5
230
+ ],
231
+ "slot_index": 0
232
+ }
233
+ ],
234
+ "properties": {
235
+ "Node name for S&R": "CLIPLoader"
236
+ },
237
+ "widgets_values": [
238
+ "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
239
+ "wan",
240
+ "default"
241
+ ],
242
+ "title": "5. umt5 (Wan 전용 - H3 텍스트 인코더 아님)",
243
+ "color": "#223",
244
+ "bgcolor": "#335"
245
+ },
246
+ {
247
+ "id": 6,
248
+ "type": "CLIPTextEncode",
249
+ "pos": [
250
+ 480,
251
+ 660
252
+ ],
253
+ "size": [
254
+ 420,
255
+ 160
256
+ ],
257
+ "flags": {},
258
+ "order": 5,
259
+ "mode": 0,
260
+ "inputs": [
261
+ {
262
+ "name": "clip",
263
+ "type": "CLIP",
264
+ "link": 4
265
+ }
266
+ ],
267
+ "outputs": [
268
+ {
269
+ "name": "CONDITIONING",
270
+ "type": "CONDITIONING",
271
+ "links": [
272
+ 6
273
+ ],
274
+ "slot_index": 0
275
+ }
276
+ ],
277
+ "properties": {
278
+ "Node name for S&R": "CLIPTextEncode"
279
+ },
280
+ "widgets_values": [
281
+ "H3에 넣었던 프롬프트를 그대로 붙여넣을 것. 캐릭터 LoRA를 켰다면 트리거 워드를 여기 추가. 저denoise에선 프롬프트가 구도를 바꾸진 못하고 질감 방향만 잡는다."
282
+ ],
283
+ "title": "positive",
284
+ "color": "#232",
285
+ "bgcolor": "#353"
286
+ },
287
+ {
288
+ "id": 7,
289
+ "type": "CLIPTextEncode",
290
+ "pos": [
291
+ 480,
292
+ 850
293
+ ],
294
+ "size": [
295
+ 420,
296
+ 160
297
+ ],
298
+ "flags": {},
299
+ "order": 6,
300
+ "mode": 0,
301
+ "inputs": [
302
+ {
303
+ "name": "clip",
304
+ "type": "CLIP",
305
+ "link": 5
306
+ }
307
+ ],
308
+ "outputs": [
309
+ {
310
+ "name": "CONDITIONING",
311
+ "type": "CONDITIONING",
312
+ "links": [
313
+ 7
314
+ ],
315
+ "slot_index": 0
316
+ }
317
+ ],
318
+ "properties": {
319
+ "Node name for S&R": "CLIPTextEncode"
320
+ },
321
+ "widgets_values": [
322
+ "blurry, low quality, oversharpened, plastic skin, waxy, flicker, 色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋的, 残缺的, 多余的手指"
323
+ ],
324
+ "title": "negative"
325
+ },
326
+ {
327
+ "id": 8,
328
+ "type": "VAELoader",
329
+ "pos": [
330
+ 40,
331
+ 810
332
+ ],
333
+ "size": [
334
+ 400,
335
+ 60
336
+ ],
337
+ "flags": {},
338
+ "order": 7,
339
+ "mode": 0,
340
+ "inputs": [],
341
+ "outputs": [
342
+ {
343
+ "name": "VAE",
344
+ "type": "VAE",
345
+ "links": [
346
+ 8
347
+ ],
348
+ "slot_index": 0
349
+ }
350
+ ],
351
+ "properties": {
352
+ "Node name for S&R": "VAELoader"
353
+ },
354
+ "widgets_values": [
355
+ "Wan2_1_VAE_bf16.safetensors"
356
+ ],
357
+ "title": "6. Wan2.1 VAE (16ch) — 다른 걸 꽂으면 노드가 막는다",
358
+ "color": "#223",
359
+ "bgcolor": "#335"
360
+ },
361
+ {
362
+ "id": 9,
363
+ "type": "WanH3Refine_Dolphin",
364
+ "pos": [
365
+ 960,
366
+ 300
367
+ ],
368
+ "size": [
369
+ 430,
370
+ 430
371
+ ],
372
+ "flags": {},
373
+ "order": 8,
374
+ "mode": 0,
375
+ "inputs": [
376
+ {
377
+ "name": "model",
378
+ "type": "MODEL",
379
+ "link": 3
380
+ },
381
+ {
382
+ "name": "positive",
383
+ "type": "CONDITIONING",
384
+ "link": 6
385
+ },
386
+ {
387
+ "name": "negative",
388
+ "type": "CONDITIONING",
389
+ "link": 7
390
+ },
391
+ {
392
+ "name": "vae",
393
+ "type": "VAE",
394
+ "link": 8
395
+ },
396
+ {
397
+ "name": "images",
398
+ "type": "IMAGE",
399
+ "link": 9
400
+ },
401
+ {
402
+ "name": "audio",
403
+ "type": "AUDIO",
404
+ "link": 10,
405
+ "shape": 7
406
+ }
407
+ ],
408
+ "outputs": [
409
+ {
410
+ "name": "IMAGE",
411
+ "type": "IMAGE",
412
+ "links": [
413
+ 11
414
+ ],
415
+ "slot_index": 0
416
+ },
417
+ {
418
+ "name": "audio",
419
+ "type": "AUDIO",
420
+ "links": [
421
+ 12
422
+ ],
423
+ "slot_index": 1
424
+ },
425
+ {
426
+ "name": "frames",
427
+ "type": "INT",
428
+ "links": [],
429
+ "slot_index": 2
430
+ },
431
+ {
432
+ "name": "width",
433
+ "type": "INT",
434
+ "links": [],
435
+ "slot_index": 3
436
+ },
437
+ {
438
+ "name": "height",
439
+ "type": "INT",
440
+ "links": [],
441
+ "slot_index": 4
442
+ },
443
+ {
444
+ "name": "info",
445
+ "type": "STRING",
446
+ "links": [
447
+ 13
448
+ ],
449
+ "slot_index": 5
450
+ }
451
+ ],
452
+ "properties": {
453
+ "Node name for S&R": "WanH3Refine_Dolphin"
454
+ },
455
+ "widgets_values": [
456
+ 0,
457
+ "fixed",
458
+ 4,
459
+ 1.0,
460
+ "euler",
461
+ "simple",
462
+ 0.3,
463
+ 3.0,
464
+ "trim",
465
+ "first_frame",
466
+ 1.0,
467
+ 1.0,
468
+ 24
469
+ ],
470
+ "title": "7. Wan Refine H3 — 리파인 패스 본체",
471
+ "color": "#432",
472
+ "bgcolor": "#653"
473
+ },
474
+ {
475
+ "id": 10,
476
+ "type": "VHS_VideoCombine",
477
+ "pos": [
478
+ 1440,
479
+ 300
480
+ ],
481
+ "size": [
482
+ 460,
483
+ 560
484
+ ],
485
+ "flags": {},
486
+ "order": 9,
487
+ "mode": 0,
488
+ "inputs": [
489
+ {
490
+ "name": "images",
491
+ "type": "IMAGE",
492
+ "link": 11
493
+ },
494
+ {
495
+ "name": "audio",
496
+ "type": "AUDIO",
497
+ "link": 12,
498
+ "shape": 7
499
+ },
500
+ {
501
+ "name": "meta_batch",
502
+ "type": "VHS_BatchManager",
503
+ "link": null,
504
+ "shape": 7
505
+ },
506
+ {
507
+ "name": "vae",
508
+ "type": "VAE",
509
+ "link": null,
510
+ "shape": 7
511
+ }
512
+ ],
513
+ "outputs": [
514
+ {
515
+ "name": "Filenames",
516
+ "type": "VHS_FILENAMES",
517
+ "links": [],
518
+ "slot_index": 0
519
+ }
520
+ ],
521
+ "properties": {
522
+ "Node name for S&R": "VHS_VideoCombine"
523
+ },
524
+ "widgets_values": {
525
+ "frame_rate": 24,
526
+ "loop_count": 0,
527
+ "filename_prefix": "H3_REFINE/%date:yy-MM-dd-hh-mm%_h3refine",
528
+ "format": "video/nvenc_h264-mp4",
529
+ "pix_fmt": "yuv420p",
530
+ "bitrate": 10,
531
+ "megabit": true,
532
+ "save_metadata": true,
533
+ "pingpong": false,
534
+ "save_output": true
535
+ },
536
+ "title": "8. 저장 (24fps 고정 — H3 네이티브)",
537
+ "color": "#232",
538
+ "bgcolor": "#353"
539
+ },
540
+ {
541
+ "id": 11,
542
+ "type": "PreviewAny",
543
+ "pos": [
544
+ 1440,
545
+ 900
546
+ ],
547
+ "size": [
548
+ 460,
549
+ 200
550
+ ],
551
+ "flags": {},
552
+ "order": 10,
553
+ "mode": 0,
554
+ "inputs": [
555
+ {
556
+ "name": "source",
557
+ "type": "*",
558
+ "link": 13
559
+ }
560
+ ],
561
+ "outputs": [
562
+ {
563
+ "name": "STRING",
564
+ "type": "STRING",
565
+ "links": [],
566
+ "slot_index": 0
567
+ }
568
+ ],
569
+ "properties": {
570
+ "Node name for S&R": "PreviewAny"
571
+ },
572
+ "widgets_values": [],
573
+ "title": "리파인 리포트 (시작 σ / MoE 판정 / 정렬 로그)"
574
+ },
575
+ {
576
+ "id": 12,
577
+ "type": "WanH3FrameGrid_Dolphin",
578
+ "pos": [
579
+ 960,
580
+ 40
581
+ ],
582
+ "size": [
583
+ 430,
584
+ 110
585
+ ],
586
+ "flags": {},
587
+ "order": 11,
588
+ "mode": 0,
589
+ "inputs": [],
590
+ "outputs": [
591
+ {
592
+ "name": "length",
593
+ "type": "INT",
594
+ "links": [],
595
+ "slot_index": 0
596
+ },
597
+ {
598
+ "name": "seconds",
599
+ "type": "FLOAT",
600
+ "links": [],
601
+ "slot_index": 1
602
+ },
603
+ {
604
+ "name": "info",
605
+ "type": "STRING",
606
+ "links": [
607
+ 14
608
+ ],
609
+ "slot_index": 2
610
+ }
611
+ ],
612
+ "properties": {
613
+ "Node name for S&R": "WanH3FrameGrid_Dolphin"
614
+ },
615
+ "widgets_values": [
616
+ 5.9,
617
+ "wan_compatible"
618
+ ],
619
+ "title": "H3 length 계산기 (실행 전 여기서 값부터 확인)",
620
+ "color": "#223",
621
+ "bgcolor": "#335"
622
+ },
623
+ {
624
+ "id": 13,
625
+ "type": "PreviewAny",
626
+ "pos": [
627
+ 960,
628
+ 190
629
+ ],
630
+ "size": [
631
+ 430,
632
+ 80
633
+ ],
634
+ "flags": {},
635
+ "order": 12,
636
+ "mode": 0,
637
+ "inputs": [
638
+ {
639
+ "name": "source",
640
+ "type": "*",
641
+ "link": 14
642
+ }
643
+ ],
644
+ "outputs": [
645
+ {
646
+ "name": "STRING",
647
+ "type": "STRING",
648
+ "links": [],
649
+ "slot_index": 0
650
+ }
651
+ ],
652
+ "properties": {
653
+ "Node name for S&R": "PreviewAny"
654
+ },
655
+ "widgets_values": [],
656
+ "title": "→ 이 length를 H3 노드에 넣어라"
657
+ },
658
+ {
659
+ "id": 14,
660
+ "type": "Note",
661
+ "pos": [
662
+ 480,
663
+ 40
664
+ ],
665
+ "size": [
666
+ 440,
667
+ 560
668
+ ],
669
+ "flags": {},
670
+ "order": 13,
671
+ "mode": 0,
672
+ "inputs": [],
673
+ "outputs": [],
674
+ "properties": {
675
+ "Node name for S&R": "Note"
676
+ },
677
+ "widgets_values": [
678
+ "H3 → Wan 2.2 리파인 패스\n\n■ 실행 순서\n1) 노드 12(계산기)만 먼저 돌려서 length 확인 → H3 생성 때 그 값을 쓴다.\n 두 그리드가 겹치는 값: 5 / 73 / 141 / 209 / 277 / 345\n H3 기본 124는 Wan에서 불법이라 121로 잘리고, 그만큼 오디오 싱크가 밀린다.\n2) 노드 1에 H3 출력 mp4 경로를 넣는다. force_rate는 반드시 0\n (24fps 원본을 그대로 읽어야 한다).\n3) 노드 3에 캐릭터 LoRA를 지정하고 Ctrl+B로 bypass 해제.\n 이게 이 파이프라인의 핵심 이득이다 — H3엔 못 거는 LoRA를 여기서 건다.\n4) 노드 6에 H3 프롬프트 + LoRA 트리거를 붙여넣는다.\n\n■ 왜 LOW noise만 로드하나\ndenoise 0.30 / shift 3.0이면 시작 σ가 0.562라서 MoE 경계(0.9)를 안 넘는다.\nHIGH expert는 호출 자체가 안 되므로 로드할 이유가 없다.\nshift=5면 denoise 0.62까지, shift=3이면 0.75까지 LOW 단독 구간이다.\n노드 7의 info 출력이 매 실행마다 이 판정을 찍어준다."
679
+ ],
680
+ "color": "#432",
681
+ "bgcolor": "#653"
682
+ },
683
+ {
684
+ "id": 15,
685
+ "type": "Note",
686
+ "pos": [
687
+ 480,
688
+ 1050
689
+ ],
690
+ "size": [
691
+ 440,
692
+ 560
693
+ ],
694
+ "flags": {},
695
+ "order": 14,
696
+ "mode": 0,
697
+ "inputs": [],
698
+ "outputs": [],
699
+ "properties": {
700
+ "Node name for S&R": "Note"
701
+ },
702
+ "widgets_values": [
703
+ "■ 튜닝 순서 (한 번에 하나씩)\n\n1차 A/B — denoise 0.20 / 0.30 / 0.40\n Wan은 16fps 기준으로 학습돼서 24fps 소스를 '덜 움직이는 영상'으로 읽는다.\n 올릴수록 모션을 죽이는 방향이므로 모션이 뭉개지기 시작하는 지점이 상한선.\n 보통 0.35 근처.\n\n2차 A/B — 캐릭터 LoRA 강도 0.6 / 1.0 / 1.3\n\n■ 건드리지 말 것\n· steps=6은 '실제로 도는 스텝 수'다. denoise가 낮다고 3배로 올리지 말 것\n (스케줄 총 길이가 steps/denoise로 자동으로 늘어난다).\n· sigma_shift는 생성용 5가 아니라 2~3. 리파인은 저시그마를 촘촘히 밟아야\n 하는데 shift가 크면 스텝이 고시그마로 쏠린다.\n· cfg는 증류(lightning/lightx2v) LoRA를 걸었으면 1.0, 뺐으면 2.5~3.5.\n 리파인 패스엔 증류 LoRA를 아예 안 거는 걸 권장 — 궤적 끝 30%만 밟는\n 상황과 전체 궤적 기준 증류가 안 맞는다.\n· upscale_by는 1.3까지. 큰 업스케일은 SeedVR2/Topaz로 따로.\n\n■ 이 패스로 못 고치는 것\n· 작은 얼굴 붕괴 → ComfyUI-H3-FaceRefine (크롭해서 H3로 재생성)\n· 대사 샷 립싱크 → denoise 0.2 이하로 묶거나 아예 리파인 제외\n· 해상도 증가 → 이 패스 다음에 SeedVR2/Topaz"
704
+ ],
705
+ "color": "#432",
706
+ "bgcolor": "#653"
707
+ }
708
+ ],
709
+ "links": [
710
+ [
711
+ 1,
712
+ 2,
713
+ 0,
714
+ 3,
715
+ 0,
716
+ "MODEL"
717
+ ],
718
+ [
719
+ 2,
720
+ 3,
721
+ 0,
722
+ 4,
723
+ 0,
724
+ "MODEL"
725
+ ],
726
+ [
727
+ 3,
728
+ 4,
729
+ 0,
730
+ 9,
731
+ 0,
732
+ "MODEL"
733
+ ],
734
+ [
735
+ 4,
736
+ 5,
737
+ 0,
738
+ 6,
739
+ 0,
740
+ "CLIP"
741
+ ],
742
+ [
743
+ 5,
744
+ 5,
745
+ 0,
746
+ 7,
747
+ 0,
748
+ "CLIP"
749
+ ],
750
+ [
751
+ 6,
752
+ 6,
753
+ 0,
754
+ 9,
755
+ 1,
756
+ "CONDITIONING"
757
+ ],
758
+ [
759
+ 7,
760
+ 7,
761
+ 0,
762
+ 9,
763
+ 2,
764
+ "CONDITIONING"
765
+ ],
766
+ [
767
+ 8,
768
+ 8,
769
+ 0,
770
+ 9,
771
+ 3,
772
+ "VAE"
773
+ ],
774
+ [
775
+ 9,
776
+ 1,
777
+ 0,
778
+ 9,
779
+ 4,
780
+ "IMAGE"
781
+ ],
782
+ [
783
+ 10,
784
+ 1,
785
+ 2,
786
+ 9,
787
+ 5,
788
+ "AUDIO"
789
+ ],
790
+ [
791
+ 11,
792
+ 9,
793
+ 0,
794
+ 10,
795
+ 0,
796
+ "IMAGE"
797
+ ],
798
+ [
799
+ 12,
800
+ 9,
801
+ 1,
802
+ 10,
803
+ 1,
804
+ "AUDIO"
805
+ ],
806
+ [
807
+ 13,
808
+ 9,
809
+ 5,
810
+ 11,
811
+ 0,
812
+ "STRING"
813
+ ],
814
+ [
815
+ 14,
816
+ 12,
817
+ 2,
818
+ 13,
819
+ 0,
820
+ "STRING"
821
+ ]
822
+ ],
823
+ "groups": [
824
+ {
825
+ "id": 1,
826
+ "title": "모델 체인 (LOW noise 단독)",
827
+ "bounding": [
828
+ 20,
829
+ -20,
830
+ 440,
831
+ 570
832
+ ],
833
+ "color": "#3f789e",
834
+ "font_size": 24,
835
+ "flags": {}
836
+ },
837
+ {
838
+ "id": 2,
839
+ "title": "리파인",
840
+ "bounding": [
841
+ 940,
842
+ 260,
843
+ 470,
844
+ 500
845
+ ],
846
+ "color": "#a1309b",
847
+ "font_size": 24,
848
+ "flags": {}
849
+ }
850
+ ],
851
+ "definitions": {
852
+ "subgraphs": []
853
+ },
854
+ "config": {},
855
+ "extra": {
856
+ "frontendVersion": "1.48.7",
857
+ "ds": {
858
+ "scale": 0.6,
859
+ "offset": [
860
+ 100,
861
+ 100
862
+ ]
863
+ }
864
+ },
865
+ "version": 0.4
866
+ }
lora_profile.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 로라 파일 하나를 "LLM에게 설명할 수 있는 프로필"로 변환한다.
4
+
5
+ 기존 `get_triggers()`(dolphin_vision_director.py)는 트리거 '단어'만 뽑아서
6
+ 캐릭터 이름 옆에 콤마로 나열하는 용도였다. 그 방식은 문법적 맥락이 없어서
7
+ `bounce`/`swing` 같은 범용 동작 동사가 전신 모션으로 새는 문제가 있었고,
8
+ 대응으로 그런 단어를 아예 제외하는 필터(_filter_generic_motion_verbs)를
9
+ 넣어둔 상태다 - 로라가 의도한 효과까지 같이 사라지는 우회책이다.
10
+
11
+ 이 모듈은 다른 방향으로 접근한다: 트리거를 '나중에 덧붙일 토큰'이 아니라
12
+ '프롬프트를 쓰기 전에 LLM에게 줄 재료'로 본다. LoRA Manager가 파일 옆에
13
+ 만들어두는 `<이름>.metadata.json` 사이드카에 이미 다음이 다 들어있다.
14
+
15
+ - model_name / civitai.model.name : 이 로라의 이름
16
+ - modelDescription : 자연어 설명(HTML)
17
+ - tags / civitai.model.tags : 분류 태그
18
+ - civitai.trainedWords : 학습 트리거. 단어인 경우도 있지만
19
+ 상당수는 학습 캡션 문장 그대로다
20
+ (예: "She pulls down her pants revealing a penis.")
21
+ - base_model : Wan 2.1/2.2, i2v/t2v 구분
22
+
23
+ 로라는 학습 캡션 분포와 닮은 프롬프트에서 가장 잘 발화하므로, 이 재료를
24
+ LLM에 먼저 주면 트리거를 문장 안에 문법적으로 녹인 프롬프트를 쓸 수 있다.
25
+
26
+ trainedWords 3분류 (`classify_trained_words` 참고):
27
+ hard : 공백 없고 숫자나 대문자가 섞인 학습 식별자. LLM이 "고쳐주면"
28
+ 로라가 안 걸리므로 원문 그대로 나와야 한다 (예: `4n4lfi5tin6`,
29
+ `PENISLORA`). 출력 검증 대상.
30
+ soft : 공백 없는 평범한 소문자 영단어 (예: `bounce`, `penis`).
31
+ 강제하지 않는다 - 강제 삽입이 바로 예전 버그의 원인이었다.
32
+ 문맥에 맞을 때 몸 부위에 붙여 쓰라고 '권고'만 한다.
33
+ caption: 공백이 있는 구/문장. 학습 캡션 예시로만 준다(문체 참고용).
34
+ """
35
+ import json
36
+ import os
37
+ import re
38
+
39
+ _HTML_TAG_RE = re.compile(r"<[^>]+>")
40
+ _WS_RE = re.compile(r"\s+")
41
+
42
+ # 세션 내 프로필 캐시. 키는 로라 파일의 (경로, size, mtime).
43
+ _PROFILE_CACHE = {}
44
+
45
+ # LLM 컨텍스트를 잡아먹지 않도록 설명은 잘라서 준다. 로라 설명문은 보통
46
+ # 앞부분에 "무엇을 하는 로라인지"가 있고 뒤는 학습 설정/후원 링크다.
47
+ MAX_DESC_CHARS = 400
48
+ MAX_CAPTION_EXAMPLES = 2
49
+
50
+
51
+ def _strip_html(s):
52
+ if not s:
53
+ return ""
54
+ s = re.sub(r"<br\s*/?>", " ", s, flags=re.IGNORECASE)
55
+ s = re.sub(r"</p>", " ", s, flags=re.IGNORECASE)
56
+ s = _HTML_TAG_RE.sub("", s)
57
+ s = (s.replace("&nbsp;", " ").replace("&amp;", "&")
58
+ .replace("&lt;", "<").replace("&gt;", ">").replace("&quot;", '"'))
59
+ return _WS_RE.sub(" ", s).strip()
60
+
61
+
62
+ def _truncate(s, limit):
63
+ if not s or len(s) <= limit:
64
+ return s
65
+ cut = s[:limit]
66
+ # 문장 중간에서 자르지 않도록 마지막 문장부호까지만.
67
+ for sep in (". ", "! ", "? "):
68
+ idx = cut.rfind(sep)
69
+ if idx > limit * 0.5:
70
+ return cut[:idx + 1]
71
+ return cut.rstrip() + "..."
72
+
73
+
74
+ def is_hard_token(word):
75
+ """LLM이 철자를 바꾸면 안 되는 학습 식별자인가.
76
+
77
+ 공백이 없고(=단일 토큰) 숫자나 대문자가 섞여있으면 학습 식별자로 본다.
78
+ `4n4lfi5tin6`, `PENISLORA`, `az420` -> True
79
+ `bounce`, `penis`, `swing` -> False (평범한 소문자 영단어)
80
+ """
81
+ w = word.strip()
82
+ if not w or _WS_RE.search(w):
83
+ return False
84
+ return any(c.isdigit() for c in w) or any(c.isupper() for c in w)
85
+
86
+
87
+ def classify_trained_words(words):
88
+ """trainedWords 리스트를 (hard, soft, captions)로 나눈다."""
89
+ hard, soft, captions = [], [], []
90
+ for raw in words or []:
91
+ w = str(raw).strip().strip(",")
92
+ if not w:
93
+ continue
94
+ if _WS_RE.search(w):
95
+ captions.append(w)
96
+ elif is_hard_token(w):
97
+ hard.append(w)
98
+ else:
99
+ soft.append(w)
100
+ # 순서 유지 중복 제거
101
+ return (list(dict.fromkeys(hard)), list(dict.fromkeys(soft)),
102
+ list(dict.fromkeys(captions)))
103
+
104
+
105
+ def _sidecar_path(lora_path):
106
+ """LoRA Manager가 만드는 `<확장자 뗀 이름>.metadata.json`."""
107
+ return os.path.splitext(lora_path)[0] + ".metadata.json"
108
+
109
+
110
+ def _read_sidecar(lora_path):
111
+ p = _sidecar_path(lora_path)
112
+ if not os.path.exists(p):
113
+ return None
114
+ try:
115
+ with open(p, "r", encoding="utf-8") as f:
116
+ return json.load(f)
117
+ except (OSError, ValueError, json.JSONDecodeError) as e:
118
+ print(f"⚠️ [LoraProfile] 사이드카 파싱 실패({os.path.basename(p)}): "
119
+ f"{type(e).__name__}: {e}")
120
+ return None
121
+
122
+
123
+ def _resolve_lora_path(lora_name):
124
+ try:
125
+ import folder_paths
126
+ return folder_paths.get_full_path("loras", lora_name)
127
+ except Exception:
128
+ return lora_name if os.path.exists(lora_name) else None
129
+
130
+
131
+ def build_profile_from_sidecar(data, lora_name, lora_path):
132
+ """사이드카 dict -> 프로필 dict. 사이드카가 없으면 호출하지 않는다."""
133
+ civitai = data.get("civitai") or {}
134
+ model = civitai.get("model") or {}
135
+
136
+ name = (data.get("model_name") or model.get("name")
137
+ or os.path.splitext(os.path.basename(lora_name))[0])
138
+ desc = _strip_html(data.get("modelDescription") or model.get("description") or "")
139
+ tags = data.get("tags") or model.get("tags") or []
140
+ if not isinstance(tags, list):
141
+ tags = []
142
+ trained = civitai.get("trainedWords") or []
143
+ if not isinstance(trained, list):
144
+ trained = [trained]
145
+ hard, soft, captions = classify_trained_words(trained)
146
+
147
+ return {
148
+ "lora_name": lora_name,
149
+ "file": os.path.basename(lora_name),
150
+ "display_name": str(name).strip(),
151
+ "description": _truncate(desc, MAX_DESC_CHARS),
152
+ "tags": [str(t).strip() for t in tags if str(t).strip()],
153
+ "base_model": str(data.get("base_model") or civitai.get("baseModel") or "").strip(),
154
+ "hard_tokens": hard,
155
+ "soft_words": soft,
156
+ "captions": captions[:MAX_CAPTION_EXAMPLES],
157
+ "source": "sidecar",
158
+ }
159
+
160
+
161
+ def load_profile(lora_name, fallback_triggers=None):
162
+ """로라 이름(ComfyUI 상대경로) -> 프로필 dict. 실패 시 None.
163
+
164
+ 사이드카가 없으면 `fallback_triggers`(기존 get_triggers 경로의 결과)로
165
+ 최소 프로필이라도 만든다 - 개인 학습 로라처럼 civitai에 없는 경우.
166
+
167
+ `fallback_triggers`는 리스트 또는 "호출하면 리스트를 주는 함수"를 받는다.
168
+ 기존 조회 경로는 sha256 해시(수 GB 파일) + 네트워크 호출까지 할 수 있어서,
169
+ 사이드카가 있는 99.5%의 경우엔 아예 호출되지 않도록 지연 평가한다.
170
+ """
171
+ if not lora_name or lora_name == "None":
172
+ return None
173
+
174
+ path = _resolve_lora_path(lora_name)
175
+ cache_key = lora_name
176
+ if path and os.path.exists(path):
177
+ try:
178
+ st = os.stat(path)
179
+ cache_key = f"{lora_name}::{st.st_size}::{int(st.st_mtime)}"
180
+ except OSError:
181
+ pass
182
+ if cache_key in _PROFILE_CACHE:
183
+ return _PROFILE_CACHE[cache_key]
184
+
185
+ profile = None
186
+ if path:
187
+ data = _read_sidecar(path)
188
+ if data:
189
+ profile = build_profile_from_sidecar(data, lora_name, path)
190
+
191
+ if profile is None:
192
+ # 사이드카 없음 - 기존 트리거 조회 결과로 최소 프로필.
193
+ fb = fallback_triggers() if callable(fallback_triggers) else fallback_triggers
194
+ hard, soft, captions = classify_trained_words(fb or [])
195
+ if not (hard or soft or captions):
196
+ return None
197
+ profile = {
198
+ "lora_name": lora_name,
199
+ "file": os.path.basename(lora_name),
200
+ "display_name": os.path.splitext(os.path.basename(lora_name))[0],
201
+ "description": "",
202
+ "tags": [],
203
+ "base_model": "",
204
+ "hard_tokens": hard,
205
+ "soft_words": soft,
206
+ "captions": captions[:MAX_CAPTION_EXAMPLES],
207
+ "source": "triggers-only",
208
+ }
209
+
210
+ _PROFILE_CACHE[cache_key] = profile
211
+ return profile
212
+
213
+
214
+ def format_profiles_for_llm(profiles, indent=" "):
215
+ """프로필 목록 -> LLM 프롬프트에 넣을 텍스트 블록.
216
+
217
+ 한 로라당 4줄 이내로 압축한다. 로라가 4개까지 걸릴 수 있고 클립도 4개라
218
+ 장황하면 시스템 프롬프트의 다른 규칙이 묻힌다.
219
+ """
220
+ if not profiles:
221
+ return ""
222
+ lines = []
223
+ for p in profiles:
224
+ head = f"{indent}- {p['display_name']}"
225
+ if p.get("tags"):
226
+ head += f" [tags: {', '.join(p['tags'][:5])}]"
227
+ lines.append(head)
228
+ if p.get("description"):
229
+ lines.append(f"{indent} what it does: {p['description']}")
230
+ if p.get("hard_tokens"):
231
+ lines.append(f"{indent} MUST APPEAR VERBATIM: "
232
+ + ", ".join(f'"{t}"' for t in p["hard_tokens"]))
233
+ if p.get("soft_words"):
234
+ lines.append(f"{indent} vocabulary it was trained on (use only where it "
235
+ f"genuinely fits, anchored to the body part): "
236
+ + ", ".join(p["soft_words"]))
237
+ for cap in p.get("captions", []):
238
+ lines.append(f'{indent} training caption example: "{cap}"')
239
+ return "\n".join(lines)
240
+
241
+
242
+ def collect_hard_tokens(profiles):
243
+ out = []
244
+ for p in profiles or []:
245
+ out.extend(p.get("hard_tokens") or [])
246
+ return list(dict.fromkeys(out))
247
+
248
+
249
+ def missing_hard_tokens(text, profiles):
250
+ """생성된 프롬프트에 빠진 hard token 목록.
251
+
252
+ LLM은 `4n4lfi5tin6` 같은 토큰을 "정상 철자로 고쳐주는" 경향이 있어서
253
+ (그 순간 로라가 안 걸린다) 대소문자 무시하고 존재만 확인한다.
254
+ """
255
+ low = (text or "").lower()
256
+ return [t for t in collect_hard_tokens(profiles) if t.lower() not in low]
257
+
258
+
259
+ if __name__ == "__main__":
260
+ # 실제 로라 폴더에 대고 돌려보는 자체 점검.
261
+ import glob
262
+ import sys
263
+
264
+ # ComfyUI 콘솔은 UTF-8이지만 맨손으로 돌릴 땐 cp949라 이모지 print가 죽는다.
265
+ try:
266
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
267
+ except (AttributeError, OSError):
268
+ pass
269
+
270
+ root = sys.argv[1] if len(sys.argv) > 1 else "."
271
+ files = sorted(glob.glob(os.path.join(root, "*.safetensors")))
272
+ print(f"safetensors {len(files)}개 검사\n")
273
+ stats = {"sidecar": 0, "none": 0, "hard": 0, "soft": 0, "caption": 0, "desc": 0}
274
+ samples = []
275
+ for f in files:
276
+ data = _read_sidecar(f)
277
+ if not data:
278
+ stats["none"] += 1
279
+ continue
280
+ p = build_profile_from_sidecar(data, os.path.basename(f), f)
281
+ stats["sidecar"] += 1
282
+ if p["hard_tokens"]:
283
+ stats["hard"] += 1
284
+ if p["soft_words"]:
285
+ stats["soft"] += 1
286
+ if p["captions"]:
287
+ stats["caption"] += 1
288
+ if p["description"]:
289
+ stats["desc"] += 1
290
+ if p["hard_tokens"] and len(samples) < 5:
291
+ samples.append(p)
292
+ print(f"사이드카 있음: {stats['sidecar']} / 없음: {stats['none']}")
293
+ print(f" 설명 있음: {stats['desc']}")
294
+ print(f" hard token 있음: {stats['hard']}")
295
+ print(f" soft word 있음: {stats['soft']}")
296
+ print(f" caption 예시 있음: {stats['caption']}\n")
297
+ for p in samples:
298
+ print("=" * 60)
299
+ print(format_profiles_for_llm([p]))
mask_region_color_match.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import torch
3
+ import torch.nn.functional as F
4
+
5
+
6
+ class MaskRegionColorMatch_Dolphin:
7
+ """Color-matches the pixels inside the mask against the pixels in a
8
+ ring immediately outside the mask, using only those two pixel sets
9
+ (never a rectangular crop that mixes correct and incorrect pixels).
10
+ Mixing dilutes the statistical difference the transfer function sees;
11
+ comparing pure "already correct" vs pure "actually wrong" pixels gives
12
+ the strongest possible correction signal."""
13
+
14
+ @classmethod
15
+ def INPUT_TYPES(s):
16
+ return {
17
+ "required": {
18
+ "original_image": ("IMAGE", {"tooltip": "Reference image (same size as generated_image)."}),
19
+ "generated_image": ("IMAGE", {"tooltip": "Image to color-correct (same size as original_image)."}),
20
+ "mask": ("MASK", {"tooltip": "Inpainted region mask (same size as the images)."}),
21
+ "method": (["hm", "mkl", "reinhard", "mvgd", "hm-mvgd-hm", "hm-mkl-hm"], {"default": "hm", "tooltip": "hm (histogram matching) is numerically stable for small pixel counts. mkl/mvgd need a matrix square root and can go complex/unstable on small or low-variance regions."}),
22
+ "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05}),
23
+ "ring_width": ("INT", {"default": 60, "min": 4, "max": 512, "step": 2, "tooltip": "Thickness in pixels of the original-image ring just outside the mask used as the color reference."}),
24
+ "edge_feather": ("INT", {"default": 40, "min": 0, "max": 512, "step": 2, "tooltip": "Fades the correction from 0 at the mask boundary up to full strength this many pixels inside the mask, so there's no color cliff for MaskBlend to reveal as a ring."}),
25
+ }
26
+ }
27
+
28
+ RETURN_TYPES = ("IMAGE",)
29
+ RETURN_NAMES = ("image",)
30
+ FUNCTION = "match"
31
+ CATEGORY = "image/postprocessing"
32
+
33
+ def match(self, original_image, generated_image, mask, method, strength, ring_width, edge_feather):
34
+ if strength == 0:
35
+ return (generated_image,)
36
+
37
+ try:
38
+ from color_matcher import ColorMatcher
39
+ except ImportError as e:
40
+ raise ImportError("Can't import color-matcher, install with: pip install color-matcher") from e
41
+
42
+ out = generated_image.clone()
43
+ batch = generated_image.shape[0]
44
+ cm = ColorMatcher()
45
+ kernel_size = ring_width * 2 + 1
46
+
47
+ for i in range(batch):
48
+ m = mask[min(i, mask.shape[0] - 1)]
49
+ m_bin = (m > 0.5).float()
50
+ if m_bin.max() <= 0:
51
+ continue
52
+
53
+ dilated = F.max_pool2d(m_bin.unsqueeze(0).unsqueeze(0), kernel_size=kernel_size, stride=1, padding=ring_width)
54
+ dilated = (dilated.squeeze(0).squeeze(0) > 0.5)
55
+ interior = m_bin > 0.5
56
+ ring = dilated & (~interior)
57
+
58
+ if ring.sum().item() < 10:
59
+ ring = ~interior
60
+
61
+ ref_pixels = original_image[i][ring].cpu().numpy()
62
+ target_pixels = generated_image[i][interior].cpu().numpy()
63
+ if target_pixels.shape[0] == 0 or ref_pixels.shape[0] == 0:
64
+ continue
65
+
66
+ # Per-pixel correction weight: 0 right at the mask boundary,
67
+ # ramping up to 1 over edge_feather px toward the mask interior.
68
+ # Without this, the correction is uniform inside the mask and
69
+ # MaskBlend's outer feather ends up revealing the resulting
70
+ # color jump as a visible ring instead of hiding it.
71
+ if edge_feather > 0:
72
+ k = edge_feather * 2 + 1
73
+ falloff = F.avg_pool2d(m_bin.unsqueeze(0).unsqueeze(0), kernel_size=k, stride=1, padding=edge_feather)
74
+ falloff = falloff.squeeze(0).squeeze(0).clamp(0, 1)
75
+ alpha = falloff[interior].numpy()[:, None]
76
+ else:
77
+ alpha = 1.0
78
+
79
+ try:
80
+ result = cm.transfer(src=target_pixels[:, None, :], ref=ref_pixels[:, None, :], method=method)
81
+ result = result[:, 0, :]
82
+ if np.iscomplexobj(result):
83
+ # mkl/mvgd take a matrix square root; on small or low-variance
84
+ # pixel sets this can go complex. The imaginary part is
85
+ # numerical noise here, so drop it rather than silently
86
+ # discarding the whole correction.
87
+ result = result.real
88
+ result = np.nan_to_num(result, nan=0.0, posinf=1.0, neginf=0.0)
89
+ result = target_pixels + (strength * alpha) * (result - target_pixels)
90
+ result_t = torch.from_numpy(result).clamp(0, 1).to(out.dtype)
91
+ out[i][interior] = result_t
92
+ except Exception as e:
93
+ print(f"[MaskRegionColorMatch_Dolphin] batch {i} failed, leaving uncorrected: {e}")
94
+
95
+ return (out,)
96
+
97
+
98
+ NODE_CLASS_MAPPINGS = {
99
+ "MaskRegionColorMatch_Dolphin": MaskRegionColorMatch_Dolphin,
100
+ }
101
+ NODE_DISPLAY_NAME_MAPPINGS = {
102
+ "MaskRegionColorMatch_Dolphin": "🐬 Mask-Region Color Match (Dolphin)",
103
+ }
match_size.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn.functional as F
3
+
4
+
5
+ class MatchImageAndMaskSize_Dolphin:
6
+ """Resizes an image and a mask to exactly match a reference image's
7
+ runtime pixel size. Reads reference.shape directly at execution time,
8
+ so it can't drift out of sync the way two separately-wired
9
+ GetImageSize -> width/height links can."""
10
+
11
+ @classmethod
12
+ def INPUT_TYPES(s):
13
+ return {
14
+ "required": {
15
+ "image_to_resize": ("IMAGE", {"tooltip": "Image that will be resized to match the reference."}),
16
+ "mask_to_resize": ("MASK", {"tooltip": "Mask that will be resized to match the reference."}),
17
+ "reference_image": ("IMAGE", {"tooltip": "Image whose size image_to_resize and mask_to_resize will be matched to."}),
18
+ }
19
+ }
20
+
21
+ RETURN_TYPES = ("IMAGE", "MASK")
22
+ RETURN_NAMES = ("image", "mask")
23
+ FUNCTION = "match"
24
+ CATEGORY = "image/postprocessing"
25
+
26
+ def match(self, image_to_resize, mask_to_resize, reference_image):
27
+ target_h = reference_image.shape[1]
28
+ target_w = reference_image.shape[2]
29
+
30
+ img = image_to_resize.movedim(-1, 1)
31
+ img = F.interpolate(img, size=(target_h, target_w), mode="bicubic", antialias=True)
32
+ img = img.clamp(0, 1).movedim(1, -1)
33
+
34
+ mask = mask_to_resize
35
+ if mask.ndim == 2:
36
+ mask = mask.unsqueeze(0)
37
+ mask = F.interpolate(mask.unsqueeze(1), size=(target_h, target_w), mode="nearest-exact").squeeze(1)
38
+
39
+ return (img, mask)
40
+
41
+
42
+ NODE_CLASS_MAPPINGS = {
43
+ "MatchImageAndMaskSize_Dolphin": MatchImageAndMaskSize_Dolphin,
44
+ }
45
+ NODE_DISPLAY_NAME_MAPPINGS = {
46
+ "MatchImageAndMaskSize_Dolphin": "🐬 Match Image & Mask Size (Dolphin)",
47
+ }