diff --git a/.claude/launch.json b/.claude/launch.json deleted file mode 100644 index 4f1e617affd49ef0ea41c561906f92bd1c5c221b..0000000000000000000000000000000000000000 --- a/.claude/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "0.0.1", - "configurations": [ - { - "name": "comfyui", - "runtimeExecutable": "D:\\SD\\ComfyUI_windows_portable\\python_embeded\\python.exe", - "runtimeArgs": [ - "-s", - "D:\\SD\\ComfyUI_windows_portable\\ComfyUI\\main.py", - "--windows-standalone-build", - "--enable-dynamic-vram", - "--use-sage-attention", - "--output-directory", - "D:\\sd\\output" - ], - "port": 8188 - } - ] -} diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index a617ac968d9e16e21cdb36587b0f52a3f7d58ac8..0000000000000000000000000000000000000000 --- a/.claude/settings.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(curl -s -m * http://127.0.0.1:8188/*)", - "Bash(curl -s -m * \"http://127.0.0.1:8188/*)", - "Bash(curl -s http://127.0.0.1:8188*)", - "Bash(curl -s \"http://127.0.0.1:8188*)", - "Bash(curl -s * http://127.0.0.1:8188*)", - "Bash(curl -s * \"http://127.0.0.1:8188*)", - "Bash(nvidia-smi)", - "Bash(nvidia-smi --query-gpu=*)", - "Bash(nvidia-smi --query-compute-apps=*)", - "Bash(tasklist)", - "Bash(tasklist *)", - "Bash(wmic process get *)", - "Bash(where *)", - "Bash(git --no-pager log *)", - "Bash(git --no-pager diff *)", - "Bash(git --no-pager show *)", - "Bash(git --no-pager status *)", - "Bash(git add *)", - "Bash(git commit *)", - "PowerShell(Get-ChildItem *)", - "PowerShell(Get-Item *)", - "PowerShell(Get-Process *)", - "PowerShell(Get-CimInstance *)", - "PowerShell(Get-NetTCPConnection *)", - "PowerShell(Get-WinEvent *)", - "PowerShell(nvidia-smi --query-gpu=*)", - "PowerShell(nvidia-smi --query-compute-apps=*)", - "mcp__claude-in-chrome__tabs_context_mcp", - "mcp__Claude_Browser__read_page", - "mcp__Claude_Browser__get_page_text", - "mcp__Claude_Browser__read_console_messages", - "mcp__Claude_Browser__read_network_requests", - "mcp__Claude_Browser__find", - "mcp__Claude_Browser__tabs_context", - "mcp__Claude_Browser__preview_list", - "mcp__Claude_Browser__preview_logs" - ] - } -} diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 9cdfa2d57336b409d58be775a7f2e7ea27c6bf60..0000000000000000000000000000000000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,665 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(claude mcp *)", - "Bash(\"/c/Users/bjoo/.local/bin/claude.exe\" -p \"Call the list_saved_workflows tool from comfyui-cloud and print the raw result.\" --allowedTools \"mcp__comfyui-cloud__list_saved_workflows\")", - "Bash(curl -s -m 10 http://127.0.0.1:8188/system_stats)", - "Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); [print\\(g.get\\('name'\\), g.get\\('vram_free'\\)\\) for g in d.get\\('devices',[]\\)]\")", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/input/**)", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/models/checkpoints/**)", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/models/diffusion_models/**)", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/models/unet/**)", - "mcp__claude-in-chrome__tabs_context_mcp", - "Bash(curl -s -m 10 \"http://127.0.0.1:8188/object_info/UNETLoader\")", - "Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d['UNETLoader']['input'], indent=2\\)[:1500]\\)\")", - "Bash(curl -s -m 10 \"http://127.0.0.1:8188/object_info/ShowText|pysssss\")", - "Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); k=list\\(d.keys\\(\\)\\)[0]; print\\(json.dumps\\(d[k]['input'], indent=2\\)[:800]\\); print\\('OUTPUT_NODE:', d[k].get\\('output_node'\\)\\)\")", - "Bash(mkdir -p /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad)", - "Bash(curl -s -m 10 \"http://127.0.0.1:8188/object_info/DolphinVisionDirector\")", - "Bash(python - /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/dvd_info.json)", - "Read(//c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/**)", - "Bash(curl -sv -m 10 http://127.0.0.1:8188/object_info/DolphinVisionDirector -o /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/dvd_info.json)", - "Bash(curl -s -m 60 http://127.0.0.1:8188/object_info/DolphinVisionDirector -o /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/dvd_info.json)", - "Bash(curl -s -m 8 http://127.0.0.1:8188/system_stats -o /dev/null -w \"system_stats: %{http_code} in %{time_total}s\\\\n\")", - "Bash(curl -s -m 8 http://127.0.0.1:8188/queue -w \"\\\\nqueue: %{http_code} in %{time_total}s\\\\n\")", - "PowerShell(Get-Process -Id 31388 -ErrorAction SilentlyContinue)", - "PowerShell(Get-CimInstance Win32_Process -Filter \"ProcessId=31388\" -ErrorAction SilentlyContinue)", - "Bash(curl -s -m 8 http://127.0.0.1:8188/system_stats -o /dev/null -w \"now: %{http_code} in %{time_total}s\\\\n\")", - "Bash(curl -s -m 30 http://127.0.0.1:8188/object_info/DolphinVisionDirector -o /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/dvd_info.json)", - "Bash(curl -s -m 15 \"http://127.0.0.1:8188/history/f583e65d-e2d9-46fc-a4e5-a67122c3adc4\" -o \"/c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/history_result.json\")", - "Bash(PYTHONIOENCODING=utf-8 python - /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/history_result.json)", - "mcp__claude-in-chrome__navigate", - "mcp__claude-in-chrome__javascript_tool", - "mcp__claude-in-chrome__computer", - "mcp__claude-in-chrome__read_page", - "mcp__claude-in-chrome__tabs_create_mcp", - "Bash(python -)", - "Bash(python -m py_compile dolphin_vision_director.py)", - "PowerShell(Get-NetTCPConnection -LocalPort 8188 -ErrorAction SilentlyContinue)", - "Bash(curl -s -m 20 \"http://127.0.0.1:8188/object_info/DolphinVisionDirector\" -o /dev/null -w \"DolphinVisionDirector object_info: %{http_code}\\\\n\")", - "Bash(curl -s -m 20 http://127.0.0.1:8188/object_info/WanVideoNAG -o /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/nag_info.json)", - "Bash(PYTHONIOENCODING=utf-8 python - /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/7b5a824e-a1dd-42ed-be45-b5a9cccfc005/scratchpad/nag_info.json)", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/user/**)", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/**)", - "Bash(python3 -c ' *)", - "Bash(python -c ' *)", - "Read(//c/Users/bjoo/AppData/Local/ComfyUI-LoRA-Manager/cache/model/**)", - "Bash(python - /c/Users/bjoo/AppData/Local/ComfyUI-LoRA-Manager/cache/model/comfyui.sqlite)", - "Bash(PYTHONIOENCODING=utf-8 python -c ' *)", - "Bash(mkdir -p \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/5f5946c1-33a3-471e-8b49-6552febb9164/scratchpad\")", - "Bash(cd \"D:/SD/ComfyUI_windows_portable\" && ls)", - "PowerShell(Get-NetTCPConnection -LocalPort 8188 -State Listen -ErrorAction SilentlyContinue)", - "Bash(curl -s -m 8 http://127.0.0.1:8188/queue)", - "PowerShell(Get-CimInstance Win32_Process -Filter \"ProcessId=5452\" -ErrorAction SilentlyContinue)", - "Bash(cd \"D:/SD/ComfyUI_windows_portable/ComfyUI/models/loras\" 2>&1 && find . -iname \"*PENISLORA*\" 2>&1)", - "Bash(ls \"C:\\\\Users\\\\bjoo\\\\.claude\\\\projects\\\\D--SD-ComfyUI-windows-portable-ComfyUI\\\\memory\\\\\" 2>&1)", - "Skill(fewer-permission-prompts)", - "Skill(fewer-permission-prompts:*)", - "Bash(ls -la \"C:/Users/bjoo/.claude/projects/\" 2>&1 | head -50)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/5f5946c1-33a3-471e-8b49-6552febb9164/scratchpad/scan_perms2.py\")", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/5f5946c1-33a3-471e-8b49-6552febb9164/scratchpad/scan_curl.py\")", - "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:8188/ --max-time 3)", - "Bash(curl -s http://127.0.0.1:8188/history?max_items=5 --max-time 5)", - "Bash(curl -s http://127.0.0.1:8188/history?max_items=5 --max-time 5 -o C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/history.json)", - "Bash(curl -s http://127.0.0.1:8188/queue --max-time 5)", - "Bash(nvidia-smi --query-gpu=name,memory.used,memory.total,memory.free --format=csv)", - "PowerShell(Get-CimInstance Win32_OperatingSystem)", - "Read(//d/SD/ComfyUI_windows_portable/**)", - "PowerShell(Get-CimInstance Win32_Process -Filter \"Name = 'python.exe'\")", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/**)", - "Bash(grep -rniL \"^$\" --include=\"*.py\" -l \"unload_all_models\\\\|soft_empty_cache\\\\|free_memory\\\\|PurgeVRAM\\\\|UnloadAllModels\" .)", - "Bash(grep -rniE \"unload_all_models|soft_empty_cache\\\\\\(|PurgeVRAM|UnloadAllModels|Free Memory|FreeMemory|ClearVRAM|VRAM.?Debug\" --include=\"*.py\" -l .)", - "Bash(cd \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/comfyui_memory_cleanup\" && cat __init__.py 2>/dev/null | head -150)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/VRAMCleanup\" --max-time 5)", - "Bash(grep -rl \"class TripleKSamplerWan22LightningAdvanced\" --include=\"*.py\" .)", - "Bash(curl -s http://127.0.0.1:8188/object_info/TripleKSamplerWan22LightningAdvanced --max-time 5 -o C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/tks_info.json)", - "Read(//d/sd/models/**)", - "Bash(find \"D:/sd\" -maxdepth 2 -type d 2>/dev/null | head -20)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/TorchCompileModel\" --max-time 5)", - "mcp__claude-in-chrome__read_console_messages", - "mcp__claude-in-chrome__read_network_requests", - "Bash(curl -s -D - -o /dev/null \"http://127.0.0.1:8188/\" --max-time 5 -A \"Mozilla/5.0 \\(Windows NT 10.0; Win64; x64\\) AppleWebKit/537.36 \\(KHTML, like Gecko\\) Chrome/120.0.0.0 Safari/537.36\")", - "mcp__claude-in-chrome__list_connected_browsers", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/Fast%20Groups%20Bypasser%20\\(rgthree\\)\" --max-time 5)", - "Bash(curl -s http://127.0.0.1:8188/object_info --max-time 15 -o C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/all_object_info.json)", - "Bash(head -50 __init__.py)", - "Bash(python_embeded/python.exe -c ' *)", - "Bash(PYTHONIOENCODING=utf-8 python_embeded/python.exe -c ' *)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/DolphinVisionDirector\" --max-time 5)", - "Bash(cp 0705_vision2.json 0705_vision2.json.bak_20260703_before_numclips)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/VHS_VideoCombine\" --max-time 5)", - "PowerShell(Stop-Process -Id 2044 -Force)", - "PowerShell(Get-Process -Id 2044 -ErrorAction SilentlyContinue)", - "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://127.0.0.1:8188/ --max-time 5)", - "Bash(curl -s \"http://127.0.0.1:8188/queue\" --max-time 5)", - "Bash(curl -s -o /dev/null -w \"VRAMCleanup: %{http_code}\\\\n\" \"http://127.0.0.1:8188/object_info/VRAMCleanup\")", - "Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\('VRAMCleanup registered:', bool\\(d\\)\\)\")", - "Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\('TorchCompileModel registered:', bool\\(d\\)\\)\")", - "Bash(curl -s http://127.0.0.1:8188/history?max_items=10 --max-time 5 -o C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/history2.json)", - "Bash(\"D:/SD/ComfyUI_windows_portable/python_embeded/python.exe\" -m pip show sageattention)", - "Bash(\"D:/SD/ComfyUI_windows_portable/python_embeded/python.exe\" -c \"import sageattention; print\\(sageattention.__file__\\); print\\(dir\\(sageattention\\)\\)\")", - "Bash(D:/SD/ComfyUI_windows_portable/python_embeded/python.exe -c ' *)", - "Bash(\"D:/SD/ComfyUI_windows_portable/python_embeded/python.exe\" -c \"import sageattn3\")", - "WebSearch", - "WebFetch(domain:github.com)", - "Bash(\"D:/SD/ComfyUI_windows_portable/python_embeded/python.exe\" --version)", - "Bash(where nvcc *)", - "Bash(nvcc --version)", - "Bash(where cl.exe)", - "Bash(\"C:/Program Files \\(x86\\)/Microsoft Visual Studio/Installer/vswhere.exe\" -latest -products '*' -property installationPath)", - "Bash(find \"C:/Program Files/Microsoft Visual Studio/2022\" -iname \"vcvarsall.bat\" 2>/dev/null)", - "Bash(cmd //c \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\fcfc6473-30fd-4447-95fe-3e3e03867e77\\\\scratchpad\\\\build_sage3.bat\")", - "Bash(cmd //c \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\fcfc6473-30fd-4447-95fe-3e3e03867e77\\\\scratchpad\\\\build_sage3_v2.bat\")", - "Bash(cmd //c \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\fcfc6473-30fd-4447-95fe-3e3e03867e77\\\\scratchpad\\\\build_sage3_v3.bat\")", - "Bash(cmd //c \"C:\\\\sage3build\\\\build_sage3.bat\")", - "Bash(git checkout *)", - "Bash(\"D:/SD/ComfyUI_windows_portable/python_embeded/python.exe\" -m pip show sageattn3)", - "Bash(cp 0705_vision2.json 0705_vision2.json.bak_20260703_before_lightningsteps)", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\inspect_wf.py\")", - "Bash(grep -n \"\" \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/tripleksampler/nodes.py\" | sed -n '1140,1260p')", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\inspect_vd.py\")", - "Bash(/c/Windows/py -c ' *)", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\trace_prompt_links.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\trace2.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\trace3.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\remove_settings_node.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\bump_steps_cfg.py\")", - "mcp__claude-in-chrome__find", - "mcp__claude-in-chrome__form_input", - "mcp__d88663d0-df79-4bf4-92bd-3d23932f7923__hub_repo_search", - "mcp__d88663d0-df79-4bf4-92bd-3d23932f7923__hub_repo_details", - "WebFetch(domain:huggingface.co)", - "WebFetch(domain:civitai.com)", - "mcp__d88663d0-df79-4bf4-92bd-3d23932f7923__hf_hub_query", - "mcp__d88663d0-df79-4bf4-92bd-3d23932f7923__hf_fs", - "Bash(powershell -NoProfile -Command \"Get-CimInstance Win32_Process -Filter \\\\\"Name='python.exe'\\\\\" | Select-Object ProcessId, CommandLine | Format-List\")", - "Bash(powershell -NoProfile -Command \"Stop-Process -Id 47672 -Force\")", - "Bash(powershell -NoProfile -Command \"Get-CimInstance Win32_Process -Filter \\\\\"Name='python.exe'\\\\\" | Select-Object ProcessId, CommandLine\")", - "Bash(powershell -NoProfile -Command \"Get-Process -Id 47672 -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Responding\")", - "Bash(sleep 15 && ls -t \"D:/SD/ComfyUI_windows_portable\"/comfyui_restart_*.log 2>/dev/null | head -3)", - "Bash(powershell -NoProfile -Command \"Stop-Process -Id 2668 -Force\")", - "Bash(powershell -NoProfile -Command \"Stop-Process -Id 11268 -Force\")", - "Bash(powershell -NoProfile -Command \"Get-Process -Id 11268 -ErrorAction SilentlyContinue | Select-Object Id\")", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/web/dolphin_lora_rows.js\" /tmp/check.mjs)", - "Bash(node --check /tmp/check.mjs)", - "Bash(powershell -NoProfile -Command \"Stop-Process -Id 45940 -Force\")", - "Bash(powershell -NoProfile -Command \"Get-CimInstance Win32_Process -Filter \\\\\"Name='python.exe'\\\\\" | Select-Object ProcessId\")", - "Bash(powershell -NoProfile -Command \"Get-Process -Id 45940 -ErrorAction SilentlyContinue\")", - "Bash(curl -s -o /dev/null -w \"HTTP %{http_code}\\\\n\" http://127.0.0.1:8188/extensions/dolphin_nodes/dolphin_lora_rows.js)", - "Bash(curl -s http://127.0.0.1:8188/extensions/dolphin_nodes/dolphin_lora_rows.js)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/web/dolphin_lora_rows.js\" /tmp/check2.mjs)", - "Bash(node --check /tmp/check2.mjs)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/web/dolphin_lora_rows.js\" /tmp/check3.mjs)", - "Bash(node --check /tmp/check3.mjs)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/web/dolphin_lora_rows.js\" /tmp/check4.mjs)", - "Bash(node --check /tmp/check4.mjs)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/web/dolphin_lora_rows.js\" /tmp/check5.mjs)", - "Bash(node --check /tmp/check5.mjs)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/web/dolphin_lora_rows.js\" /tmp/check6.mjs)", - "Bash(node --check /tmp/check6.mjs)", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\fix_link1455.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\fix_link1376.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\remove_lightx2v_only_mode.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\dump_widgets.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\reset_lora_widgets.py\")", - "Bash(curl -s http://127.0.0.1:8188/history?max_items=1)", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\check_history.py\")", - "Read(//d/sd/models/loras/**)", - "Bash(sort -k9)", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\set_native4step.py\")", - "Bash(python -m py_compile __init__.py arch_logic.py promp_logic_v28_stitch.py promp_logic_v30_story.py promp_logic_v32_vision.py)", - "Bash(powershell -NoProfile -Command \"Stop-Process -Id 23756 -Force\")", - "Bash(powershell -NoProfile -Command \"Get-Process -Id 23756 -ErrorAction Stop\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\check_gguf_history.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\diff_v30_vs_current.py\")", - "Bash(/c/Windows/py \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\57e6bdef-7658-487a-ba07-aea70100b0eb\\\\scratchpad\\\\apply_wan21_hybrid.py\")", - "Bash(powershell -Command \"\\(Get-Item '0705_vision2.json'\\).Length\")", - "Bash(stat -c%s \"0705_vision2.json\")", - "Bash(node -e ' *)", - "Bash(cp \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\0705_vision2.json\" \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\user\\\\default\\\\workflows\\\\0705_vision2.json\")", - "Bash(grep -n '^ \"links\"\\\\|^ \"links\"\\\\|\"links\": \\\\[\\\\s*$' \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\0705_vision2.json\")", - "Bash(python3 -c \"print\\('hello'\\)\")", - "Bash(echo \"exit:$?\")", - "Bash(python)", - "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://127.0.0.1:8188 --max-time 5)", - "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://localhost:8188 --max-time 5)", - "Bash(curl -s http://127.0.0.1:8188/object_info/MatchImageAndMaskSize_Dolphin)", - "Bash(\"D:/SD/ComfyUI_windows_portable/python_embeded/python.exe\" -c \"import color_matcher; print\\(color_matcher.__file__\\)\")", - "Bash(curl -s http://127.0.0.1:8188/object_info/MaskRegionColorMatch_Dolphin)", - "Bash(python -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d['MaskRegionColorMatch_Dolphin']['input']['required']['ring_width']\\)\\)\")", - "Bash(python -c \"import sys,json; d=json.load\\(sys.stdin\\)['MaskRegionColorMatch_Dolphin']['input']['required']; print\\(list\\(d.keys\\(\\)\\)\\)\")", - "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d['hf_models'], indent=2\\)\\)\")", - "Bash(git config *)", - "Bash(git add *)", - "Bash(git commit -m ' *)", - "PowerShell(Get-CimInstance Win32_Process -Filter \"ProcessId=70880\" -ErrorAction SilentlyContinue)", - "Bash(curl -s -m 8 \"http://127.0.0.1:8188/object_info/DolphinVisionDirector\" -o /dev/null -w \"DolphinVisionDirector: %{http_code}\\\\n\")", - "Bash(curl -s -m 8 \"http://127.0.0.1:8188/object_info/DolphinPromptDirector\" -o /dev/null -w \"DolphinPromptDirector: %{http_code}\\\\n\")", - "Bash(curl -s -m 8 \"http://127.0.0.1:8188/object_info/DolphinPromptDirector\")", - "Bash(python -m py_compile dolphin_prompt_director.py)", - "Bash(curl -s -m 8 \"http://127.0.0.1:8188/object_info/DolphinPromptDirector\" -o \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/5f5946c1-33a3-471e-8b49-6552febb9164/scratchpad/ppd_info2.json\")", - "Bash(xargs -I{} sh -c 'grep -l \"getMostRecentWorkflow\" \"{}\" 2>/dev/null')", - "Bash(grep -o \"getMostRecentWorkflow[^}]\\\\{0,300\\\\}\" \"D:/SD/ComfyUI_windows_portable/python_embeded/Lib/site-packages/comfyui_frontend_package/static/assets/dialogService-CVq-DX47.js\")", - "Bash(curl -s -m 8 \"http://127.0.0.1:8188/api/extensions\")", - "Bash(ls \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\\" | head -20; echo \"---\"; ls \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\" 2>/dev/null | head -20)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/485e6e77-03ce-4325-8ff3-3895d87d3091/scratchpad/gen_v2v_workflow.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" -c \"print\\('hello'\\)\"; Write-Output \"EXIT=$LASTEXITCODE\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" -c \"import torch; print\\('torch OK', torch.__version__\\)\"; Write-Output \"EXIT=$LASTEXITCODE\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\d4bb3e78-b9b7-42fa-912c-5e8d393db7cb\\\\scratchpad\\\\smoke_test.py\")", - "Bash(rm -rf \"/c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d4bb3e78-b9b7-42fa-912c-5e8d393db7cb/scratchpad/dsb_test\")", - "Bash(./python.exe \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/build.py\" --help)", - "Read(//d/SD/**)", - "Read(//d//**)", - "Read(//d/sd/models/diffusion_models/**)", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\dataset_builder\\\\train\\\\setup_env.ps1\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\dataset_builder\\\\train\\\\setup_env.ps1\" -Yes)", - "Bash(./python.exe \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/train/download_checkpoints.py\")", - "Bash(./python.exe \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/train/download_checkpoints.py\" --yes)", - "Bash(./python.exe -c ' *)", - "Bash(powershell -Command \"\\(Get-Item 'D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\user\\\\default\\\\workflows\\\\10S_comb.json'\\).Length\")", - "Bash(find \"/c/Users/bjoo/.cache/huggingface/hub\" -maxdepth 1 -iname \"*Wan_2.2_ComfyUI*\" -o -iname \"*Wan2.1-I2V-14B-720P*\" 2>/dev/null *)", - "PowerShell(Get-Process -Id 89032)", - "PowerShell(Get-NetTCPConnection -OwningProcess 89032 -ErrorAction SilentlyContinue)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/485e6e77-03ce-4325-8ff3-3895d87d3091/scratchpad/patch_10s_comb.py\")", - "Bash(python -m py_compile wan_svipro_fixed.py __init__.py)", - "Bash(\"./venv/Scripts/python.exe\" -m pip install \"torch==2.9.1\" torchvision --index-url https://download.pytorch.org/whl/cu130)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/485e6e77-03ce-4325-8ff3-3895d87d3091/scratchpad/patch_svipro_fixed.py\")", - "Bash(\"./venv/Scripts/python.exe\" -c \"import torch; print\\(torch.__version__, torch.version.cuda\\); print\\(torch.cuda.get_device_capability\\(0\\)\\); print\\(torch.cuda.is_available\\(\\)\\)\")", - "Bash(PYTHONIOENCODING=utf-8 \"./venv/Scripts/python.exe\" \"./musubi_tuner/src/musubi_tuner/wan_train_network.py\" --help)", - "Bash(./python.exe D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/build.py --input __TRACKED_VAR__/raw --output __TRACKED_VAR__/out --trigger test_subject --captioner wd14)", - "Bash(python -m py_compile wan_svipro_fixed.py)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/485e6e77-03ce-4325-8ff3-3895d87d3091/scratchpad/patch_svipro_amplitude.py\")", - "Bash(git commit *)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/485e6e77-03ce-4325-8ff3-3895d87d3091/scratchpad/patch_continuity.py\")", - "Bash(mkdir -p C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d4bb3e78-b9b7-42fa-912c-5e8d393db7cb/scratchpad/smoke_ds2/raw)", - "Bash(wmic process *)", - "Bash(python -m py_compile wan_svipro_gray.py __init__.py)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/485e6e77-03ce-4325-8ff3-3895d87d3091/scratchpad/patch_svipro_gray.py\")", - "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://127.0.0.1:7861)", - "Bash(./python.exe \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/inspect_lora.py\" \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\models\\\\loras\\\\2255050_wan2.2pisswet_high_noise.safetensors\")", - "Bash(cmd //c \"set\")", - "Bash(./python.exe \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/inspect_lora.py\" \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\models\\\\loras\\\\PENISLORA_22_i2v_HIGH_e320.safetensors\")", - "Bash(ls -la ~/Downloads/ 2>/dev/null | grep -i yt-dlp)", - "Bash(where python *)", - "Bash(python -m py_compile yt-dlp-gui.py)", - "Bash(ls \"/c/Users/bjoo/AppData/Local/Programs/Python/Python312/\" | grep -i pythonw)", - "PowerShell(Get-NetAdapter | Where-Object {$_.InterfaceDescription -match 'VPN|TAP|WireGuard|Tunnel|PPP'} | Select-Object Name, InterfaceDescription, Status | Format-Table -AutoSize)", - "PowerShell(Get-Process | Where-Object {$_.ProcessName -match 'openvpn|wireguard|nordvpn|expressvpn|protonvpn|surfshark|tunnelbear|cisco|anyconnect|forticlient|globalprotect|zerotier|tailscale|mullvad|ivpn|hotspotshield|windscribe|hidemyass|privatevpn|purevpn'} | Select-Object ProcessName, Id | Format-Table -AutoSize)", - "Bash(timeout 3 python yt-dlp-gui.py)", - "Bash(echo \"EXIT_CODE:$?\")", - "Bash(grep -E '^[0-9]+$')", - "Bash(curl -s -o /dev/null -w \"http %{http_code}\\\\n\" http://127.0.0.1:7861)", - "Bash(rm -rf C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d4bb3e78-b9b7-42fa-912c-5e8d393db7cb/scratchpad/webui_test/train_out3)", - "Bash(echo \"launched pid $!\")", - "Bash(python -m py_compile wan_svipro_gray.py)", - "Bash(ls -la C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d4bb3e78-b9b7-42fa-912c-5e8d393db7cb/scratchpad/webui_test/*.log)", - "Bash(echo \"=== full run_train3.log ===\")", - "Bash(cat C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d4bb3e78-b9b7-42fa-912c-5e8d393db7cb/scratchpad/webui_test/run_train3.log)", - "Bash(curl -s -o /dev/null -w \"webui http %{http_code}\\\\n\" http://127.0.0.1:7861)", - "Bash(grep -v \"^$\")", - "Bash(rm -rf \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d4bb3e78-b9b7-42fa-912c-5e8d393db7cb/scratchpad/webui_test\")", - "Bash(rm -f \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d4bb3e78-b9b7-42fa-912c-5e8d393db7cb/scratchpad/webui.log\")", - "Bash(curl -s -o /dev/null -w \"webui still up: http %{http_code}\\\\n\" http://127.0.0.1:7861)", - "PowerShell(Stop-Process -Id 103496 -Force)", - "PowerShell(Get-Process -Id 103496 -ErrorAction SilentlyContinue)", - "Bash(cp 0705_vision2.json 0705_vision2.json.bak_20260703_before_previewonly)", - "PowerShell(Stop-Process -Id 31768 -Force)", - "PowerShell(Get-Process -Id 31768 -ErrorAction SilentlyContinue)", - "Bash(curl -s http://127.0.0.1:8188/object_info/DolphinVisionDirector --max-time 5 -o C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/dvd_objinfo.json)", - "Bash(cp 0705_vision2.json 0705_vision2.json.bak_20260703_before_trimwidgets)", - "Bash(curl -s \"http://127.0.0.1:8188/history?max_items=5\" --max-time 5)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/WanNativeResize_Dolphin\" --max-time 5)", - "Bash(./python.exe D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/build.py --input C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fa088da8-83db-496b-ae2c-8203c1b258d5/scratchpad/clips_test/raw --output C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fa088da8-83db-496b-ae2c-8203c1b258d5/scratchpad/clips_test/out --trigger test --captioner wd14 --clips-per-video 5)", - "Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\('registered:', bool\\(d\\)\\)\")", - "Bash(cp 0705_vision2.json lora_test.json)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/LoraLoaderModelOnly\" --max-time 5)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/ShowText%7Cpysssss\" --max-time 5)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/WanNativeResize_Dolphin\" --max-time 5 -o /tmp_resize.json)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/CLIPTextEncode\" --max-time 5)", - "Bash(python -m py_compile dolphin_prompt_director.py __init__.py)", - "Bash(tasklist)", - "Bash(python -m py_compile disk_cache_provider.py __init__.py)", - "Bash(curl -s http://127.0.0.1:8188/queue)", - "Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\('running:', len\\(d['queue_running']\\), 'pending:', len\\(d['queue_pending']\\)\\)\")", - "Bash(sort -k5 -n -r)", - "Bash(python -m py_compile disk_cache_provider.py)", - "Bash(python -m py_compile dolphin_prompt_director.py dolphin_vision_director.py __init__.py)", - "Bash(python \"/c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/rewire_perclip.py\")", - "Bash(python \"/c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/rewire_perclip_fix.py\")", - "Bash(python \"/c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/rewire_perclip_step5.py\")", - "Bash(python \"/c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/rewire_perclip_layout.py\")", - "Bash(python \"/c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fcfc6473-30fd-4447-95fe-3e3e03867e77/scratchpad/fix_modelsampling_nag.py\")", - "Bash(python -m json.tool)", - "Bash(python -m py_compile arch_logic_v16.py)", - "Bash(python -c *)", - "Bash(../../python_embeded/python.exe -c ' *)", - "mcp__claude-in-chrome__get_page_text", - "WebFetch(domain:aidispo.xyz)", - "Bash(curl -sS -o /dev/null -w \"HTTP:%{http_code} TIME:%{time_total}s\\\\n\" --max-time 15 https://aidispo.xyz/)", - "Bash(nslookup aidispo.xyz)", - "Bash(nslookup aidispo.xyz 8.8.8.8)", - "Bash(nslookup aidispo.xyz 1.1.1.1)", - "Bash(nslookup -type=NS aidispo.xyz)", - "Bash(python -m py_compile lora_classifier.py)", - "Bash(python lora_classifier.py)", - "Bash(PYTHONIOENCODING=utf-8 python lora_classifier.py)", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/553f71b1-eee1-4b6b-b29d-ae7b91fee046/scratchpad/convert_lanpaint.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/553f71b1-eee1-4b6b-b29d-ae7b91fee046/scratchpad/convert_lanpaint_live.py\")", - "Bash(gh issue *)", - "Bash(py --version)", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\inspect_wf.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\inspect_wf2.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\inspect_wf3.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\build_slowmo_workflows.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\validate_wf.py\")", - "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://127.0.0.1:8188/ --max-time 3)", - "WebFetch(domain:www.apatero.com)", - "Bash(gh api *)", - "WebFetch(domain:api.github.com)", - "Bash(curl -sL \"https://raw.githubusercontent.com/vita-epfl/Stable-Video-Infinity/svi_wan22/comfyui_workflow/SVI-Wan22-1210-4-Clips.json\" -o \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a/scratchpad/svi_wan22_4clips.json\" -w \"\\\\nHTTP %{http_code} size=%{size_download}\\\\n\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\inspect_svi_wf.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\inspect_svi_wf2.py\")", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\build_svi_scenario_a.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\build_svi_scenario_a_2clip.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\swap_svipro_gray.py\")", - "Bash(python -m http.server 8799 --bind 127.0.0.1)", - "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"http://127.0.0.1:8799/0705_vision3_svigray_test.json\")", - "Bash(netstat -ano)", - "Bash(taskkill //F //PID 41392)", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\cors_server.py\")", - "Bash(curl -s -D - -o /dev/null \"http://127.0.0.1:8799/0705_vision3_svigray_test.json\")", - "Bash(awk '{print $5}')", - "Bash(taskkill //F //PID 35940)", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/WanVideoNAG\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3cd796ee-7ccd-4afc-a4b7-afbcf94f5e7a\\\\scratchpad\\\\build_svi_simple_nag.py\")", - "Bash(curl -s \"http://127.0.0.1:8188/object_info/TripleKSamplerWan22LightningAdvanced\")", - "Bash(curl -sL -o i2v_native.json \"https://huggingface.co/lightx2v/Wan2.2-Lightning/resolve/main/Wan2.2-I2V-A14B-4steps-lora-rank64-Seko-V1/Wan2.2-I2V-A14B-4steps-lora-rank64-Seko-V1-NativeComfy.json\")", - "Bash(./python_embeded/python.exe \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/test_svipro_motion.py\")", - "Bash(PYTHONIOENCODING=utf-8 ./python_embeded/python.exe \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/test_svipro_motion.py\")", - "Bash(curl -s -m 5 http://127.0.0.1:8188/system_stats -o /dev/null -w \"%{http_code}\\\\n\")", - "Bash(curl -s -m 10 http://127.0.0.1:8188/object_info/WanImageToVideoSVIProMotion_Dolphin)", - "Bash(PYTHONIOENCODING=utf-8 ./python_embeded/python.exe -c ' *)", - "Bash(curl -s -m 15 http://127.0.0.1:8188/object_info/WanImageToVideoSVIProMotion_Dolphin)", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/make_svimotion_test.py\")", - "Bash(ls -d \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows\" 2>&1 && cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/0705_vision3_svimotion_test.json\" \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/__validate_svimotion.json\" && echo copied)", - "Bash(PYTHONIOENCODING=utf-8 python -)", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/patch_svi_test.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/wire_nag.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/fix_first_base_model.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/apply_all.py\")", - "Bash(curl -s -m 5 -X POST http://127.0.0.1:8188/manager/reboot -o /dev/null -w \"reboot endpoint: %{http_code}\\\\n\")", - "Bash(curl -s -m 10 http://127.0.0.1:8188/object_info/DolphinArchitectV16)", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/d23fdd5e-1ca3-4729-9900-2ec0a67708b1/scratchpad/migrate_architect_v16_widgets.py\")", - "Bash(command -v node)", - "Bash(node --check \"D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/web/dolphin_lora_rows.js\")", - "Bash(curl -s -m 10 http://127.0.0.1:8188/object_info/TripleKSamplerWan22LightningAdvanced)", - "Bash(curl -s -m 10 http://127.0.0.1:8188/object_info/ImageBatchExtendWithOverlap)", - "Bash(tasklist /FI \"IMAGENAME eq python.exe\" /FO CSV /V)", - "Bash(xargs -n1 basename)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0730.json\" \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0730.bak_20260730_1_before_seedcontroller.json\" && ls -la \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/\" | grep SVI_0730)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/validate_seed_controller.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/validate_seed_controller.py\")", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/make_draft_workflow.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/validate_independent.py\")", - "Bash(git status *)", - "Bash(git mv *)", - "Bash(mv lora_classifier.py _archive/nodes_unregistered/)", - "Bash(mv \"0705_vision2.json.bak_20260703_before_previewonly\" _archive/workflows/)", - "Bash(mv \"0705_vision2.json.bak_20260703_before_trimwidgets\" _archive/workflows/)", - "Bash(mv \"0705_vision2.json.bak_20260724_1316_before_perclip_refactor\" _archive/workflows/)", - "Bash(mv \"0705_vision2.json.bak_20260724_before_fixseed\" _archive/workflows/)", - "Bash(mv \"0705_vision3_svigray_test.json\" _archive/workflows/)", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/migrate_visiondirector_widgets.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/revert_visiondirector_widgets.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/make_0730C_3step.py\")", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0730B.json\" \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0730D.json\" *)", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/make_0730D_3sampler.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/validate_0730D.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/swap_extend_svipro_motion.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/validate_0730C_swap.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/add_colormatch.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/edcc9f60-df16-4a24-a1cc-2f19a6b49251/scratchpad/validate_colormatch.py\")", - "Bash(python lora_profile.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/models/loras\")", - "Bash(python -m py_compile dolphin_vision_director.py lora_profile.py)", - "Bash(python -m py_compile dolphin_vision_director.py dolphin_prompt_director.py arch_logic_v16.py lora_profile.py)", - "Bash(python test_weave.py)", - "Bash(python -m py_compile dolphin_vision_director.py dolphin_prompt_director.py arch_logic_v16.py)", - "PowerShell(nvidia-smi --query-gpu=name,driver_version,memory.total,memory.used,power.limit,power.max_limit,power.default_limit,temperature.gpu,pcie.link.gen.current,pcie.link.gen.max --format=csv)", - "PowerShell(Get-WinEvent -FilterHashtable @{LogName='System'; StartTime=\\(Get-Date\\).AddDays\\(-14\\)} -ErrorAction SilentlyContinue | Where-Object { $_.Id -in 4101,41,6008,17,18,19,1001 -or $_.ProviderName -match 'WHEA|Display|nvlddmkm' } | Select-Object -First 40 TimeCreated,Id,ProviderName,@{n='Msg';e={\\($_.Message -split \"`n\"\\)[0]}} | Format-Table -AutoSize -Wrap)", - "PowerShell(Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='nvlddmkm'; StartTime=\\(Get-Date\\).AddDays\\(-30\\)} -ErrorAction SilentlyContinue | Group-Object Id | Select-Object Name,Count | Format-Table -AutoSize; \"---- burst dates ----\"; Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='nvlddmkm'; StartTime=\\(Get-Date\\).AddDays\\(-30\\)} -ErrorAction SilentlyContinue | Group-Object {$_.TimeCreated.ToString\\('yyyy-MM-dd HH:mm'\\)} | Select-Object Name,Count | Format-Table -AutoSize)", - "PowerShell($e = Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='nvlddmkm'; StartTime=\\(Get-Date\\).AddDays\\(-30\\)} -ErrorAction SilentlyContinue | Select-Object -First 3; foreach \\($x in $e\\) { \"=== Id=$\\($x.Id\\) $\\($x.TimeCreated\\) ===\"; \"MSG: \" + $x.Message; $b=$x.Properties | ForEach-Object { $_.Value }; \"RAWCOUNT: \" + $b.Count })", - "PowerShell(\"--- TDR settings ---\"; Get-ItemProperty 'HKLM:\\\\SYSTEM\\\\CurrentControlSet\\\\Control\\\\GraphicsDrivers' -ErrorAction SilentlyContinue | Select-Object TdrDelay,TdrDdiDelay,TdrLevel,TdrLimitTime,TdrLimitCount | Format-List; \"--- Kernel-Power 41 history \\(30d\\) ---\"; Get-WinEvent -FilterHashtable @{LogName='System'; Id=41; StartTime=\\(Get-Date\\).AddDays\\(-30\\)} -ErrorAction SilentlyContinue | Select-Object TimeCreated | Format-Table -AutoSize; \"--- WHEA ---\"; Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-WHEA-Logger'; StartTime=\\(Get-Date\\).AddDays\\(-30\\)} -ErrorAction SilentlyContinue | Select-Object -First 10 TimeCreated,Id | Format-Table -AutoSize)", - "PowerShell($p='C:\\\\Program Files \\(x86\\)\\\\MSI Afterburner'; if\\(Test-Path $p\\){ Get-ChildItem \"$p\\\\Profiles\" -ErrorAction SilentlyContinue | Select-Object Name,LastWriteTime | Format-Table -AutoSize; \"=== MSIAfterburner.cfg 주요 항목 ===\"; Get-Content \"$p\\\\MSIAfterburner.cfg\" -ErrorAction SilentlyContinue | Select-String -Pattern 'PowerLimit|CoreClkBoost|CurveEditor|StartupProfile|ApplyOverclockingAtStartup|VFCurve|MemClkBoost|VoltageLimit|ThermalLimit' | Select-Object -First 30 } else { \"Afterburner 기본 경로에 없음\" })", - "Bash(python test_weaver.py)", - "Bash(python -m py_compile __init__.py dolphin_lora_prompt.py lora_profile.py dolphin_vision_director.py dolphin_prompt_director.py arch_logic_v16.py)", - "PowerShell($p='C:\\\\Program Files \\(x86\\)\\\\MSI Afterburner\\\\MSIAfterburner.cfg'; $c=Get-Content -LiteralPath $p; $i=[array]::IndexOf\\($c,'[Profile1]'\\); if\\($i -ge 0\\){ \"=== [Profile1] ===\"; $c[$i..\\($i+8\\)] | Where-Object { $_ -match '^\\(PowerLimit|CoreClkBoost|MemClkBoost|CoreVoltageBoost|FanMode\\)=' } }; \"=== 현재 GPU 상태 ===\"; nvidia-smi --query-gpu=power.limit,power.default_limit,clocks.max.memory,clocks.current.memory,clocks.max.graphics,power.draw,utilization.gpu --format=csv)", - "PowerShell($p='C:\\\\Program Files \\(x86\\)\\\\MSI Afterburner\\\\Profiles\\\\VEN_10DE&DEV_2B85&SUBSYS_176119DA&REV_A1&BUS_43&DEV_0&FN_0.cfg'; \"=== 최종 수정: \" + \\(Get-Item -LiteralPath $p\\).LastWriteTime + \" ===\"; Get-Content -LiteralPath $p | Select-String -Pattern '^\\\\[|^PowerLimit=|^MemClkBoost=|^CoreClkBoost=|^CoreVoltageBoost=' | Select-Object -First 40)", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\cmp_lightx.py\" \"$s\\\\lightx.json\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\deep.py\" \"$s\\\\deep.json\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\deep2.py\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\seko.py\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\gguf_keys.py\" \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\models\\\\unet\\\\Wan2.2-I2V-A14B-HighNoise-Q8_0.gguf\" \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\models\\\\unet\\\\Wan2.2-I2V-A14B-LowNoise-Q8_0.gguf\")", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/bafbbba1-90c6-473d-adca-987f2886303d/scratchpad/test_energy.py\")", - "Bash(PYTHONIOENCODING=utf-8 python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/bafbbba1-90c6-473d-adca-987f2886303d/scratchpad/test_energy.py\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\schedule.py\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\inspect_wf.py\" 2>&1 | Select-Object -First 130)", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\inspect_wf.py\" 2>&1 | Select-String -Pattern 'SUBGRAPH|TripleKSampler|NAG|EmptyHunyuan|INTConstant|LoraLoader|widgets|IN ' | Select-Object -First 90)", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\split_trace.py\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\verify_beats.py\")", - "PowerShell($s=\"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\3550f829-5fde-43b7-b6dd-cb0068774acd\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"$s\\\\lora_matrix.py\")", - "PowerShell(try { $r = Invoke-WebRequest -Uri \"http://127.0.0.1:8188/system_stats\" -UseBasicParsing -TimeoutSec 5; $r.Content } catch { \"NOT RUNNING: $\\($_.Exception.Message\\)\" })", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\inspect_svi_lora.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\inspect2.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\make_variants.py\")", - "Bash(cd /c/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/c0afbfdf-4086-4bd7-97c2-9a00be688031/scratchpad && ls -la /d/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/painter10s.json /d/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0801.json)", - "Bash(python dump.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/painter10s.json\")", - "Bash(python dump.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0801.json\")", - "Bash(PYTHONIOENCODING=utf-8 python dump.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/painter10s.json\")", - "Bash(PYTHONIOENCODING=utf-8 python dump.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0801.json\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\motion_metric2.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\ab2_metrics.py\")", - "Bash(PYTHONIOENCODING=utf-8 sed -n '150,240p' nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '440,600p' nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '836,880p' nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '880,900p' nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 python dumpsub.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0801.json\")", - "Bash(echo \"--- subgraph diff rc=$? ---\")", - "Bash(echo \"--- toplevel diff rc=$? ---\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\inspect_0801.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\inspect_0801b.py\")", - "Bash(PYTHONIOENCODING=utf-8 python make_0801A.py)", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\sigma_sched.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\inspect_0801c.py\")", - "Bash(PYTHONIOENCODING=utf-8 python make_0801B.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '527,560p' dolphin_vision_director.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"seed\" dolphin_vision_director.py)", - "Read(//d/SD/ComfyUI_windows_portable/ComfyUI/temp/**)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"CACHE_DIR\" /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/disk_cache_provider.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '405,412p' dolphin_vision_director.py)", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\ab3_metrics.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\find_setnode.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\apply_steps8.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\state_and_flicker.py\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b07deb9e-9dc0-45e2-baa2-db286b327e37\\\\scratchpad\\\\nag_chain.py\")", - "Bash(PYTHONIOENCODING=utf-8 grep -rln \"PainterI2V\" --include=*.py .)", - "Bash(PYTHONIOENCODING=utf-8 grep -rn \"PainterI2V\\\\w*\" --include=*.py . -o)", - "Bash(PYTHONIOENCODING=utf-8 grep -oh \"\\\\\"type\\\\\": \\\\\"Painter[^\\\\\"]*\\\\\"\" *.json)", - "Bash(PYTHONIOENCODING=utf-8 grep -rloh \"PainterI2V_S\\\\|painteri2v_s\\\\|I2V_S\" *.json)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '1,150p' /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/ComfyUI-PainterI2Vadvanced/nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -rn \"NODE_CLASS_MAPPINGS\" -A 12 /d/SD/ComfyUI_windows_portable/ComfyUI/comfy_extras/nodes_context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class \\\\|node_id=\\\\|NODE_CLASS\\\\|display_name\\\\|def define_schema\\\\|Input\\(\" /d/SD/ComfyUI_windows_portable/ComfyUI/comfy_extras/nodes_context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 python dump.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/PainterI2V_S.json\")", - "Bash(PYTHONIOENCODING=utf-8 sed -n '40,75p' dolphin_prompt_director.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"RETURN_TYPES\\\\|RETURN_NAMES\\\\|num_clips\\\\|clip_count\\\\|10s\\\\|15s\" dolphin_prompt_director.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class StringConcatenate\\\\|node_id=\\\\\"String\" /d/SD/ComfyUI_windows_portable/ComfyUI/comfy_extras/nodes_string.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '39,64p' /d/SD/ComfyUI_windows_portable/ComfyUI/comfy_extras/nodes_string.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class ContextSchedules\" -A 12 /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class ContextFuseMethods\" -A 14 /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 python make_ctx.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class Create Hook\\\\|node_id=\\\\|display_name=\" /d/SD/ComfyUI_windows_portable/ComfyUI/comfy_extras/nodes_hooks.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"start_percent\\\\|class HookKeyframe\\\\b\" /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/hooks.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"split_conds_to_windows\\\\|region_index\\\\|def.*split_cond\" /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"cond_retain_index_list\\\\|retain_index\\\\|c_concat\\\\|concat_latent\\\\|def get_resized_cond\\\\|sub_idxs\" /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '66,90p' /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '505,525p' /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '113,132p' /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '40,110p' /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/Comfyui-PainterFLF2V/nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 python restore_flf.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class PrimitiveBoolean\" -A 20 /d/SD/ComfyUI_windows_portable/ComfyUI/comfy_extras/nodes_primitive.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '129,200p' /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/ComfyUI-Crystools/nodes/switch.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"node_id=\\\\|class \" /d/SD/ComfyUI_windows_portable/ComfyUI/comfy_extras/nodes_primitive.py)", - "Bash(PYTHONIOENCODING=utf-8 python elegant_switch.py)", - "Bash(PYTHONIOENCODING=utf-8 python audit_unused.py)", - "Bash(PYTHONIOENCODING=utf-8 python cleanup.py)", - "Bash(PYTHONIOENCODING=utf-8 python diff_effective.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"def common_ksampler\" -A 12 /d/SD/ComfyUI_windows_portable/ComfyUI/nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"vibe\\\\|pacing\\\\|PACING\\\\|beat\" dolphin_vision_director.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '366,392p' dolphin_vision_director.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '695,712p' dolphin_vision_director.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '820,835p' dolphin_vision_director.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"inject_triggers\\\\|trigger\" dolphin_prompt_director.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"def create_windows_uniform_standard\" -A 32 /d/SD/ComfyUI_windows_portable/ComfyUI/comfy/context_windows.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -rn \"class ImageFromBatch\" -A 22 /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/ComfyUI_essentials/image.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -rln \"ImageFromBatch+\" /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/*/[a-z]*.py)", - "Bash(PYTHONIOENCODING=utf-8 python cmp_svi.py \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_OK.json\" \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/SVI_0801C.json\")", - "Bash(PYTHONIOENCODING=utf-8 python make_0802ok.py)", - "Bash(PYTHONIOENCODING=utf-8 python fix_base_weights.py)", - "Bash(PYTHONIOENCODING=utf-8 python deep_cmp.py SVI_OK)", - "Bash(PYTHONIOENCODING=utf-8 python deep_cmp.py SVI_0802_OK)", - "Bash(PYTHONIOENCODING=utf-8 grep -rn \"WanImageToVideoSVIProMotion_Dolphin\" __init__.py *.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"RETURN_NAMES\\\\|RETURN_TYPES\\\\|motion_amplitude\\\\|amplitude_mode\\\\|def encode\\\\|if motion_amplitude\\\\|latents_mean\\\\|process_out\\\\|return \\(\" wan_svipro_motion.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"_amplify\" -A 14 wan_svipro_motion.py)", - "Bash(PYTHONIOENCODING=utf-8 python parse_meta.py meta_ok.txt)", - "Bash(PYTHONIOENCODING=utf-8 python pin_prompt.py)", - "Bash(PYTHONIOENCODING=utf-8 python make_10s_smooth.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -rn \"class ImageBatchExtendWithOverlap\" -A 40 /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/*/*.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -rln \"ImageBatchExtendWithOverlap\" --include=*.py .)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class ImageBatchExtendWithOverlap\" -A 60 /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/comfyui-kjnodes/nodes/image_nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 sed -n '2156,2215p' /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/comfyui-kjnodes/nodes/image_nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 grep -n \"class ColorMatchV2\" -A 30 /d/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/comfyui-kjnodes/nodes/image_nodes.py)", - "Bash(PYTHONIOENCODING=utf-8 python smooth_v2.py)", - "Bash(mkdir -p C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fa088da8-83db-496b-ae2c-8203c1b258d5/scratchpad/dircheck/raw)", - "Bash(./python.exe D:/SD/ComfyUI_windows_portable/ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/build.py --input C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fa088da8-83db-496b-ae2c-8203c1b258d5/scratchpad/dircheck/raw --output C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/fa088da8-83db-496b-ae2c-8203c1b258d5/scratchpad/dircheck/out --trigger test --captioner wd14)", - "PowerShell(Get-CimInstance Win32_Process -Filter \"Name='python.exe' or Name='accelerate.exe'\")", - "Bash(nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu --format=csv)", - "PowerShell(Get-CimInstance Win32_Process -Filter \"Name='python.exe'\")", - "Bash(tasklist //FI \"PID eq 40764\")", - "PowerShell(Get-NetTCPConnection -LocalPort 7861 -State Listen -ErrorAction SilentlyContinue)", - "Bash(netstat -ano -p tcp)", - "Bash(taskkill //F //T //PID 48048)", - "Bash(taskkill //F //T //PID 47888)", - "Bash(tasklist //FI \"PID eq 47888\" //FI \"PID eq 49728\" //FI \"PID eq 21336\" //FI \"PID eq 30148\" //FI \"PID eq 32348\")", - "PowerShell(Get-ChildItem \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\output\" -Force | Where-Object { $_.LinkType } | Select-Object Name, LinkType, @{n='Target';e={$_.Target}} | Format-List)", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" -c \"import os;d=r'D:\\\\SD\\\\output';t=os.path.join\\(d,'I2V','RAW'\\);print\\('realpath dir :',os.path.realpath\\(d\\)\\);print\\('realpath targ:',os.path.realpath\\(t\\)\\);print\\('common:',os.path.commonpath\\(\\(os.path.realpath\\(d\\),os.path.realpath\\(t\\)\\)\\)\\)\")", - "PowerShell(git -C \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\" log -5 --format=\"%h %ad %s\" --date=short -- folder_paths.py)", - "PowerShell(git -C \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\" log -S \"def is_within_directory\" --format=\"%h %ad %s\" --date=short -- folder_paths.py)", - "PowerShell(git -C \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\" log -1 --format=\"%h %ad %s\" --date=iso)", - "PowerShell(\"=== when was is_within_directory introduced ===\")", - "PowerShell(\"=== HEAD ===\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" -s -c \"import sys; sys.path.insert\\(0, r'D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI'\\); import folder_paths; folder_paths.set_output_directory\\(r'D:\\\\sd\\\\output'\\); print\\(folder_paths.get_save_image_path\\('I2V/RAW/test', folder_paths.get_output_directory\\(\\), 1280, 720\\)\\)\")", - "PowerShell(\"=== dolphin_nodes tools dir? ===\")", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\tools\\\\reapply_symlink_output_patch.ps1\"; \"exit=$LASTEXITCODE\")", - "Bash(mkdir -p /d/sd/models/loras/Krea2)", - "Bash(curl -L --fail -o \"/d/sd/models/loras/Krea2/krea2_identity_edit_v1_2.safetensors.part\" \"https://huggingface.co/conradlocke/krea2-identity-edit/resolve/main/krea2_identity_edit_v1_2.safetensors\" -w \"\\\\nHTTP %{http_code} | %{size_download} bytes | %{speed_download} B/s\\\\n\" -s --show-error)", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_minimax.py\")", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_minimax.py\" 2>&1 | Select-String -Pattern '^\\(ok|FAIL|ALL|SOME|---|\\\\[|At |She |Yuna|dusk|fallback\\)')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_reg.py\" 2>&1 | Select-String -Pattern 'registered|widgets|missing|extra|RETURN|Minimax')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_minimax.py\" 2>&1 | Select-String -Pattern '^\\(ok |FAIL| got| want|ALL|SOME|20s fallback|⚠\\)')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_minimax.py\" 2>&1 | Select-String -Pattern '^\\(FAIL| got| want|ALL|SOME|20s fallback|fallback:\\)')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_reg.py\" 2>&1 | Select-String -Pattern 'registered|widgets|missing|extra|RETURN|Minimax 로드')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_e2e.py\" 2>&1 | Select-Object -Skip 2)", - "Bash(nvidia-smi --query-gpu=name,memory.total,memory.used,memory.reserved,utilization.gpu,power.draw,clocks.sm --format=csv)", - "Bash(nvidia-smi --query-compute-apps=pid,used_memory,name --format=csv)", - "PowerShell(Get-PhysicalDisk | Select-Object DeviceId,FriendlyName,MediaType,BusType,@{n='SizeGB';e={[math]::Round\\($_.Size/1GB\\)}} | Format-Table -AutoSize; Get-Partition | Where-Object DriveLetter -in 'C','D' | Select-Object DriveLetter,DiskNumber; Get-CimInstance Win32_PageFileUsage | Select-Object Name,AllocatedBaseSize,CurrentUsage)", - "PowerShell($os = Get-CimInstance Win32_OperatingSystem; $cs = Get-CimInstance Win32_ComputerSystem; [PSCustomObject]@{ AutoManagedPagefile=$cs.AutomaticManagedPagefile; TotalRAM_GB=[math]::Round\\($cs.TotalPhysicalMemory/1GB,1\\); FreeRAM_GB=[math]::Round\\($os.FreePhysicalMemory/1MB,1\\); CommitLimit_GB=[math]::Round\\($os.TotalVirtualMemorySize/1MB,1\\); CommitUsed_GB=[math]::Round\\(\\($os.TotalVirtualMemorySize-$os.FreeVirtualMemory\\)/1MB,1\\) } | Format-List; Get-CimInstance Win32_PageFileSetting | Select-Object Name,InitialSize,MaximumSize)", - "Bash(PYTHONIOENCODING=utf-8 ../python_embeded/python.exe -c ' *)", - "WebFetch(domain:docs.ltx.io)", - "Bash(/d/SD/ComfyUI_windows_portable/python_embeded/python.exe -X utf8 -c ' *)", - "Bash(grep -n \"relative_path\\\\|os.sep\\\\|replace\\(\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"\" /d/SD/ComfyUI_windows_portable/ComfyUI/folder_paths.py)", - "PowerShell(& \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b860b1fe-4dc7-4bed-a239-3b1ec6dd53e9\\\\scratchpad\\\\scan.ps1\")", - "PowerShell(& \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b860b1fe-4dc7-4bed-a239-3b1ec6dd53e9\\\\scratchpad\\\\scan2.ps1\")", - "PowerShell($p=\"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\.claude\"; if \\(Test-Path $p\\) { Get-ChildItem $p -Recurse -File | Select-Object FullName } else { \"no .claude dir\" }; \"--- user settings ---\"; if \\(Test-Path \"$env:USERPROFILE\\\\.claude\\\\settings.json\"\\) { Get-Content \"$env:USERPROFILE\\\\.claude\\\\settings.json\" -Raw } else { \"none\" })", - "Bash(awk '{print $1}')", - "Bash(git --no-pager log --stat -1)", - "Bash(git --no-pager log --oneline -3)", - "WebFetch(domain:kingy.ai)", - "WebFetch(domain:blog.comfy.org)", - "Bash(wmic ComputerSystem get TotalPhysicalMemory)", - "WebFetch(domain:www.kombitz.com)", - "WebFetch(domain:www.videotoprompt.app)", - "WebFetch(domain:mixio.studio)", - "WebFetch(domain:www.photogptai.com)", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; Set-Location \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" fix.py)", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_minimax.py\" 2>&1 | Select-String -Pattern '^\\(FAIL| got| want|ALL|SOME\\)')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_e2e.py\" 2>&1 | Select-Object -Skip 30)", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_reg.py\" 2>&1 | Select-String -Pattern 'registered|widgets|missing|extra|RETURN')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; Set-Location \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" patch2.py)", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_minimax.py\" 2>&1 | Select-String -Pattern '^\\(FAIL| got| want|ALL|SOME|Yuna, a woman\\)')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_e2e.py\" 2>&1 | Select-String -Pattern '^\\(\\\\[|Yuna|First|=====|>>>|⚠|AssertionError|Traceback\\)' -Context 0,1)", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_minimax.py\" 2>&1 | Select-String -Pattern '^\\(FAIL| got| want|ALL|SOME\\)'; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_e2e.py\" 2>&1 | Select-String -Pattern '^\\(>>>|AssertionError|For roughly|Yuna\\\\. For\\)')", - "PowerShell($env:PYTHONIOENCODING=\"utf-8\"; & \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\python_embeded\\\\python.exe\" \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\b67454ac-dcc9-436c-a752-89e8afa7fd38\\\\scratchpad\\\\test_reg.py\" 2>&1 | Select-String -Pattern 'registered|widgets|missing|extra')", - "Bash(mkdir -p D:/SD/ComfyUI_windows_portable/ComfyUI/models/loras)", - "Bash(curl -L -o minimax_h3_turbo_4step_ema_ckpt850_pruned_comfyui.safetensors.part \"https://huggingface.co/drbaph/MiniMax-H3-Turbo-Lora-ComfyUI/resolve/main/minimax_h3_turbo_4step_ema_ckpt850_pruned_comfyui.safetensors\" --progress-bar)", - "Bash(python_embeded/python.exe \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/bcff1cb2-b893-4caa-b23f-9593c85aa7f9/scratchpad/build_h3_extend.py\")", - "Bash(sed -i '22s/다섯 가지/SVI\\\\/COMP\\\\/ANI\\\\/COMB\\\\/GROK\\\\/LTX\\\\/H3/' rename.py)", - "Bash(command -v python3.exe)", - "Bash(\"/d/SD/ComfyUI_windows_portable/python_embeded/python.exe\" -m py_compile rename.py)", - "WebFetch(domain:sonusahani.com)", - "WebFetch(domain:x.com)", - "Bash(gh repo *)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/H3_I2V_turbo_2.json\" \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/H3_I2V_turbo_2.json.bak\" *)", - "WebFetch(domain:jo-nike.github.io)", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/4eaf8adb-112d-4cf0-973e-596238ffa388/scratchpad/integrate_motion_context.py\")", - "Bash(python \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/4eaf8adb-112d-4cf0-973e-596238ffa388/scratchpad/validate_workflow.py\")", - "Bash(curl -s \"https://api.github.com/search/code?q=video_wan_animate2+repo:Comfy-Org/workflow_templates\")", - "Bash(curl -s \"https://api.github.com/repos/Comfy-Org/workflow_templates/git/trees/main?recursive=1\")", - "Bash(curl -s \"https://raw.githubusercontent.com/Comfy-Org/workflow_templates/main/templates/video_wan_animate2.json\" -o \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/6375561a-517b-4f36-9a22-cda969f4d956/scratchpad/video_wan_animate2.json\")", - "Bash(py -3 -c \"print\\('ok'\\)\")", - "Bash(py -3 -c ' *)", - "Bash(./python_embeded/python.exe -s -m pip install -r ComfyUI/requirements.txt)", - "Bash(cp \"C:/Users/bjoo/AppData/Local/Temp/claude/D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes/6375561a-517b-4f36-9a22-cda969f4d956/scratchpad/video_wan_animate2.json\" \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/default/workflows/video_wan_animate2.json\" && echo copied)", - "Bash(curl -sI \"https://huggingface.co/Comfy-Org/Wan-Animate-2/resolve/main/diffusion_models/wan_animate_2_int8_convrot.safetensors\")", - "Bash(curl -sI \"https://huggingface.co/Comfy-Org/Wan-Animate-2/resolve/main/loras/lightx2v_I2V_14B_480p_cfg_step_distill_rank64_bf16.safetensors\")", - "Bash(curl -sIL \"https://huggingface.co/Comfy-Org/Wan-Animate-2/resolve/main/diffusion_models/wan_animate_2_int8_convrot.safetensors\")", - "Bash(curl -sIL \"https://huggingface.co/Comfy-Org/Wan-Animate-2/resolve/main/loras/lightx2v_I2V_14B_480p_cfg_step_distill_rank64_bf16.safetensors\")", - "Bash(curl -sL \"https://raw.githubusercontent.com/Comfy-Org/workflow_templates/refs/heads/main/input/pink_hair_mech_arms_ref.png\" -o pink_hair_mech_arms_ref.png)", - "Bash(curl -sL \"https://raw.githubusercontent.com/Comfy-Org/workflow_templates/refs/heads/main/input/street_dance_drive.mp4\" -o street_dance_drive.mp4)", - "Bash(curl -L \"https://huggingface.co/Comfy-Org/Wan-Animate-2/resolve/main/loras/lightx2v_I2V_14B_480p_cfg_step_distill_rank64_bf16.safetensors\" -o lightx2v_I2V_14B_480p_cfg_step_distill_rank64_bf16.safetensors --progress-bar)", - "Bash(curl -s http://127.0.0.1:8188/history?max_items=3 -o /tmp/history.json)", - "Bash(curl -sv http://127.0.0.1:8188/system_stats)", - "Bash(curl -s http://127.0.0.1:8188/history?max_items=5 -o 'C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\d26bed8e-b31a-41a3-8a79-051cab0eea70\\\\scratchpad/history.json')", - "Bash(curl -s http://127.0.0.1:8188/history?max_items=50 -o 'C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\d26bed8e-b31a-41a3-8a79-051cab0eea70\\\\scratchpad/history_full.json')", - "Bash(curl -s http://127.0.0.1:8188/queue -o 'C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\d26bed8e-b31a-41a3-8a79-051cab0eea70\\\\scratchpad/queue.json')", - "Bash(python -m py_compile dolphin_minimax_director.py)", - "Bash(python -m py_compile dolphin_vision_director.py dolphin_minimax_director.py dolphin_prompt_director.py)", - "Bash(python3)", - "Bash(cat)", - "Bash(python /tmp/test_split.py)", - "Bash(export PYTHONIOENCODING=utf-8)", - "Bash(find / -iname \"*nsfw*mmaudio*\" -o -iname \"*mmaudio*nsfw*\" 2>/dev/null | head -50)", - "Bash(find / -ipath \"*mmaudio*\" 2>/dev/null | grep -v \"custom_nodes/.*\\\\.py\" | head -100)", - "Bash(kill %1 %2)", - "Bash(jobs -l)", - "WebFetch(domain:comfyai.run)", - "Bash(npm --version)", - "WebFetch(domain:opencof.github.io)", - "WebFetch(domain:www.patreon.com)", - "Bash(gh auth *)", - "PowerShell(winget list --id GitHub.cli 2>&1)", - "PowerShell(winget install --id GitHub.cli -e --accept-source-agreements --accept-package-agreements)", - "Bash(curl -s -m 3 http://127.0.0.1:8188/ -o /dev/null -w \"%{http_code}\\\\n\")", - "Bash(\"/c/SD/ComfyUI_windows_portable/python_embeded/python.exe\" cm-cli.py --help)", - "Bash(\"/d/SD/ComfyUI_windows_portable/python_embeded/python.exe\" cm-cli.py --help)", - "Bash(\"/d/SD/ComfyUI_windows_portable/python_embeded/python.exe\" cm-cli.py save-snapshot)", - "Bash(python3 -c \"import json;d=json.load\\(open\\('2026-08-12_12-32-11_snapshot.json'\\)\\);print\\(list\\(d.keys\\(\\)\\)\\);print\\('git_custom_nodes count:',len\\(d.get\\('git_custom_nodes',{}\\)\\)\\)\")", - "Bash(\"/d/SD/ComfyUI_windows_portable/python_embeded/python.exe\" -c \"import json;d=json.load\\(open\\('2026-08-12_12-32-11_snapshot.json'\\)\\);print\\(list\\(d.keys\\(\\)\\)\\);print\\('git_custom_nodes count:',len\\(d.get\\('git_custom_nodes',{}\\)\\)\\)\")", - "Bash(/d/SD/ComfyUI_windows_portable/python_embeded/python.exe -c ' *)", - "Bash(mkdir -p cloud_env)", - "Bash(cp \"D:/SD/ComfyUI_windows_portable/ComfyUI/user/__manager/snapshots/2026-08-12_12-32-11_snapshot.json\" cloud_env/comfyui_env_snapshot.json)", - "Bash(git push *)", - "Bash(where ffmpeg *)", - "Bash(ffmpeg -version)", - "Bash(npm install *)", - "PowerShell(\\(Get-Content 'D:\\\\orga.py' -Raw\\) -replace [regex]::Escape\\(\" shutil.move\\(src, dst\\)`r`n return True, None\"\\), \" shutil.move\\(src, dst\\)`r`n return True, dst\" | Set-Content 'D:\\\\orga.py' -NoNewline)", - "Bash(python -m py_compile \"/d/orga.py\")", - "Bash(ls -la /d/ 2>&1 | head -5; echo \"---\"; ls -la \"D:/orga.py\" 2>&1)", - "Bash(python -m py_compile \"D:/orga.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga2.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga4.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga5.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga6.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga7.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga8.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga9.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga11.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga12.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga13.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga14.py\")", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga15.py\")", - "Bash(python3 -)", - "Bash(python -m py_compile \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\d2b174de-a935-400d-9712-b631f10d35b9\\\\scratchpad\\\\dedupe_folders_gui.py\")", - "Bash(python -m py_compile \"D:\\\\dedupe_folders.py\" \"D:\\\\dedupe_folders_gui.py\")", - "Bash(awk '{print $5\"MB\", $NF}')", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\tools\\\\reapply_symlink_output_patch.ps1\" -Verify)", - "PowerShell(& \"D:\\\\SD\\\\ComfyUI_windows_portable\\\\ComfyUI\\\\custom_nodes\\\\dolphin_nodes\\\\tools\\\\reapply_symlink_output_patch.ps1\")", - "Bash(./yt-dlp.exe --version)", - "Bash(./yt-dlp.exe -U)", - "Bash(./yt-dlp.exe -f bestaudio/best --no-playlist -o \"test_%\\(title\\)s.%\\(ext\\)s\" \"https://www.youtube.com/watch?v=bMhDJ0S0OBA\")", - "Bash(\"/c/Users/bjoo/anaconda3/pkgs/ffmpeg-6.1.1-hc79a5da_3/Library/bin/ffmpeg.exe\" -version)", - "PowerShell(& \"C:\\\\Users\\\\bjoo\\\\anaconda3\\\\pkgs\\\\ffmpeg-6.1.1-hc79a5da_3\\\\Library\\\\bin\\\\ffmpeg.exe\" -version 2>&1)", - "PowerShell(& \"C:\\\\Program Files\\\\Krita \\(x64\\)\\\\bin\\\\ffmpeg.exe\" -version 2>&1)", - "Bash(ls \"/c/Program Files/Krita \\(x64\\)/bin/\" | grep -i \"ffprobe\\\\|ffmpeg\")", - "Bash(rm -f \"ILLIT\"*.webm \"ILLIT\"*.mp3)", - "Bash(./yt-dlp.exe -o \"verify_test.%\\(ext\\)s\" --ffmpeg-location \"C:\\\\Program Files\\\\Krita \\(x64\\)\\\\bin\" -x --audio-format mp3 -f bestaudio/best --no-playlist \"https://www.youtube.com/watch?v=bMhDJ0S0OBA\")", - "Bash(./yt-dlp.exe -o \"%\\(title\\)s.%\\(ext\\)s\" --ffmpeg-location \"C:\\\\Program Files\\\\Krita \\(x64\\)\\\\bin\" -x --audio-format mp3 -f bestaudio/best --no-playlist --force-overwrites -v \"https://www.youtube.com/watch?v=bMhDJ0S0OBA\")", - "PowerShell([System.Text.Encoding]::Default.EncodingName; chcp)", - "Bash(sort -k6,7)", - "WebFetch(domain:openrouter.ai)", - "Bash(python -m py_compile dolphin_vision_director.py dolphin_minimax_director.py)", - "Bash(node --check web/dolphin_preset_sync.js)", - "Bash(python \"C:\\\\Users\\\\bjoo\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\D--SD-ComfyUI-windows-portable-ComfyUI-custom-nodes-dolphin-nodes\\\\0a101ba9-4e7e-41e4-b61a-533bc9170100\\\\scratchpad\\\\patch_orga17.py\")" - ], - "defaultMode": "bypassPermissions" - } -} diff --git a/__init__.py b/__init__.py deleted file mode 100644 index f53e6b63dbd483145dc89bb537089bc3e4a19d49..0000000000000000000000000000000000000000 --- a/__init__.py +++ /dev/null @@ -1,227 +0,0 @@ -import traceback - -NODE_CLASS_MAPPINGS = {} -NODE_DISPLAY_NAME_MAPPINGS = {} -WEB_DIRECTORY = "./web" - - -def register_node(class_name, node_class, display_name): - NODE_CLASS_MAPPINGS[class_name] = node_class - NODE_DISPLAY_NAME_MAPPINGS[class_name] = display_name - - -# 1. Wan Native Resize (이미지 -> WAN 해상도 리사이즈, 비율 유지 + 정렬) -try: - from .wan_resizer import WanNativeResize_Dolphin - register_node("WanNativeResize_Dolphin", WanNativeResize_Dolphin, - "🐬 Wan Native Resize (Dolphin)") -except Exception: - print(f"\n❌ [Dolphin] Wan Resize 로드 실패:\n{traceback.format_exc()}") - -# 2. Story Splitter V32 (텍스트 서사 -> 4샷 프롬프트 분배) -try: - from .promp_logic_v32_story import DolphinMultiActionPromptNode_V32 - register_node("DolphinMultiActionPromptNode_V32", DolphinMultiActionPromptNode_V32, - "🐬 Dolphin Story Splitter V32") -except Exception: - print(f"\n❌ [Dolphin] V32 Story Splitter 로드 실패:\n{traceback.format_exc()}") - -# 3. Architect V16 (클립별 LoRA -> TripleKSampler, model-only) -try: - from .arch_logic_v16 import DolphinTripleLoraMatrix as DolphinArchitectV16 - register_node("DolphinArchitectV16", DolphinArchitectV16, - "🐬 Dolphin Architect V16") -except Exception: - print(f"\n❌ [Dolphin] Architect V16 로드 실패:\n{traceback.format_exc()}") - -# 4. Cinematic Director (올인원: 스토리 분배 + 클립별 로라 + 트리거 주입) -try: - from .dolphin_director import DolphinCinematicDirector - register_node("DolphinCinematicDirector", DolphinCinematicDirector, - "🎬 Dolphin Cinematic Director (All-in-One)") -except Exception: - print(f"\n❌ [Dolphin] Cinematic Director 로드 실패:\n{traceback.format_exc()}") - -# 5. Vision Director (이미지 인식 + 필수동작 + 클립당 로라 4개) -try: - from .dolphin_vision_director import DolphinVisionDirector - register_node("DolphinVisionDirector", DolphinVisionDirector, - "🎬👁 Dolphin Vision Director (Image→4Clips)") -except Exception: - print(f"\n❌ [Dolphin] Vision Director 로드 실패:\n{traceback.format_exc()}") - -# 6. Architect V15 (arch_logic_v16 이전 구버전, model_base/high/low + clip 개별 입력) -try: - from .arch_logic import DolphinArchitectV15 - register_node("DolphinArchitectV15", DolphinArchitectV15, - "🐬 Dolphin Architect V15 (legacy)") -except Exception: - print(f"\n❌ [Dolphin] Architect V15 로드 실패:\n{traceback.format_exc()}") - -# 7. Story Splitter V28 (V32 이전 구버전, stitch 방식) -try: - from .promp_logic_v28_stitch import DolphinMultiActionPromptNode_V28 - register_node("DolphinMultiActionPromptNode_V28", DolphinMultiActionPromptNode_V28, - "🐬 Dolphin Story Splitter V28 (legacy)") -except Exception: - print(f"\n❌ [Dolphin] V28 Story Splitter 로드 실패:\n{traceback.format_exc()}") - -# 8. Story Splitter V30 (V32 이전 구버전) -try: - from .promp_logic_v30_story import DolphinMultiActionPromptNode_V30 - register_node("DolphinMultiActionPromptNode_V30", DolphinMultiActionPromptNode_V30, - "🐬 Dolphin Story Splitter V30 (legacy)") -except Exception: - print(f"\n❌ [Dolphin] V30 Story Splitter 로드 실패:\n{traceback.format_exc()}") - -# 9. Prompt V32 Vision (promp_logic_v32_story.py의 DolphinMultiActionPromptNode_V32와 -# 클래스명이 동일해서 충돌 — 등록 키/클래스명을 _Vision으로 구분해서 둘 다 살림) -try: - from .promp_logic_v32_vision import DolphinMultiActionPromptNode_V32 as DolphinMultiActionPromptNode_V32_Vision - register_node("DolphinMultiActionPromptNode_V32_Vision", DolphinMultiActionPromptNode_V32_Vision, - "🐬 Dolphin Prompt V32 Vision (legacy)") -except Exception: - print(f"\n❌ [Dolphin] V32 Vision 로드 실패:\n{traceback.format_exc()}") - - -# 10. Match Image & Mask Size (인페인트 블렌드용: 이미지/마스크를 참조 이미지 실제 크기에 맞춤) -try: - from .match_size import MatchImageAndMaskSize_Dolphin - register_node("MatchImageAndMaskSize_Dolphin", MatchImageAndMaskSize_Dolphin, - "🐬 Match Image & Mask Size (Dolphin)") -except Exception: - print(f"\n❌ [Dolphin] Match Image & Mask Size 로드 실패:\n{traceback.format_exc()}") - - -# 11. Mask-Region Color Match (인페인트 마스크 영역만 국소적으로 색보정) -try: - from .mask_region_color_match import MaskRegionColorMatch_Dolphin - register_node("MaskRegionColorMatch_Dolphin", MaskRegionColorMatch_Dolphin, - "🐬 Mask-Region Color Match (Dolphin)") -except Exception: - print(f"\n❌ [Dolphin] Mask-Region Color Match 로드 실패:\n{traceback.format_exc()}") - - -# 12. Prompt Director (Vision Director와 동일한 비전+LLM 엔진, MODEL/LoRA 전부 제거 - -# 프롬프트 분배만. 로라/모델은 Architect V16 등 별도 노드에서 처리한다는 전제) -try: - from .dolphin_prompt_director import ( - DolphinPromptDirector, DolphinPromptDirectorSingle, DolphinActionSplitter, - ) - register_node("DolphinPromptDirector", DolphinPromptDirector, - "🎬📝 Dolphin Prompt Director (Image→N Prompts, no model/LoRA)") - register_node("DolphinPromptDirectorSingle", DolphinPromptDirectorSingle, - "🎬📝 Dolphin Prompt Director (Single, Image→1 Prompt)") - register_node("DolphinActionSplitter", DolphinActionSplitter, - "✂️ Dolphin Action Splitter (deterministic, no LLM)") -except Exception: - print(f"\n❌ [Dolphin] Prompt Director 로드 실패:\n{traceback.format_exc()}") - - -# 13. WanImageToVideoSVIPro mask 버그 수정판 (comfyui-kjnodes 원본은 motion_latent_count로 -# 이어붙인 모션 컨텍스트 프레임을 concat_mask에서 "known"으로 표시하지 않아서, 클립 -# 경계에서 몇 프레임 반복 후 전혀 다른 영상으로 튀는 문제가 있었음. anchor+motion 프레임 -# 전체를 known(0.0)으로 마스킹하도록 수정.) -try: - from .wan_svipro_fixed import WanImageToVideoSVIProFixed_Dolphin - register_node("WanImageToVideoSVIProFixed_Dolphin", WanImageToVideoSVIProFixed_Dolphin, - "🐬 Wan I2V SVIPro (motion-mask fix)") -except Exception: - print(f"\n❌ [Dolphin] SVIPro Fixed 로드 실패:\n{traceback.format_exc()}") - - -# 14. Wan I2V Continuity (코어 WanImageToVideo 방식 = known 프레임 여러 장을 회색 filler와 -# 함께 한 번에 통짜 VAE 인코딩 → latent 문맥 일관성 보장. SVIPro식 raw latent 스플라이싱의 -# 화질 튐/불연속 문제를 회피. PainterI2V의 motion_amplitude(슬로우모션 방지) 이식.) -try: - from .wan_i2v_continuity import WanI2VContinuity_Dolphin - register_node("WanI2VContinuity_Dolphin", WanI2VContinuity_Dolphin, - "🐬 Wan I2V Continuity (multi-frame + amplitude)") -except Exception: - print(f"\n❌ [Dolphin] Wan I2V Continuity 로드 실패:\n{traceback.format_exc()}") - - -# 15. SVIPro 드롭인 교체 (슬로우모션 수정). 원본은 filler를 latent 공간에서 -# Wan21().process_out(zeros) = latents_mean 상수로 채워서 "변화 없음" 신호를 주고 -# 분포 밖 입력이 됐음 -> 코어 WanImageToVideo/PainterI2V처럼 회색(0.5) 픽셀을 VAE -# 인코딩한 filler로 교체. 마스크 버그(anchor 1프레임만 known)도 함께 수정. -try: - from .wan_svipro_gray import WanImageToVideoSVIProGray_Dolphin - register_node("WanImageToVideoSVIProGray_Dolphin", WanImageToVideoSVIProGray_Dolphin, - "🐬 Wan I2V SVIPro (gray filler, slow-motion fix)") -except Exception: - print(f"\n❌ [Dolphin] SVIPro Gray 로드 실패:\n{traceback.format_exc()}") - - -# 16. SVIPro 드롭인 교체 (모션 증폭판). 15번(gray)과 달리 패딩·마스크는 상류 원본 그대로 두고 -# PainterI2V의 diff 증폭만 얹는다 — SVI Pro LoRA가 원본 규약 위에서 학습됐기 때문. -# high/low conditioning을 따로 뽑아서 모션 증폭은 high-noise 스테이지에만 먹인다. -# motion_amplitude=1.0이면 상류 원본과 출력이 동일(회귀 확인용). -try: - from .wan_svipro_motion import WanImageToVideoSVIProMotion_Dolphin - register_node("WanImageToVideoSVIProMotion_Dolphin", WanImageToVideoSVIProMotion_Dolphin, - "🐬 Wan I2V SVIPro (motion amplitude, hi/lo split)") -except Exception: - print(f"\n❌ [Dolphin] SVIPro Motion 로드 실패:\n{traceback.format_exc()}") - - -# 17. 디스크 노드 캐시 (노드가 아니라 ComfyUI cache provider 등록). -# 시드/프롬프트/이미지가 같으면 재시작 후에도 JoyCaption~샘플러 체인을 스킵하고 -# 디스크에서 결과 복원. RAM_PRESSURE 모드의 캐시 증발도 디스크에서 자동 복구. -try: - from . import disk_cache_provider - disk_cache_provider.register() -except Exception: - print(f"\n❌ [Dolphin] 디스크 노드 캐시 로드 실패 (구버전 ComfyUI엔 provider API 없음):\n{traceback.format_exc()}") - - -# 18. Batch Seed Controller (4클립 영상 샘플러 시드를 한 노드에서 스위칭: -# 고정/전체랜덤/특정클립만재생성). -try: - from .dolphin_seed_controller import DolphinBatchSeedController - register_node("DolphinBatchSeedController", DolphinBatchSeedController, - "🎲 Dolphin Batch Seed Controller (random-all / regen-from-clip)") -except Exception: - print(f"\n❌ [Dolphin] Batch Seed Controller 로드 실패:\n{traceback.format_exc()}") - - -# 19. LoRA Prompt Weaver (완성된 클립 프롬프트를 "그 클립에 걸리는 로라들이 잘 발화하도록" -# 다시 쓰는 후처리 노드. 로라 사이드카(.metadata.json)의 설명/태그/학습캡션을 LLM에 -# 먼저 주고, 트리거를 문장 안 해당 부위에 붙여서 재작성한다 - 예전처럼 이름 옆에 -# 콤마로 나열하면 bounce/swing 같은 범용 동사가 전신 모션으로 새기 때문. -# 기존 노드는 건드리지 않고 뒤에 끼워 넣는 방식이라 어떤 프롬프트 소스에도 붙는다.) -try: - from .dolphin_lora_prompt import DolphinLoraPromptWeaver - register_node("DolphinLoraPromptWeaver", DolphinLoraPromptWeaver, - "🧬 Dolphin LoRA Prompt Weaver (프롬프트→로라에 맞게 재작성)") -except Exception: - print(f"\n❌ [Dolphin] LoRA Prompt Weaver 로드 실패:\n{traceback.format_exc()}") - - -# 20. Minimax Director (MiniMax/Hailuo용 프롬프트 디렉터. Wan용 Prompt Director와 달리 -# 클립 하나가 6/10초라서, 그 안을 초 단위 비트로 쪼개고 각 구간에 동작 + MiniMax -# 공식 카메라 무브 태그를 박아준다.) -try: - from .dolphin_minimax_director import DolphinMinimaxDirector - register_node("DolphinMinimaxDirector", DolphinMinimaxDirector, - "🎬🅜 Dolphin Minimax Director (초 단위 타임라인 → Hailuo 프롬프트)") -except Exception: - print(f"\n❌ [Dolphin] Minimax Director 로드 실패:\n{traceback.format_exc()}") - - -# 21. Wan Refine H3 (MiniMax H3 출력 → Wan 2.2 LOW noise 저-denoise 리파인 패스 올인원). -# H3(24fps / 17k+5 프레임 그리드 / 768 short edge)와 Wan(16fps / 4n+1 / Wan2.1 VAE 16ch) -# 사이의 배선 함정을 한 노드에서 처리: 4n+1 정렬, VAE 오배선 차단, 소스 라텐트 인코딩, -# concat 컨디셔닝 재구성, sigma_shift 직접 패치, 색 드리프트 복원, 오디오 길이 정렬. -# 같이 등록되는 Frame Grid 노드는 두 그리드가 겹치는 length(5/73/141/209/277/345…)를 계산한다. -try: - from .wan_h3_refine import WanH3Refine_Dolphin, WanH3FrameGrid_Dolphin - register_node("WanH3Refine_Dolphin", WanH3Refine_Dolphin, - "🐬🅜 Wan Refine H3 (low-denoise pass)") - register_node("WanH3FrameGrid_Dolphin", WanH3FrameGrid_Dolphin, - "🐬🅜 H3↔Wan Frame Grid") -except Exception: - print(f"\n❌ [Dolphin] Wan Refine H3 로드 실패:\n{traceback.format_exc()}") - - -__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] \ No newline at end of file diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 1d41f9c531d00fec67fb657f188ea1edf639c7c3..0000000000000000000000000000000000000000 Binary files a/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/__init__.cpython-313.pyc b/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 72139fc8622f955503d9890d9c2689c65e1e1a9a..0000000000000000000000000000000000000000 Binary files a/__pycache__/__init__.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/arch_logic.cpython-312.pyc b/__pycache__/arch_logic.cpython-312.pyc deleted file mode 100644 index 13a5071baed3fc9391dc9ebd79c9ba2c540555e3..0000000000000000000000000000000000000000 Binary files a/__pycache__/arch_logic.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/arch_logic.cpython-313.pyc b/__pycache__/arch_logic.cpython-313.pyc deleted file mode 100644 index ed56650b9727d8578aad28287e3e1f836768fbef..0000000000000000000000000000000000000000 Binary files a/__pycache__/arch_logic.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/arch_logic_v16.cpython-312.pyc b/__pycache__/arch_logic_v16.cpython-312.pyc deleted file mode 100644 index b851f76001c2269cf9318065357dd3df7780ec2a..0000000000000000000000000000000000000000 Binary files a/__pycache__/arch_logic_v16.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/arch_logic_v16.cpython-313.pyc b/__pycache__/arch_logic_v16.cpython-313.pyc deleted file mode 100644 index d81866f946e753c06adfd9ef558235492589cc4d..0000000000000000000000000000000000000000 Binary files a/__pycache__/arch_logic_v16.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/dedupe_folders.cpython-312.pyc b/__pycache__/dedupe_folders.cpython-312.pyc deleted file mode 100644 index 2395d464a77c40167cfa9e4c351bf9c41a4be15a..0000000000000000000000000000000000000000 Binary files a/__pycache__/dedupe_folders.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/dedupe_folders_gui.cpython-312.pyc b/__pycache__/dedupe_folders_gui.cpython-312.pyc deleted file mode 100644 index 0277dc5f972815b9a807728c916ea9bc7edd1e77..0000000000000000000000000000000000000000 Binary files a/__pycache__/dedupe_folders_gui.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/disk_cache_provider.cpython-312.pyc b/__pycache__/disk_cache_provider.cpython-312.pyc deleted file mode 100644 index 2df7a854aa4d05c937748d7933c0d722ba330716..0000000000000000000000000000000000000000 Binary files a/__pycache__/disk_cache_provider.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/disk_cache_provider.cpython-313.pyc b/__pycache__/disk_cache_provider.cpython-313.pyc deleted file mode 100644 index 880f86f378cff82ed80487e3d14f276ca85fcdfd..0000000000000000000000000000000000000000 Binary files a/__pycache__/disk_cache_provider.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/dolphin_director.cpython-313.pyc b/__pycache__/dolphin_director.cpython-313.pyc deleted file mode 100644 index 7ba14151ab3acd6bc0447d934d672ae1462fdc0e..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_director.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/dolphin_lora_prompt.cpython-312.pyc b/__pycache__/dolphin_lora_prompt.cpython-312.pyc deleted file mode 100644 index 4936e355ccf3dc3551a5687e1b1368781724e00f..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_lora_prompt.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/dolphin_lora_prompt.cpython-313.pyc b/__pycache__/dolphin_lora_prompt.cpython-313.pyc deleted file mode 100644 index ceb8bedc28a6cf3c310482a87f19a6ed408354fe..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_lora_prompt.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/dolphin_minimax_director.cpython-312.pyc b/__pycache__/dolphin_minimax_director.cpython-312.pyc deleted file mode 100644 index b5e5e8aa70606878f30536ad0d199b5946ebc219..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_minimax_director.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/dolphin_minimax_director.cpython-313.pyc b/__pycache__/dolphin_minimax_director.cpython-313.pyc deleted file mode 100644 index eeb3ded3abe07782bce95559c2b6e43ceca0e925..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_minimax_director.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/dolphin_prompt_director.cpython-312.pyc b/__pycache__/dolphin_prompt_director.cpython-312.pyc deleted file mode 100644 index 54bf8614c63fd3310b782c6da15ce93fe3873421..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_prompt_director.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/dolphin_prompt_director.cpython-313.pyc b/__pycache__/dolphin_prompt_director.cpython-313.pyc deleted file mode 100644 index acc8f055878aa3664174fcd93b3711ccc8c2ef7f..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_prompt_director.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/dolphin_seed_controller.cpython-312.pyc b/__pycache__/dolphin_seed_controller.cpython-312.pyc deleted file mode 100644 index a689f7f3f8388be64d5944db3fdee50ab0175e7b..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_seed_controller.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/dolphin_seed_controller.cpython-313.pyc b/__pycache__/dolphin_seed_controller.cpython-313.pyc deleted file mode 100644 index 037be033dfc1c574638dd2d5eebe2a9f66177a69..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_seed_controller.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/dolphin_vision_director.cpython-312.pyc b/__pycache__/dolphin_vision_director.cpython-312.pyc deleted file mode 100644 index f706a6061ea9a27cb0b979a74778f9fcb6275b32..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_vision_director.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/dolphin_vision_director.cpython-313.pyc b/__pycache__/dolphin_vision_director.cpython-313.pyc deleted file mode 100644 index aab0040bfc4651b10f664c5e579a7cbc713e2697..0000000000000000000000000000000000000000 Binary files a/__pycache__/dolphin_vision_director.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/lora_classifier.cpython-312.pyc b/__pycache__/lora_classifier.cpython-312.pyc deleted file mode 100644 index 4828425a49ad88c9831f897eca1eb0c182d1256a..0000000000000000000000000000000000000000 Binary files a/__pycache__/lora_classifier.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/lora_profile.cpython-312.pyc b/__pycache__/lora_profile.cpython-312.pyc deleted file mode 100644 index 35125f0da62e9ecfa618e193e7ed902b3f35fe4d..0000000000000000000000000000000000000000 Binary files a/__pycache__/lora_profile.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/lora_profile.cpython-313.pyc b/__pycache__/lora_profile.cpython-313.pyc deleted file mode 100644 index 6a91927de950629a8544b4a40b3fa5113a61de5e..0000000000000000000000000000000000000000 Binary files a/__pycache__/lora_profile.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/mask_region_color_match.cpython-313.pyc b/__pycache__/mask_region_color_match.cpython-313.pyc deleted file mode 100644 index a45b9b87a2b0f2a99f2211c76309af6419cac5c1..0000000000000000000000000000000000000000 Binary files a/__pycache__/mask_region_color_match.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/match_size.cpython-313.pyc b/__pycache__/match_size.cpython-313.pyc deleted file mode 100644 index c5596aba2ef2daed791ed6e69d9ca03cfb959861..0000000000000000000000000000000000000000 Binary files a/__pycache__/match_size.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/orga.cpython-312.pyc b/__pycache__/orga.cpython-312.pyc deleted file mode 100644 index af645e7dd6751da7620b21c2098f029c8390695b..0000000000000000000000000000000000000000 Binary files a/__pycache__/orga.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v28_stitch.cpython-312.pyc b/__pycache__/promp_logic_v28_stitch.cpython-312.pyc deleted file mode 100644 index f7126b21d38ecc5fd9520afaffb580f6ecf71679..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v28_stitch.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v28_stitch.cpython-313.pyc b/__pycache__/promp_logic_v28_stitch.cpython-313.pyc deleted file mode 100644 index 6686db9f77fe3f5c24e535e051a36cd005568595..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v28_stitch.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v30_story.cpython-312.pyc b/__pycache__/promp_logic_v30_story.cpython-312.pyc deleted file mode 100644 index 9c4fa1311375c349438618645dfda712b0916169..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v30_story.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v30_story.cpython-313.pyc b/__pycache__/promp_logic_v30_story.cpython-313.pyc deleted file mode 100644 index 62516ade31c1b87049de168887a68c9400fa35d4..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v30_story.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v32_story.cpython-312.pyc b/__pycache__/promp_logic_v32_story.cpython-312.pyc deleted file mode 100644 index cd6e0f54bc730db6cc34727d0d7c61497b07e7c3..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v32_story.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v32_story.cpython-313.pyc b/__pycache__/promp_logic_v32_story.cpython-313.pyc deleted file mode 100644 index 5a2afb9d6373ba57653d1ec0c7e792f9e1400d79..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v32_story.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v32_vision.cpython-312.pyc b/__pycache__/promp_logic_v32_vision.cpython-312.pyc deleted file mode 100644 index 66d58d5447d466944ffb1d13e81d453856cc1b04..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v32_vision.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/promp_logic_v32_vision.cpython-313.pyc b/__pycache__/promp_logic_v32_vision.cpython-313.pyc deleted file mode 100644 index 8e0017710b47fe0e996e6c373d95c1438252c6a6..0000000000000000000000000000000000000000 Binary files a/__pycache__/promp_logic_v32_vision.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/wan_h3_refine.cpython-313.pyc b/__pycache__/wan_h3_refine.cpython-313.pyc deleted file mode 100644 index 3f98bfa9db32a7e2a07dbe5627fe4e8436528821..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_h3_refine.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/wan_i2v_continuity.cpython-312.pyc b/__pycache__/wan_i2v_continuity.cpython-312.pyc deleted file mode 100644 index 39451ce970fde1ecc9294f739d41d1b2a9facb3b..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_i2v_continuity.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/wan_i2v_continuity.cpython-313.pyc b/__pycache__/wan_i2v_continuity.cpython-313.pyc deleted file mode 100644 index 9e28afdf55a2bb4efeb6aa7dcee3fcfd8d3f2564..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_i2v_continuity.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/wan_resizer.cpython-312.pyc b/__pycache__/wan_resizer.cpython-312.pyc deleted file mode 100644 index fedfb6776de74f9ba1842e609968fbd9c349157f..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_resizer.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/wan_resizer.cpython-313.pyc b/__pycache__/wan_resizer.cpython-313.pyc deleted file mode 100644 index 17292746cc3500285809bca1aa78c93b1814f9c3..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_resizer.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/wan_svipro_fixed.cpython-312.pyc b/__pycache__/wan_svipro_fixed.cpython-312.pyc deleted file mode 100644 index 70634542ece8a9c442bc2e32f64f2be68796b47a..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_svipro_fixed.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/wan_svipro_fixed.cpython-313.pyc b/__pycache__/wan_svipro_fixed.cpython-313.pyc deleted file mode 100644 index b018b3893bba3f504a5f8dc72886b9aad2d32831..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_svipro_fixed.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/wan_svipro_gray.cpython-312.pyc b/__pycache__/wan_svipro_gray.cpython-312.pyc deleted file mode 100644 index 8bd9a976255c60a162d4ca5196fdb0fca5ceebf6..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_svipro_gray.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/wan_svipro_gray.cpython-313.pyc b/__pycache__/wan_svipro_gray.cpython-313.pyc deleted file mode 100644 index 6596d71d4d5e5e8e56e9c1586e81ea6826d5da82..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_svipro_gray.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/wan_svipro_motion.cpython-313.pyc b/__pycache__/wan_svipro_motion.cpython-313.pyc deleted file mode 100644 index d19f954f227dfa9afa27251b1c0dcce0deb01613..0000000000000000000000000000000000000000 Binary files a/__pycache__/wan_svipro_motion.cpython-313.pyc and /dev/null differ diff --git a/cloud_env/comfyui_env_snapshot.json b/cloud_env/comfyui_env_snapshot.json deleted file mode 100644 index 28b79025f3c51aa85042076f25e16f14429dc258..0000000000000000000000000000000000000000 --- a/cloud_env/comfyui_env_snapshot.json +++ /dev/null @@ -1,598 +0,0 @@ -{ - "comfyui": "fe4195f7f4275f2626cbafc703acc3ddde1e5490", - "git_custom_nodes": { - "https://github.com/giriss/comfy-image-saver": { - "hash": "c6223965d45871d1ff2931b6ac9518a13947c920", - "disabled": false - }, - "https://github.com/M1kep/ComfyLiterals": { - "hash": "bdddb08ca82d90d75d97b1d437a652e0284a32ac", - "disabled": false - }, - "https://github.com/evanspearman/ComfyMath": { - "hash": "c01177221c31b8e5fbc062778fc8254aeb541638", - "disabled": false - }, - "https://github.com/yolain/ComfyUI-Easy-Use": { - "hash": "595e0738a9e3f8d0d9c4d875461b2d2c9e7559c7", - "disabled": false - }, - "https://github.com/NikoDemon80/ComfyUI-H3-Motion-Context.git": { - "hash": "15fc6a7bf7b78efb27f33d7eef3818e7ed0e118a", - "disabled": false - }, - "https://github.com/facok/comfyui-krea2-controlnet": { - "hash": "79ebfd3bd80d2180b334dd7ce57f3c9ddaa0848f", - "disabled": false - }, - "https://github.com/ltdrdata/ComfyUI-Manager": { - "hash": "8c5e640f83a26f22b0c7f0e63e89f7bab7fa6084", - "disabled": false - }, - "https://github.com/princepainter/Comfyui-PainterFLF2V": { - "hash": "c81a68feceb90b28557a979d6de44eac59c89123", - "disabled": false - }, - "https://github.com/princepainter/ComfyUI-PainterI2V": { - "hash": "c46e480c35d9a54e70139e354b1fc2975d0b6805", - "disabled": false - }, - "https://github.com/princepainter/ComfyUI-PainterI2Vadvanced": { - "hash": "a044ac7b2a33565343ddc75283c94a41371b4352", - "disabled": false - }, - "https://github.com/princepainter/ComfyUI-PainterI2VforKJ": { - "hash": "56f6e8a849d24ede99f3000d2b0fa2c0bc733bd8", - "disabled": false - }, - "https://github.com/Saganaki22/ComfyUI-sol-attn.git": { - "hash": "bd28909e6a152ba5db4e3590d2d9df1c249943f2", - "disabled": false - }, - "https://github.com/un-seen/comfyui-tensorops": { - "hash": "d34488e3079ecd10db2fe867c3a7af568115faed", - "disabled": false - }, - "https://github.com/jamesWalker55/comfyui-various": { - "hash": "5bd85aaf7616878471469c4ec7e11bbd0cef3bf2", - "disabled": false - }, - "https://github.com/GACLove/ComfyUI-VFI": { - "hash": "6176a430f12cd16003f4664c1e3c6af8e96cc3c6", - "disabled": false - }, - "https://github.com/kijai/ComfyUI-WanVideoWrapper": { - "hash": "088128b224242e110d3906c6750e9a3a348a659b", - "disabled": false - }, - "https://github.com/TripleHeadedMonkey/ComfyUI-Zlycoris": { - "hash": "cc8ecb9952940aa642e71d9b123b2d0a768e84d8", - "disabled": false - }, - "https://github.com/bjoo46/dolphin_nodes.git": { - "hash": "d1dee42586cc0f2bf0ef986c92d836052629c02a", - "disabled": false - }, - "https://github.com/ClownsharkBatwing/RES4LYF": { - "hash": "26036f647ca15d3048a193daf99a40cecfc3820d", - "disabled": false - }, - "https://github.com/Ltamann/ComfyUI-TBG-ETUR": { - "hash": "39e3a5f1225a817eea634d0c7ebbd7635cccf588", - "disabled": false - } - }, - "cnr_custom_nodes": { - "10s-comfy-nodes": "1.9.7", - "cg-use-everywhere": "7.8.0", - "comfyui-af-find-nodes": "0.3.2", - "ComfyUI-Conditioning-Rebalance": "2.0.2", - "ComfyUI-Crystools": "1.27.4", - "comfyui-custom-scripts": "1.2.5", - "comfyui-easy-sam3": "1.0.4", - "comfyui-florence2": "1.1.0", - "ComfyUI-GGUF": "1.1.10", - "comfyui-image-saver": "1.24.0", - "comfyui-impact-pack": "8.28.3", - "comfyui-impact-subpack": "1.3.5", - "comfyui-inpaint-nodes": "1.4.3", - "comfyui-inpainteasy": "1.0.2", - "comfyui-jakeupgrade": "2.7.5", - "ComfyUI-JoyCaption": "2.0.2", - "comfyui-kjnodes": "1.5.0", - "comfyui-krea2-conditioning": "1.0.2", - "comfyui-krea2edit": "1.2.3", - "ComfyUI-Krea2T-Enhancer": "1.1.2", - "comfyui-lora-manager": "1.2.0", - "comfyui-minimax-h3-turbo": "1.2.3", - "comfyui-mmaudio": "1.0.3", - "comfyui-mxtoolkit": "0.9.92", - "ComfyUI-nhknodes": "1.1.0", - "comfyui-promptchain": "0.1.13", - "comfyui-rmbg": "3.1.0", - "comfyui-segment-anything-2": "1.0.2", - "comfyui-videohelpersuite": "1.7.9", - "comfyui-vrgamedevgirl": "9.1.1", - "comfyui-wd14-tagger": "1.0.1", - "ComfyUI-Woosh": "0.2.7", - "comfyui_controlnet_aux": "1.1.5", - "comfyui_essentials": "1.1.0", - "comfyui_layerstyle": "2.0.40", - "comfyui_memory_cleanup": "1.1.3", - "comfyui_nvidia_rtx_nodes": "0.1.3", - "comfyui_queue_manager": "0.0.17", - "comfyui_tinyterranodes": "2.0.11", - "controlaltai-nodes": "1.1.4", - "erosdiffusion-eulerflowmatchingdiscretescheduler": "1.0.8", - "LanPaint": "1.5.5", - "rgthree-comfy": "1.0.2607232129", - "seedvr2_videoupscaler": "2.5.24", - "stablediffusion-dpmpp_2m_alt-sampler": "1.0.0", - "tripleksampler": "0.10.5" - }, - "file_custom_nodes": [ - { - "filename": "websocket_image_save.py", - "disabled": false - } - ], - "pips": { - "absl-py==2.3.1": "", - "accelerate==1.12.0": "", - "addict==2.4.0": "", - "ai-edge-litert==2.1.4": "", - "aiofiles==24.1.0": "", - "aiohappyeyeballs": "file:///D:/a/ComfyUI/cu130_python_deps/aiohappyeyeballs-2.6.1-py3-none-any.whl#sha256=f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", - "aiohttp==3.13.3": "", - "aiohttp_socks==0.11.0": "", - "aiosignal": "file:///D:/a/ComfyUI/cu130_python_deps/aiosignal-1.4.0-py3-none-any.whl#sha256=053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", - "aiosqlite==0.22.1": "", - "albucore==0.0.24": "", - "albumentations==2.0.8": "", - "alembic": "file:///D:/a/ComfyUI/cu130_python_deps/alembic-1.17.2-py3-none-any.whl#sha256=f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", - "aliyun-python-sdk-core==2.16.0": "", - "aliyun-python-sdk-kms==2.16.5": "", - "annotated-doc==0.0.4": "", - "annotated-types": "file:///D:/a/ComfyUI/cu130_python_deps/annotated_types-0.7.0-py3-none-any.whl#sha256=1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", - "antlr4-python3-runtime==4.9.3": "", - "anyascii==0.3.3": "", - "anyio==4.12.1": "", - "argbind==0.3.9": "", - "arrow==1.4.0": "", - "asgiref==3.11.1": "", - "attrs": "file:///D:/a/ComfyUI/cu130_python_deps/attrs-25.4.0-py3-none-any.whl#sha256=adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", - "audioop-lts==0.2.2": "", - "audioread==3.1.0": "", - "av==17.0.1": "", - "backoff==2.2.1": "", - "backports.strenum==1.2.8": "", - "backports.zstd==1.3.0": "", - "beautifulsoup4==4.14.3": "", - "binaryornot==0.6.0": "", - "bitsandbytes==0.49.1": "", - "blake3==1.0.8": "", - "blend_modes==2.2.0": "", - "blind-watermark==0.4.4": "", - "boto3==1.43.40": "", - "botocore==1.43.40": "", - "braceexpand==0.1.7": "", - "brotli==1.2.0": "", - "cachetools==6.2.4": "", - "certifi": "file:///D:/a/ComfyUI/cu130_python_deps/certifi-2025.11.12-py3-none-any.whl#sha256=97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", - "cffi": "file:///D:/a/ComfyUI/cu130_python_deps/cffi-2.0.0-cp313-cp313-win_amd64.whl#sha256=19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", - "cfgv==3.5.0": "", - "chardet==5.2.0": "", - "charset-normalizer": "file:///D:/a/ComfyUI/cu130_python_deps/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl#sha256=b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", - "click==8.4.1": "", - "clip-interrogator==0.6.0": "", - "cloudpickle==3.1.2": "", - "cloudscraper==1.2.71": "", - "cmake==4.2.1": "", - "color-matcher==0.6.0": "", - "colorama": "file:///D:/a/ComfyUI/cu130_python_deps/colorama-0.4.6-py2.py3-none-any.whl#sha256=4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", - "coloredlogs==15.0.1": "", - "colorlog==6.10.1": "", - "colour-science==0.4.7": "", - "comfy-3d-viewers==0.2.44": "", - "comfy-aimdo==0.4.13": "", - "comfy-angle==0.1.0": "", - "comfy-cli==1.10.5": "", - "comfy-env==0.3.89": "", - "comfy-kitchen==0.2.28": "", - "comfy-test==0.0.70": "", - "comfyui-embedded-docs==0.5.9": "", - "comfyui-workflow-templates-core==0.3.302": "", - "comfyui-workflow-templates-json==0.1.37": "", - "comfyui-workflow-templates-media-api==0.3.84": "", - "comfyui-workflow-templates-media-assets-01==0.1.24": "", - "comfyui-workflow-templates-media-image==0.3.160": "", - "comfyui-workflow-templates-media-other==0.3.229": "", - "comfyui-workflow-templates-media-video==0.3.101": "", - "comfyui_frontend_package==1.48.7": "", - "comfyui_workflow_templates==0.11.37": "", - "configs==3.0.3": "", - "contourpy==1.3.3": "", - "contractions==0.1.73": "", - "cookiecutter==2.7.1": "", - "crcmod==1.7": "", - "cryptography==46.0.3": "", - "cstr": "git+https://github.com/ltdrdata/cstr@0520c29a18a7a869a6e5983861d6f7a4c86f8e9b", - "cycler==0.12.1": "", - "datasets==3.6.0": "", - "dds-cloudapi-sdk==0.5.3": "", - "ddt==1.7.2": "", - "decorator==5.2.1": "", - "decord==0.6.0": "", - "deepdiff==8.6.1": "", - "defusedxml==0.7.1": "", - "demucs==4.0.1": "", - "diffusers==0.36.0": "", - "dill==0.3.8": "", - "distlib==0.4.0": "", - "distro==1.9.0": "", - "docstring_parser==0.18.0": "", - "docutils==0.22.4": "", - "dora_search==0.1.12": "", - "dotenv==0.9.9": "", - "easydict==1.13": "", - "einops": "file:///D:/a/ComfyUI/cu130_python_deps/einops-0.8.1-py3-none-any.whl#sha256=919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", - "evalidate==2.1.4": "", - "exejs==0.0.7": "", - "facexlib==0.3.0": "", - "fairscale==0.4.13": "", - "fal_client==0.13.1": "", - "fastapi==0.138.0": "", - "ffmpeg-python==0.2.0": "", - "ffmpy": "git+https://github.com/ltdrdata/ffmpy.git@f000737698b387ffaeab7cd871b0e9185811230d", - "filelock==3.25.2": "", - "filterpy==1.4.5": "", - "flatbuffers==25.12.19": "", - "fonttools==4.61.1": "", - "frozenlist": "file:///D:/a/ComfyUI/cu130_python_deps/frozenlist-1.8.0-cp313-cp313-win_amd64.whl#sha256=878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", - "fsspec==2025.3.0": "", - "ftfy==6.1.1": "", - "funasr==1.4.1": "", - "future==1.0.0": "", - "fvcore==0.1.5.post20221221": "", - "gdown==5.2.0": "", - "gguf==0.17.1": "", - "gitdb==4.0.12": "", - "GitPython==3.1.46": "", - "glcontext==3.0.0": "", - "glfw==2.10.0": "", - "google-ai-generativelanguage==0.6.15": "", - "google-api-core==2.29.0": "", - "google-api-python-client==2.188.0": "", - "google-auth==2.47.0": "", - "google-auth-httplib2==0.3.0": "", - "google-genai==1.59.0": "", - "google-generativeai==0.8.6": "", - "googleapis-common-protos==1.72.0": "", - "gradio==6.19.0": "", - "gradio_client==2.5.0": "", - "greenlet": "file:///D:/a/ComfyUI/cu130_python_deps/greenlet-3.2.4-cp313-cp313-win_amd64.whl#sha256=554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", - "grog==0.93": "", - "groovy==0.1.2": "", - "groundingdino-py==0.4.0": "", - "grpcio==1.76.0": "", - "grpcio-status==1.71.2": "", - "h11==0.16.0": "", - "h2==4.3.0": "", - "hear21passt==0.0.26": "", - "hf-gradio==0.4.1": "", - "hf-xet==1.5.1": "", - "hf_transfer==0.1.9": "", - "hpack==4.1.0": "", - "httpcore==1.0.9": "", - "httplib2==0.31.1": "", - "httpx==0.28.1": "", - "httpx-sse==0.4.3": "", - "huggingface_hub==1.20.1": "", - "humanfriendly==10.0": "", - "hydra-core==1.3.2": "", - "hyperframe==6.1.0": "", - "identify==2.6.18": "", - "idna": "file:///D:/a/ComfyUI/cu130_python_deps/idna-3.11-py3-none-any.whl#sha256=771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", - "ImageIO==2.37.2": "", - "imageio-ffmpeg==0.6.0": "", - "img2texture": "git+https://github.com/ltdrdata/img2texture.git@d6159abea44a0b2cf77454d3d46962c8b21eb9d3", - "importlib_metadata==8.7.1": "", - "inflate64==1.0.4": "", - "inflect==7.5.0": "", - "iniconfig==2.3.0": "", - "insightface==1.0.1": "", - "iopath==0.1.10": "", - "jaconv==0.5.0": "", - "jamo==0.4.1": "", - "jh2==5.0.10": "", - "jieba==0.42.1": "", - "Jinja2": "file:///D:/a/ComfyUI/cu130_python_deps/jinja2-3.1.6-py3-none-any.whl#sha256=85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", - "jiter==0.12.0": "", - "jmespath==0.10.0": "", - "joblib==1.5.3": "", - "json-logic==0.7.0a0": "", - "jsonschema==4.25.1": "", - "jsonschema-specifications==2025.9.1": "", - "julius==0.2.7": "", - "kaldifst==1.8.0": "", - "kaldiio==2.18.1": "", - "kiwisolver==1.4.9": "", - "kornia": "file:///D:/a/ComfyUI/cu130_python_deps/kornia-0.8.2-py2.py3-none-any.whl#sha256=32dfe77c9c74a87a2de49395aa3c2c376a1b63c27611a298b394d02d13905819", - "kornia_rs": "file:///D:/a/ComfyUI/cu130_python_deps/kornia_rs-0.1.10-cp313-cp313-win_amd64.whl#sha256=c57d157bebe64c22e2e44c72455b1c7365eee4d767e0c187dc28f22d072ebaf7", - "lameenc==1.8.2": "", - "lark==1.3.1": "", - "lazy_loader==0.4": "", - "librosa==0.11.0": "", - "lightning-utilities==0.15.3": "", - "llvmlite==0.46.0": "", - "lmdb==2.2.0": "", - "loguru==0.7.3": "", - "lxml==6.0.2": "", - "lycoris==0.0.0": "", - "Mako": "file:///D:/a/ComfyUI/cu130_python_deps/mako-1.3.10-py3-none-any.whl#sha256=baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", - "manifold3d==3.3.2": "", - "mapbox_earcut==2.0.0": "", - "markdown-it-py==4.0.0": "", - "MarkupSafe": "file:///D:/a/ComfyUI/cu130_python_deps/markupsafe-3.0.3-cp313-cp313-win_amd64.whl#sha256=9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", - "matplotlib==3.10.8": "", - "matrix-nio==0.25.2": "", - "mdurl==0.1.2": "", - "mediapipe==0.10.31": "", - "meson==1.10.0": "", - "meson-python==0.19.0": "", - "mixpanel==5.1.0": "", - "ml_dtypes==0.5.4": "", - "mmcv": "https://archive1.piwheels.org/simple/mmcv/mmcv-2.1.0-py2.py3-none-any.whl#sha256=3b4e4836676290d1f15f0a36b770572e0463024e269c451e48a3686c63c211fb", - "mmdet==3.3.0": "", - "mmengine==0.10.7": "", - "modelscope==1.39.1": "", - "modelscope-hub==0.2.0": "", - "moderngl==5.12.0": "", - "more-itertools==11.0.2": "", - "mpmath": "file:///D:/a/ComfyUI/cu130_python_deps/mpmath-1.3.0-py3-none-any.whl#sha256=a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", - "msgpack==1.1.2": "", - "mss==10.1.0": "", - "multidict": "file:///D:/a/ComfyUI/cu130_python_deps/multidict-6.7.0-cp313-cp313-win_amd64.whl#sha256=30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", - "multiprocess==0.70.16": "", - "multivolumefile==0.2.3": "", - "munkres==1.1.4": "", - "natsort==8.4.0": "", - "networkx": "file:///D:/a/ComfyUI/cu130_python_deps/networkx-3.5-py3-none-any.whl#sha256=0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", - "ninja==1.13.0": "", - "niquests==3.16.1": "", - "nodeenv==1.10.0": "", - "numba==0.63.1": "", - "numpy==2.3.5": "", - "nvidia-ml-py==13.590.44": "", - "olefile==0.47": "", - "ollama==0.6.1": "", - "omegaconf==2.3.0": "", - "omnivoice==0.1.5": "", - "onnx==1.20.1": "", - "onnxruntime==1.23.2": "", - "onnxruntime-gpu==1.23.2": "", - "open_clip_torch==3.2.0": "", - "openai==2.15.0": "", - "openai-whisper==20250625": "", - "opencv-contrib-python==4.13.0.92": "", - "opencv-python==4.10.0.84": "", - "opencv-python-headless==4.13.0.92": "", - "opensimplex==0.4.5.1": "", - "openunmix==1.3.0": "", - "optimum==2.1.0": "", - "ordered-set==4.1.0": "", - "orderly-set==5.5.0": "", - "orjson==3.11.9": "", - "oss2==2.19.1": "", - "packaging": "file:///D:/a/ComfyUI/cu130_python_deps/packaging-25.0-py3-none-any.whl#sha256=29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", - "pandas==2.3.3": "", - "pathos==0.3.4": "", - "pathspec==1.1.1": "", - "pedalboard==0.9.23": "", - "peft==0.18.0": "", - "pi_heif==1.4.0": "", - "piexif==1.1.3": "", - "pilgram==2.0.0": "", - "pillow==12.1.0": "", - "pixeloe==0.1.4": "", - "platformdirs==4.5.1": "", - "pluggy==1.6.0": "", - "polars==1.37.1": "", - "polars-runtime-32==1.37.1": "", - "pooch==1.8.2": "", - "portalocker==3.2.0": "", - "posthog==7.19.2": "", - "pox==0.3.6": "", - "ppft==1.7.7": "", - "pre_commit==4.5.1": "", - "prompt_toolkit==3.0.52": "", - "propcache": "file:///D:/a/ComfyUI/cu130_python_deps/propcache-0.4.1-cp313-cp313-win_amd64.whl#sha256=381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", - "proto-plus==1.27.0": "", - "protobuf==5.29.5": "", - "psd-tools==1.12.1": "", - "psutil==7.2.1": "", - "py-cpuinfo==9.0.0": "", - "py7zr==1.1.2": "", - "pyahocorasick==2.3.1": "", - "pyarrow==25.0.0": "", - "pyasn1==0.6.2": "", - "pyasn1_modules==0.4.2": "", - "pybcj==1.0.7": "", - "pycocoevalcap==1.2": "", - "pycocotools==2.0.11": "", - "pycollada==0.9.2": "", - "pycparser": "file:///D:/a/ComfyUI/cu130_python_deps/pycparser-2.23-py3-none-any.whl#sha256=e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", - "pycryptodome==3.23.0": "", - "pycryptodomex==3.23.0": "", - "pydantic==2.12.5": "", - "pydantic-settings": "file:///D:/a/ComfyUI/cu130_python_deps/pydantic_settings-2.12.0-py3-none-any.whl#sha256=fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", - "pydantic_core": "file:///D:/a/ComfyUI/cu130_python_deps/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl#sha256=79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", - "pyDeprecate==0.5.0": "", - "pydub==0.25.1": "", - "pygit2==1.19.1": "", - "PyGithub==2.8.1": "", - "Pygments==2.19.2": "", - "PyJWT==2.8.0": "", - "pyloudnorm==0.2.0": "", - "PyMatting==1.1.14": "", - "PyNaCl==1.6.2": "", - "pynndescent==0.6.0": "", - "pynvml==13.0.1": "", - "PyOpenGL==3.1.10": "", - "pyparsing==3.3.1": "", - "pyppmd==1.3.1": "", - "pyproject-metadata==0.10.0": "", - "pyreadline3==3.5.4": "", - "PySocks==1.7.1": "", - "pytest==9.0.2": "", - "pytest-mock==3.15.1": "", - "python-dateutil==2.9.0.post0": "", - "python-discovery==1.2.2": "", - "python-dotenv": "file:///D:/a/ComfyUI/cu130_python_deps/python_dotenv-1.2.1-py3-none-any.whl#sha256=b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", - "python-multipart==0.0.32": "", - "python-slugify==8.0.4": "", - "python-socks==2.8.0": "", - "pytorch-lightning==2.6.5": "", - "pytz==2025.2": "", - "PyWavelets==1.9.0": "", - "pywin32==311": "", - "PyYAML": "file:///D:/a/ComfyUI/cu130_python_deps/pyyaml-6.0.3-cp313-cp313-win_amd64.whl#sha256=79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", - "pyzbar==0.1.9": "", - "qh3==1.5.6": "", - "qrcode==8.2": "", - "questionary==2.1.1": "", - "qwen-vl-utils==0.0.14": "", - "RapidFuzz==3.14.5": "", - "redis==7.3.0": "", - "referencing==0.37.0": "", - "regex": "file:///D:/a/ComfyUI/cu130_python_deps/regex-2025.11.3-cp313-cp313-win_amd64.whl#sha256=bac4200befe50c670c405dc33af26dad5a3b6b255dd6c000d92fe4629f9ed6a5", - "rembg==2.0.72": "", - "replicate==1.0.7": "", - "requests": "file:///D:/a/ComfyUI/cu130_python_deps/requests-2.32.5-py3-none-any.whl#sha256=2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", - "requests-toolbelt==1.0.0": "", - "retrying==1.4.2": "", - "rich==14.2.0": "", - "rotary-embedding-torch==0.8.9": "", - "rpds-py==0.30.0": "", - "rsa==4.9.1": "", - "rtree==1.4.1": "", - "ruff==0.15.18": "", - "s3transfer==0.19.0": "", - "safehttpx==0.1.7": "", - "safetensors==0.7.0": "", - "sageattention": "https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post3/sageattention-2.2.0+cu130torch2.9.0.post3-cp39-abi3-win_amd64.whl#sha256=5d6b5799379e3ae66ffd9acd5dcce4a7a17f4e9114a9c24d4e09b69aead20068", - "SAM-2": "git+https://github.com/facebookresearch/sam2@2b90b9f5ceec907a1c18123530e92e794ad901a4", - "scikit-image==0.26.0": "", - "scikit-learn==1.8.0": "", - "scipy": "file:///D:/a/ComfyUI/cu130_python_deps/scipy-1.16.3-cp313-cp313-win_amd64.whl#sha256=062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", - "segment-anything==1.0": "", - "segmentation_models_pytorch==0.5.0": "", - "semantic-version==2.10.0": "", - "semver==3.0.4": "", - "sentencepiece": "file:///D:/a/ComfyUI/cu130_python_deps/sentencepiece-0.2.1-cp313-cp313-win_amd64.whl#sha256=10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b", - "sentry-sdk==2.55.0": "", - "setuptools": "file:///D:/a/ComfyUI/cu130_python_deps/setuptools-80.9.0-py3-none-any.whl#sha256=062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", - "shapely==2.1.2": "", - "shellingham==1.5.4": "", - "shtab==1.8.0": "", - "simpleeval==1.0.3": "", - "simplejson==4.1.1": "", - "simsimd==6.5.12": "", - "six==1.17.0": "", - "smmap==5.0.2": "", - "sniffio==1.3.1": "", - "sortedcontainers==2.4.0": "", - "sounddevice==0.5.3": "", - "soundfile==0.13.1": "", - "soupsieve==2.8.1": "", - "soxr==1.0.0": "", - "spaces==0.51.1": "", - "spandrel": "file:///D:/a/ComfyUI/cu130_python_deps/spandrel-0.4.1-py3-none-any.whl#sha256=49a39aa979769749a42203428355bc4840452854d6334ce0d465af46098dd448", - "SQLAlchemy==2.0.45": "", - "stable-ts==2.19.1": "", - "standard-aifc==3.13.0": "", - "standard-chunk==3.13.0": "", - "standard-sunau==3.13.0": "", - "starlette==1.3.1": "", - "stringzilla==4.6.0": "", - "submitit==1.5.4": "", - "supervision==0.6.0": "", - "surrealist==2.0.2": "", - "svg.path==7.0": "", - "sympy": "file:///D:/a/ComfyUI/cu130_python_deps/sympy-1.14.0-py3-none-any.whl#sha256=e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", - "tabulate==0.9.0": "", - "taichi==1.7.4": "", - "tenacity==9.1.2": "", - "tensorboardX==2.6.5": "", - "termcolor==3.3.0": "", - "terminaltables==3.1.10": "", - "text-unidecode==1.3": "", - "textsearch==0.0.24": "", - "texttable==1.7.0": "", - "threadpoolctl==3.6.0": "", - "tifffile==2025.12.20": "", - "tiktoken==0.12.0": "", - "timm==1.0.27": "", - "tokenizers==0.22.2": "", - "toml==0.10.2": "", - "tomli==2.4.1": "", - "tomli_w==1.2.0": "", - "tomlkit==0.14.0": "", - "torch": "file:///D:/a/ComfyUI/cu130_python_deps/torch-2.9.1%2Bcu130-cp313-cp313-win_amd64.whl#sha256=47cae56331fdec7f7082aad967b25868cc5f94b203fc273e40346a852aa009dd", - "torch-complex==0.4.4": "", - "torchaudio": "file:///D:/a/ComfyUI/cu130_python_deps/torchaudio-2.9.1%2Bcu130-cp313-cp313-win_amd64.whl#sha256=2e90a149a17a41676f5a2e4e95c3a46baf3a9389af205894c23b056c8ad06daf", - "torchcodec==0.9.1": "", - "torchdiffeq==0.2.5": "", - "torchlanc==1.1.1": "", - "torchmetrics==1.9.0": "", - "torchscale==0.3.0": "", - "torchsde": "file:///D:/a/ComfyUI/cu130_python_deps/torchsde-0.2.6-py3-none-any.whl#sha256=19bf7ff02eec7e8e46ba1cdb4aa0f9db1c51d492524a16975234b467f7fc463b", - "torchvision": "file:///D:/a/ComfyUI/cu130_python_deps/torchvision-0.24.1%2Bcu130-cp313-cp313-win_amd64.whl#sha256=d688426d623b8ba763087726faf5b34b1fe404f44741656978d855403dbc7444", - "tqdm": "file:///D:/a/ComfyUI/cu130_python_deps/tqdm-4.67.1-py3-none-any.whl#sha256=26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", - "trampoline": "file:///D:/a/ComfyUI/cu130_python_deps/trampoline-0.1.2-py3-none-any.whl#sha256=36cc9a4ff9811843d177fc0e0740efbd7da39eadfe6e50c9e2937cbc06d899d9", - "transformers==5.12.1": "", - "translators==6.0.2": "", - "transparent-background==1.3.4": "", - "treetable==0.2.6": "", - "trimesh==4.11.0": "", - "triton-windows==3.5.1.post24": "", - "typeguard==4.6.0": "", - "typer==0.21.1": "", - "typer-config==1.4.3": "", - "typing-inspection": "file:///D:/a/ComfyUI/cu130_python_deps/typing_inspection-0.4.2-py3-none-any.whl#sha256=4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", - "typing_extensions": "file:///D:/a/ComfyUI/cu130_python_deps/typing_extensions-4.15.0-py3-none-any.whl#sha256=f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", - "tyro==0.8.5": "", - "tzdata==2025.3": "", - "ultralytics==8.4.5": "", - "ultralytics-thop==2.0.18": "", - "umap-learn==0.5.12": "", - "unpaddedbase64==2.1.0": "", - "uritemplate==4.2.0": "", - "urllib3": "file:///D:/a/ComfyUI/cu130_python_deps/urllib3-2.5.0-py3-none-any.whl#sha256=e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", - "urllib3-future==2.15.901": "", - "uv==0.9.22": "", - "uvicorn==0.49.0": "", - "vhacdx==0.0.10": "", - "virtualenv==21.2.1": "", - "voxcpm==2.0.3": "", - "wandb==0.27.2": "", - "wassima==2.0.4": "", - "wcwidth==0.2.14": "", - "webdataset==1.0.2": "", - "websocket-client==1.9.0": "", - "websockets==15.0.1": "", - "wetext==0.1.6": "", - "wget==3.2": "", - "wheel==0.45.1": "", - "win32_setctime==1.2.0": "", - "word2number==1.1": "", - "wordcloud==1.9.6": "", - "xxhash==3.6.0": "", - "yacs==0.1.8": "", - "yapf==0.43.0": "", - "yarl": "file:///D:/a/ComfyUI/cu130_python_deps/yarl-1.22.0-cp313-cp313-win_amd64.whl#sha256=47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", - "yt-dlp==2026.3.13": "", - "zhipuai==2.1.5.20250825": "", - "zipp==3.23.0": "" - } -} \ No newline at end of file diff --git a/dataset_builder/README.md b/dataset_builder/README.md deleted file mode 100644 index 4788f9a718e41e3d3c26d040bb6a34089708641f..0000000000000000000000000000000000000000 --- a/dataset_builder/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# Wan 2.2 LoRA Dataset Builder - -임의의 이미지/영상 폴더를 넣으면 피사체 중심으로 크롭하고 로컬 VLM으로 캡션을 달아, -LoRA 학습 스크립트(musubi-tuner 등)에 바로 넣을 수 있는 `images/`, `videos/`, -`.txt` 캡션, `dataset.toml` 초안을 생성하는 독립 CLI 도구. - -ComfyUI 노드가 아님 — `__init__.py`에서 import하지 않으므로 ComfyUI 노드 로딩에 -관여하지 않고, `python_embeded\python.exe`로 직접 실행한다. - -**사용 전제: 학습 대상 인물/캐릭터가 명백히 성인이어야 함.** 나이가 불명확하거나 -앳돼 보이는 인물의 성적 콘텐츠 데이터셋 구축에는 이 도구를 쓰지 말 것. - -## 사용법 - -``` -D:\SD\ComfyUI_windows_portable\python_embeded\python.exe ^ - dataset_builder\build.py ^ - --input "D:\raw_media" ^ - --output "D:\wan_lora_dataset" ^ - --trigger "ohwx man" ^ - --captioner joycaption -``` - -`--input` 폴더를 재귀적으로 스캔해 이미지(jpg/png/webp/bmp)와 영상(mp4/mov/mkv/avi/webm)을 -자동 분류하여 처리한다. - -## 파이프라인 - -1. **피사체 감지**: mediapipe 얼굴 검출 → (실패 시) OpenCV Haar cascade 얼굴 검출 → - (그래도 실패 시) ultralytics YOLOv8n 사람 검출 → (그래도 없으면) 전체 프레임 - 그대로 사용. 사람이 아닌 사물/장면 LoRA는 자연스럽게 "전체 프레임" 경로로 빠진다. -2. **버킷 매칭 + 크롭**: 감지된 bbox를 얼굴 2.2배 / 사람 1.3배로 패딩한 뒤, Wan - 480p/720p/1080p × (가로/세로/정사각) 버킷 중 종횡비가 가장 가까운 것을 골라 - 그 해상도로 크롭+리사이즈(16 배수 정렬). 원본 해상도보다 커지는(업스케일) 버킷은 - 자동으로 건너뛴다 — `--max-tier`로 상한만 지정. -3. **영상 씬 분할**: OpenCV 히스토그램 차분으로 컷 지점을 찾고, `--clip-min-sec` - ~ `--clip-max-sec` 범위로 클립을 나눈다. 클립마다 대표 프레임 몇 장의 bbox - 중앙값으로 크롭을 고정하고(프레임 흔들림 방지), `--video-fps`로 리샘플, 4n+1 - 프레임 수로 트리밍, optical flow 평균 크기로 "static/subtle/moderate/fast - motion" 태그를 캡션에 덧붙인다. -4. **캡셔닝** (`--captioner`): - - `joycaption` (기본) — `fancyfeast/llama-joycaption-beta-one-hf-llava`. - **주의**: HuggingFace 모델 카드에 "학습용 캡션" 전용 프롬프트 문자열이 공식 - 문서화되어 있지 않아, 여기 쓰인 지시문(`caption.py`의 `DEFAULT_PROMPT`)은 - 자체 작성한 기본값이다. 원하는 스타일이 있으면 `--caption-prompt`로 바꿔라. - - `florence2` — `microsoft/Florence-2-large`, 가볍고 빠름(~0.77B). - - `wd14` — `SmilingWolf/wd-vit-tagger-v3` (onnx), booru 태그 스타일. - - 공통: `--trigger`로 넘긴 트리거 워드를 캡션 맨 앞에 자동 삽입. - -## 출력 - -``` -output_dir/ - images/0001.jpg, 0001.txt, ... - videos/0001.mp4, 0001.txt, ... - dataset.toml # musubi-tuner 스타일 초안 — 실제 트레이너 버전 문서와 대조 후 사용 -``` - -## 모델 다운로드 / VRAM 참고 - -- 최초 실행 시 HuggingFace에서 캡셔너 가중치(JoyCaption ~16GB, Florence-2-large - ~1.5GB, WD14 ~수백MB)와 ultralytics `yolov8n.pt`(~6MB)를 자동 다운로드한다. - 네트워크/디스크 용량 확인 후 실행할 것. -- RTX 5090 32GB 기준, ComfyUI가 이미 VRAM을 점유 중이면 JoyCaption(8B, bf16 기준 - ~16GB)이 부족할 수 있다. 이 경우 `--load-in-4bit`(bitsandbytes)로 줄이거나, - ComfyUI 서버를 잠시 내려두고 실행할 것. - -## 알려진 환경 특이사항 - -- 이 embedded Python 환경은 `import torch`만 해도 인터프리터 종료 시 비정상 - 종료 코드(예: `-1073740791`)를 낸다. `dataset_builder`와 무관한 기존 환경 - 이슈이며, 실제 작업(파일 저장)은 그 전에 이미 끝난 뒤 발생하는 종료 시점 - 크래시라 결과물에는 영향 없음. 배치 스크립트에서 종료 코드로 성공/실패를 - 판단하지 말고 콘솔 로그의 `Done.` 줄로 확인할 것. - -## 검증된 것 / 안 된 것 - -- `crop_resize.py`(버킷 매칭 수학), `detect.py`(다단계 폴백), `scene_split.py` - (씬 컷 + 클립 추출 + 4n+1 트리밍 + 모션 태그)는 실제 샘플 이미지/영상으로 - 스모크 테스트 완료. -- `caption.py`(JoyCaption/Florence-2/WD14 실제 추론 호출)는 모델 가중치 다운로드가 - 커서 이번 세션에서 end-to-end로 돌려보지 않았다. 클래스/전처리는 각 모델의 - 공식 문서(HF 모델 카드, wd-tagger Space 소스)를 대조해 작성했지만, 처음 - 실행할 때 캡셔너 쪽 에러가 나면 그 부분부터 확인할 것. diff --git a/dataset_builder/__pycache__/build.cpython-313.pyc b/dataset_builder/__pycache__/build.cpython-313.pyc deleted file mode 100644 index 9fb341dc1b1fc7e644f6ad0041ed0a2f713f67e2..0000000000000000000000000000000000000000 Binary files a/dataset_builder/__pycache__/build.cpython-313.pyc and /dev/null differ diff --git a/dataset_builder/__pycache__/crop_resize.cpython-313.pyc b/dataset_builder/__pycache__/crop_resize.cpython-313.pyc deleted file mode 100644 index 53c9f2ad9ceb0830809323cfa3528b677430528f..0000000000000000000000000000000000000000 Binary files a/dataset_builder/__pycache__/crop_resize.cpython-313.pyc and /dev/null differ diff --git a/dataset_builder/build.py b/dataset_builder/build.py deleted file mode 100644 index 14df6ed41dfce3954b9b22b85f6bd3447e1e46e5..0000000000000000000000000000000000000000 --- a/dataset_builder/build.py +++ /dev/null @@ -1,262 +0,0 @@ -"""Wan 2.2 LoRA dataset builder. - -Feed it a folder of random images/videos; it detects the main subject, crops -to a Wan resolution bucket, captions locally, and writes an -images/ + videos/ + .txt caption dataset plus a starter dataset.toml. - -Usage: - python_embeded\\python.exe dataset_builder\\build.py --input ./raw --output ./dataset_out --trigger "ohwx man" - -Run from anywhere -- this script adds its own directory to sys.path so the -sibling modules (crop_resize, detect, caption, scene_split) import cleanly -whether invoked directly or via -m. -""" -import argparse -import os -import sys -import traceback - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import cv2 -import numpy as np -from PIL import Image - -import crop_resize -import scene_split -from caption import build_captioner, with_trigger -from detect import detect_subject_bbox, subdivide_bbox - -IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp"} -VIDEO_EXTS = {".mp4", ".mov", ".mkv", ".avi", ".webm"} - - -def parse_args(): - p = argparse.ArgumentParser(description="Wan 2.2 LoRA dataset auto-builder") - p.add_argument("--input", required=True, help="Folder of raw images/videos (scanned recursively)") - p.add_argument("--output", required=True, help="Output dataset folder") - p.add_argument("--trigger", default="", help="Trigger word/phrase prepended to every caption") - - p.add_argument("--captioner", choices=["joycaption", "florence2", "wd14"], default="joycaption") - p.add_argument("--device", default="cuda") - p.add_argument("--load-in-4bit", action="store_true", help="4-bit load for JoyCaption (bitsandbytes)") - p.add_argument("--caption-prompt", default=None, help="Override JoyCaption instruction text") - p.add_argument("--wd14-general-thresh", type=float, default=0.35) - p.add_argument("--wd14-character-thresh", type=float, default=0.75) - - p.add_argument("--max-tier", choices=["480p", "720p", "1080p"], default="1080p", - help="Highest resolution bucket to consider; auto-picks smaller tiers to avoid upscaling") - p.add_argument("--face-pad", type=float, default=2.2) - p.add_argument("--person-pad", type=float, default=1.3) - p.add_argument("--person-region-pad", type=float, default=1.7, - help="padding for person_upper/person_lower crops (see --body-region)") - p.add_argument("--body-region", choices=["auto", "upper", "lower"], default="auto", - help="for person-kind detections, narrow the crop to the top or bottom " - "portion of the detected body instead of the whole figure -- use " - "when most source frames are already close-ups of one body region. " - "'auto' (default) keeps the full detected box. No effect on face-kind " - "detections.") - p.add_argument("--body-region-frac", type=float, default=0.45, - help="fraction of the person bbox's height kept when --body-region is upper/lower") - p.add_argument("--min-face-conf", type=float, default=0.5) - p.add_argument("--min-person-conf", type=float, default=0.35) - p.add_argument("--image-format", choices=["jpg", "png"], default="jpg") - - p.add_argument("--video-fps", type=float, default=16.0) - p.add_argument("--clip-min-sec", type=float, default=2.0) - p.add_argument("--clip-max-sec", type=float, default=6.0) - p.add_argument("--scene-diff-thresh", type=float, default=0.45) - p.add_argument("--clips-per-video", type=int, default=5, - help="cap clips extracted per source video, evenly sampled across its " - "timeline (e.g. a 5-minute video with many scene cuts won't dump " - "dozens of near-duplicate clips into the dataset); pass 0 for no cap") - - return p.parse_args() - - -def scan_inputs(input_dir): - images, videos = [], [] - for root, _dirs, files in os.walk(input_dir): - for name in files: - ext = os.path.splitext(name)[1].lower() - path = os.path.join(root, name) - if ext in IMAGE_EXTS: - images.append(path) - elif ext in VIDEO_EXTS: - videos.append(path) - return sorted(images), sorted(videos) - - -def process_image(path, idx, out_dir, args, captioner, pad_overrides): - pil_img = Image.open(path).convert("RGB") - bgr = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) - - bbox, kind = detect_subject_bbox(bgr, args.min_face_conf, args.min_person_conf) - if kind == "person" and args.body_region != "auto": - bbox = subdivide_bbox(bbox, args.body_region, args.body_region_frac) - kind = f"person_{args.body_region}" - crop_box, (tw, th), bucket_name, tier = crop_resize.compute_crop( - pil_img.width, pil_img.height, bbox, kind, pad_overrides, args.max_tier - ) - cropped = crop_resize.crop_resize_pil(pil_img, crop_box, tw, th) - - stem = f"{idx:04d}" - images_dir = os.path.join(out_dir, "images") - os.makedirs(images_dir, exist_ok=True) - img_path = os.path.join(images_dir, f"{stem}.{args.image_format}") - if args.image_format == "jpg": - cropped.save(img_path, quality=95) - else: - cropped.save(img_path) - - caption = captioner.caption([cropped]) - caption = with_trigger(caption, args.trigger) - with open(os.path.join(images_dir, f"{stem}.txt"), "w", encoding="utf-8") as f: - f.write(caption) - - print(f" [image] {os.path.basename(path)} -> {stem}.{args.image_format} " - f"({kind}, {bucket_name}/{tier}, {tw}x{th})") - - -def subsample_ranges(ranges, target): - """Evenly picks `target` ranges spread across the full list (by index, so - spread across the video's timeline) instead of just taking the first N -- - keeps whatever diversity of scenes exists rather than favoring the start.""" - if not target or len(ranges) <= target: - return ranges - if target <= 1: - return ranges[len(ranges) // 2 : len(ranges) // 2 + 1] - step = (len(ranges) - 1) / (target - 1) - picked_indices = sorted({round(i * step) for i in range(target)}) - return [ranges[i] for i in picked_indices] - - -def process_video(path, idx_start, out_dir, args, captioner, pad_overrides): - cuts = scene_split.find_scene_cuts(path, diff_thresh=args.scene_diff_thresh) - cap = cv2.VideoCapture(path) - total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - src_fps = cap.get(cv2.CAP_PROP_FPS) or args.video_fps - cap.release() - - ranges = scene_split.build_clip_ranges( - total_frames, src_fps, cuts, args.clip_min_sec, args.clip_max_sec - ) - detected_count = len(ranges) - ranges = subsample_ranges(ranges, args.clips_per_video) - if args.clips_per_video and detected_count > len(ranges): - print(f" [video] {os.path.basename(path)}: {detected_count} scene(s) detected, " - f"sampling {len(ranges)} (--clips-per-video {args.clips_per_video})") - - idx = idx_start - for start, end in ranges: - result = scene_split.extract_clip( - path, start, end, args.video_fps, args.max_tier, pad_overrides, - args.body_region, args.body_region_frac, - ) - if result is None: - continue - frames, (tw, th), representative, motion_tag = result - - stem = f"{idx:04d}" - videos_dir = os.path.join(out_dir, "videos") - os.makedirs(videos_dir, exist_ok=True) - clip_path = os.path.join(videos_dir, f"{stem}.mp4") - scene_split.write_clip_mp4(frames, clip_path, args.video_fps) - - caption = captioner.caption([representative]) - caption = f"{caption.strip()}, {motion_tag}" if caption.strip() else motion_tag - caption = with_trigger(caption, args.trigger) - with open(os.path.join(videos_dir, f"{stem}.txt"), "w", encoding="utf-8") as f: - f.write(caption) - - print(f" [video] {os.path.basename(path)} [{start}-{end}] -> {stem}.mp4 " - f"({len(frames)}f @ {args.video_fps}fps, {tw}x{th}, {motion_tag})") - idx += 1 - return idx - - -def write_dataset_toml(out_dir, args, has_images, has_videos): - """musubi-tuner dataset config (schema verified against docs/dataset_config.md). - Items are already pre-cropped to one of a handful of Wan resolution buckets - by this tool, so enable_bucket lets musubi-tuner group them without forcing - a single resolution; `resolution` below is just the nominal/reference size.""" - ref_w, ref_h = crop_resize.bucket_set(args.max_tier)["landscape"] - - def p(*parts): - return os.path.join(out_dir, *parts).replace("\\", "/") - - lines = [ - "[general]", - f"resolution = [{ref_w}, {ref_h}]", - 'caption_extension = ".txt"', - "batch_size = 1", - "enable_bucket = true", - "bucket_no_upscale = true", - "", - ] - if has_images: - lines += [ - "[[datasets]]", - f'image_directory = "{p("images")}"', - f'cache_directory = "{p("images", "cache")}"', - "num_repeats = 1", - "", - ] - if has_videos: - max_frames = int(args.video_fps * args.clip_max_sec) + 1 - lines += [ - "[[datasets]]", - f'video_directory = "{p("videos")}"', - f'cache_directory = "{p("videos", "cache")}"', - 'frame_extraction = "full"', - f"max_frames = {max_frames}", - f"source_fps = {args.video_fps}", - "num_repeats = 1", - "", - ] - with open(os.path.join(out_dir, "dataset.toml"), "w", encoding="utf-8") as f: - f.write("\n".join(lines)) - - -def main(): - args = parse_args() - os.makedirs(os.path.join(args.output, "images"), exist_ok=True) - os.makedirs(os.path.join(args.output, "videos"), exist_ok=True) - - images, videos = scan_inputs(args.input) - print(f"Found {len(images)} image(s), {len(videos)} video(s) under {args.input}") - if not images and not videos: - print("Nothing to do.") - return - - pad_overrides = { - "face": args.face_pad, "person": args.person_pad, - "person_upper": args.person_region_pad, "person_lower": args.person_region_pad, - } - - print(f"Loading captioner: {args.captioner} ...") - captioner = build_captioner(args) - - img_idx = 1 - for path in images: - try: - process_image(path, img_idx, args.output, args, captioner, pad_overrides) - img_idx += 1 - except Exception: - print(f" [image] FAILED {path}:") - traceback.print_exc() - - vid_idx = 1 - for path in videos: - try: - vid_idx = process_video(path, vid_idx, args.output, args, captioner, pad_overrides) - except Exception: - print(f" [video] FAILED {path}:") - traceback.print_exc() - - write_dataset_toml(args.output, args, bool(images), bool(videos)) - print(f"Done. {img_idx - 1} image(s), {vid_idx - 1} clip(s) -> {args.output}") - - -if __name__ == "__main__": - main() diff --git a/dataset_builder/caption.py b/dataset_builder/caption.py deleted file mode 100644 index ffa21ca1747f682fdc2ea1d56a5dfb79f1c7aeb1..0000000000000000000000000000000000000000 --- a/dataset_builder/caption.py +++ /dev/null @@ -1,209 +0,0 @@ -"""Pluggable local captioners for LoRA dataset building. - -All captioners expose the same interface: ``caption(images: list[PIL.Image]) -> str``. -``images`` is a list so video callers can pass multiple sampled frames; single-image -captioners just use the first one. - -- JoyCaptionCaptioner: fancyfeast/llama-joycaption-beta-one-hf-llava (AutoProcessor + - LlavaForConditionalGeneration, confirmed against the model card). The exact prompt - wording for a dedicated "training caption" mode is not documented on the model card, - so the instruction text here is our own default and is overridable via --caption-prompt. -- Florence2Captioner: microsoft/Florence-2-large, task. -- WD14Captioner: SmilingWolf/wd-vit-tagger-v3 onnx tagger (booru-style tags), preprocessing - confirmed against the reference wd-tagger Space source (white-pad-to-square, RGB->BGR, - no normalization, category 9/4/0 = rating/character/general). -""" -import csv - -import numpy as np -import torch -from PIL import Image - - -class JoyCaptionCaptioner: - MODEL_ID = "fancyfeast/llama-joycaption-beta-one-hf-llava" - DEFAULT_PROMPT = ( - "Write a detailed but concise caption for this image, suitable for training " - "an image/video LoRA. Describe the subject's appearance, pose, clothing, " - "setting, and lighting in plain descriptive sentences. Do not guess names." - ) - - def __init__(self, device="cuda", load_in_4bit=False, prompt=None, system_prompt=None): - from transformers import AutoProcessor, LlavaForConditionalGeneration - - self.processor = AutoProcessor.from_pretrained(self.MODEL_ID) - load_kwargs = {} - if load_in_4bit: - from transformers import BitsAndBytesConfig - load_kwargs["quantization_config"] = BitsAndBytesConfig(load_in_4bit=True) - load_kwargs["device_map"] = "auto" - else: - load_kwargs["torch_dtype"] = torch.bfloat16 - load_kwargs["device_map"] = device - self.model = LlavaForConditionalGeneration.from_pretrained(self.MODEL_ID, **load_kwargs) - self.model.eval() - - self.system_prompt = system_prompt or "You are a helpful image captioner." - self.prompt = prompt or self.DEFAULT_PROMPT - - def caption(self, images): - image = images[0].convert("RGB") - convo = [ - {"role": "system", "content": self.system_prompt}, - {"role": "user", "content": self.prompt}, - ] - convo_string = self.processor.apply_chat_template( - convo, tokenize=False, add_generation_prompt=True - ) - inputs = self.processor(text=[convo_string], images=[image], return_tensors="pt") - inputs = {k: v.to(self.model.device) for k, v in inputs.items()} - if "pixel_values" in inputs: - inputs["pixel_values"] = inputs["pixel_values"].to(self.model.dtype) - - with torch.no_grad(): - generate_ids = self.model.generate( - **inputs, - max_new_tokens=300, - do_sample=True, - suppress_tokens=None, - use_cache=True, - temperature=0.6, - top_k=None, - top_p=0.9, - )[0] - - generate_ids = generate_ids[inputs["input_ids"].shape[1]:] - text = self.processor.tokenizer.decode( - generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False - ) - return text.strip() - - -class Florence2Captioner: - MODEL_ID = "microsoft/Florence-2-large" - TASK = "" - - def __init__(self, device="cuda"): - from transformers import AutoModelForCausalLM, AutoProcessor - - dtype = torch.float16 if device.startswith("cuda") else torch.float32 - self.device = device - self.model = ( - AutoModelForCausalLM.from_pretrained(self.MODEL_ID, trust_remote_code=True, torch_dtype=dtype) - .to(device) - .eval() - ) - self.processor = AutoProcessor.from_pretrained(self.MODEL_ID, trust_remote_code=True) - - def caption(self, images): - image = images[0].convert("RGB") - inputs = self.processor(text=self.TASK, images=image, return_tensors="pt") - inputs = {k: v.to(self.device, self.model.dtype) if v.is_floating_point() else v.to(self.device) - for k, v in inputs.items()} - - with torch.no_grad(): - generated_ids = self.model.generate( - input_ids=inputs["input_ids"], - pixel_values=inputs["pixel_values"], - max_new_tokens=200, - num_beams=3, - do_sample=False, - ) - text = self.processor.batch_decode(generated_ids, skip_special_tokens=False)[0] - parsed = self.processor.post_process_generation( - text, task=self.TASK, image_size=(image.width, image.height) - ) - return parsed[self.TASK].strip() - - -class WD14Captioner: - REPO_ID = "SmilingWolf/wd-vit-tagger-v3" - MODEL_FILE = "model.onnx" - TAGS_FILE = "selected_tags.csv" - - def __init__(self, general_thresh=0.35, character_thresh=0.75, use_gpu=True): - import onnxruntime as ort - from huggingface_hub import hf_hub_download - - model_path = hf_hub_download(self.REPO_ID, self.MODEL_FILE) - tags_path = hf_hub_download(self.REPO_ID, self.TAGS_FILE) - - providers = ["CPUExecutionProvider"] - if use_gpu: - providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] - self.session = ort.InferenceSession(model_path, providers=providers) - self.input_name = self.session.get_inputs()[0].name - self.output_name = self.session.get_outputs()[0].name - self.target_size = int(self.session.get_inputs()[0].shape[1]) - - self.general_thresh = general_thresh - self.character_thresh = character_thresh - self.tag_names, self.general_idx, self.character_idx = self._load_tags(tags_path) - - @staticmethod - def _load_tags(path): - names, general, character = [], [], [] - with open(path, newline="", encoding="utf-8") as f: - for i, row in enumerate(csv.DictReader(f)): - names.append(row["name"]) - cat = int(row["category"]) - if cat == 4: - character.append(i) - elif cat != 9: # skip rating (9); keep general (0) and any others - general.append(i) - return names, general, character - - def _preprocess(self, image: Image.Image): - image = image.convert("RGBA") - canvas = Image.new("RGBA", image.size, (255, 255, 255, 255)) - canvas.alpha_composite(image) - image = canvas.convert("RGB") - - w, h = image.size - size = max(w, h) - padded = Image.new("RGB", (size, size), (255, 255, 255)) - padded.paste(image, ((size - w) // 2, (size - h) // 2)) - padded = padded.resize((self.target_size, self.target_size), Image.BICUBIC) - - arr = np.asarray(padded, dtype=np.float32) - arr = arr[:, :, ::-1] # RGB -> BGR - return np.expand_dims(arr, axis=0) - - def caption(self, images): - image = images[0] - arr = self._preprocess(image) - preds = self.session.run([self.output_name], {self.input_name: arr})[0][0] - - general = [(self.tag_names[i], preds[i]) for i in self.general_idx if preds[i] >= self.general_thresh] - character = [(self.tag_names[i], preds[i]) for i in self.character_idx if preds[i] >= self.character_thresh] - general.sort(key=lambda t: -t[1]) - character.sort(key=lambda t: -t[1]) - - tags = [t.replace("_", " ") for t, _ in character] + [t.replace("_", " ") for t, _ in general] - return ", ".join(tags) - - -def build_captioner(args): - name = args.captioner - if name == "joycaption": - return JoyCaptionCaptioner( - device=args.device, - load_in_4bit=args.load_in_4bit, - prompt=args.caption_prompt, - ) - if name == "florence2": - return Florence2Captioner(device=args.device) - if name == "wd14": - return WD14Captioner( - general_thresh=args.wd14_general_thresh, - character_thresh=args.wd14_character_thresh, - use_gpu=args.device.startswith("cuda"), - ) - raise ValueError(f"Unknown captioner: {name}") - - -def with_trigger(caption, trigger): - caption = (caption or "").strip() - if not trigger: - return caption - return f"{trigger}, {caption}" if caption else trigger diff --git a/dataset_builder/crop_resize.py b/dataset_builder/crop_resize.py deleted file mode 100644 index 56036fd6106e9d4fe6d83163fc4f0a50a8c93bdc..0000000000000000000000000000000000000000 --- a/dataset_builder/crop_resize.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Wan 2.2 resolution-bucket matching + subject-centered crop math. - -Ports the area-preserving resize logic from ``wan_resizer.py`` -(``WanNativeResize_Dolphin``) into a torch/comfy-free form so it can run -outside ComfyUI in the standalone dataset builder CLI. -""" -import math - -import cv2 -import numpy as np -from PIL import Image - -TIER_AREA = { - "480p": 832 * 480, - "720p": 1280 * 720, - "1080p": 1920 * 1080, -} -TIER_ORDER = ["1080p", "720p", "480p"] - -ASPECTS = { - "landscape": 16 / 9, - "portrait": 9 / 16, - "square": 1.0, -} - -# Default bbox padding multiplier per detected subject kind (see detect.py). -# person_upper/person_lower are subdivide_bbox() regions -- padded more than -# plain "person" since they start from an already-narrowed sub-box. -DEFAULT_PAD = { - "face": 2.2, - "person": 1.3, - "person_upper": 1.7, - "person_lower": 1.7, - "full_frame": 1.0, -} - - -def _bucket_dims(area, aspect, alignment=16): - h = math.sqrt(area / aspect) - w = h * aspect - w = max(alignment, int(round(w / alignment)) * alignment) - h = max(alignment, int(round(h / alignment)) * alignment) - return w, h - - -def bucket_set(tier): - area = TIER_AREA[tier] - return {name: _bucket_dims(area, aspect) for name, aspect in ASPECTS.items()} - - -def nearest_bucket(crop_w, crop_h, tier): - """Pick the bucket of ``tier`` whose aspect ratio is closest to crop_w/crop_h.""" - crop_aspect = crop_w / crop_h - buckets = bucket_set(tier) - best_name = min( - buckets, - key=lambda n: abs(math.log(buckets[n][0] / buckets[n][1]) - math.log(crop_aspect)), - ) - return best_name, buckets[best_name] - - -def auto_tier(crop_w, crop_h, max_tier="1080p"): - """Pick the largest tier whose bucket area does not exceed the native crop's - pixel area, to avoid upscaling past source resolution. Falls back to the - smallest tier under max_tier if even that would upscale.""" - order = TIER_ORDER[TIER_ORDER.index(max_tier):] - native_area = crop_w * crop_h - for tier in order: - _, (bw, bh) = nearest_bucket(crop_w, crop_h, tier) - if bw * bh <= native_area: - return tier - return order[-1] - - -def pad_bbox(bbox, pad_mult): - x1, y1, x2, y2 = bbox - w, h = x2 - x1, y2 - y1 - cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 - new_w, new_h = w * pad_mult, h * pad_mult - return (cx - new_w / 2, cy - new_h / 2, cx + new_w / 2, cy + new_h / 2) - - -def fit_aspect(bbox, img_w, img_h, target_aspect): - """Expand bbox to match target_aspect (w/h), centered on bbox, then clamp - to image bounds (shift first, hard-clamp as a last resort for oversized - crops).""" - x1, y1, x2, y2 = bbox - w, h = x2 - x1, y2 - y1 - cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 - cur_aspect = w / h if h else target_aspect - - if cur_aspect < target_aspect: - new_h, new_w = h, h * target_aspect - else: - new_w, new_h = w, w / target_aspect - - x1, x2 = cx - new_w / 2, cx + new_w / 2 - y1, y2 = cy - new_h / 2, cy + new_h / 2 - - if x1 < 0: - x2 -= x1 - x1 = 0 - if x2 > img_w: - x1 -= (x2 - img_w) - x2 = img_w - if y1 < 0: - y2 -= y1 - y1 = 0 - if y2 > img_h: - y1 -= (y2 - img_h) - y2 = img_h - - x1, y1 = max(0, x1), max(0, y1) - x2, y2 = min(img_w, x2), min(img_h, y2) - return (x1, y1, x2, y2) - - -def compute_crop(img_w, img_h, bbox, kind, pad_overrides=None, max_tier="1080p"): - """Given an image size and a detected subject bbox, return the crop box - (in original-image pixel space) and the resolution bucket to resize it to. - - Returns: (crop_box, (target_w, target_h), bucket_name, tier) - """ - pad = dict(DEFAULT_PAD) - if pad_overrides: - pad.update(pad_overrides) - - padded = pad_bbox(bbox, pad.get(kind, 1.0)) - pw, ph = padded[2] - padded[0], padded[3] - padded[1] - pw, ph = max(1.0, pw), max(1.0, ph) - - tier = auto_tier(pw, ph, max_tier) - bucket_name, (bw, bh) = nearest_bucket(pw, ph, tier) - crop_box = fit_aspect(padded, img_w, img_h, bw / bh) - return crop_box, (bw, bh), bucket_name, tier - - -def crop_resize_pil(image: Image.Image, crop_box, target_w, target_h): - x1, y1, x2, y2 = (int(round(v)) for v in crop_box) - return image.crop((x1, y1, x2, y2)).resize((target_w, target_h), Image.LANCZOS) - - -def crop_resize_np(frame_bgr: np.ndarray, crop_box, target_w, target_h): - x1, y1, x2, y2 = (int(round(v)) for v in crop_box) - cropped = frame_bgr[y1:y2, x1:x2] - return cv2.resize(cropped, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4) diff --git a/dataset_builder/detect.py b/dataset_builder/detect.py deleted file mode 100644 index 0bebd66957097651da094fc867207092977bab44..0000000000000000000000000000000000000000 --- a/dataset_builder/detect.py +++ /dev/null @@ -1,140 +0,0 @@ -"""Subject bbox detection for smart cropping. - -Priority: mediapipe face detection -> OpenCV Haar cascade face detection -(no model download required, always available with opencv-python) -> -ultralytics YOLOv8n person detection -> full frame (no crop). - -All bboxes are returned as (x1, y1, x2, y2) in pixel space, plus a "kind" -string ("face" | "person" | "full_frame") used by crop_resize.py to pick a -padding multiplier. -""" -import cv2 -import numpy as np - -_mp_detector = None -_mp_unavailable = False -_haar_cascade = None -_yolo_model = None - - -def _get_mediapipe_detector(min_conf): - global _mp_detector, _mp_unavailable - if _mp_unavailable: - return None - if _mp_detector is None: - try: - import mediapipe as mp - _mp_detector = mp.solutions.face_detection.FaceDetection( - model_selection=1, min_detection_confidence=min_conf - ) - except Exception: - _mp_unavailable = True - return None - return _mp_detector - - -def _get_haar_cascade(): - global _haar_cascade - if _haar_cascade is None: - path = cv2.data.haarcascades + "haarcascade_frontalface_default.xml" - _haar_cascade = cv2.CascadeClassifier(path) - return _haar_cascade - - -def _get_yolo(): - global _yolo_model - if _yolo_model is None: - from ultralytics import YOLO - _yolo_model = YOLO("yolov8n.pt") - return _yolo_model - - -def _largest_bbox(candidates): - """candidates: list of (x1, y1, x2, y2). Returns the largest-area one.""" - if not candidates: - return None - return max(candidates, key=lambda b: max(0, b[2] - b[0]) * max(0, b[3] - b[1])) - - -def _detect_face_mediapipe(image_bgr, min_conf): - detector = _get_mediapipe_detector(min_conf) - if detector is None: - return None - h, w = image_bgr.shape[:2] - rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) - try: - results = detector.process(rgb) - except Exception: - return None - if not results.detections: - return None - boxes = [] - for det in results.detections: - rb = det.location_data.relative_bounding_box - x1, y1 = rb.xmin * w, rb.ymin * h - x2, y2 = x1 + rb.width * w, y1 + rb.height * h - boxes.append((x1, y1, x2, y2)) - return _largest_bbox(boxes) - - -def _detect_face_haar(image_bgr): - cascade = _get_haar_cascade() - gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY) - faces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(40, 40)) - if len(faces) == 0: - return None - boxes = [(float(x), float(y), float(x + fw), float(y + fh)) for x, y, fw, fh in faces] - return _largest_bbox(boxes) - - -def _detect_person_yolo(image_bgr, min_conf): - try: - model = _get_yolo() - results = model.predict(image_bgr, classes=[0], conf=min_conf, verbose=False) - except Exception: - return None - boxes = [] - for r in results: - if r.boxes is None: - continue - for b in r.boxes: - x1, y1, x2, y2 = b.xyxy[0].tolist() - boxes.append((x1, y1, x2, y2)) - return _largest_bbox(boxes) - - -def detect_subject_bbox(image_bgr: np.ndarray, min_face_conf=0.5, min_person_conf=0.35): - """Returns (bbox, kind) where kind is 'face', 'person', or 'full_frame'.""" - h, w = image_bgr.shape[:2] - - bbox = _detect_face_mediapipe(image_bgr, min_face_conf) - if bbox: - return bbox, "face" - - bbox = _detect_face_haar(image_bgr) - if bbox: - return bbox, "face" - - bbox = _detect_person_yolo(image_bgr, min_person_conf) - if bbox: - return bbox, "person" - - return (0.0, 0.0, float(w), float(h)), "full_frame" - - -def subdivide_bbox(bbox, region, frac=0.45): - """Narrows a bbox to its top or bottom portion (by height), same width. - - Used to bias person-kind crops toward a specific body region (e.g. most - of the frame is a torso/lower-body shot rather than a full figure) without - needing a specialized body-part detector -- just a fraction of the - already-detected person bbox. `region` is "upper", "lower", or anything - else (returned unchanged, e.g. "auto"/"full"). - """ - x1, y1, x2, y2 = bbox - h = y2 - y1 - if region == "lower": - return (x1, y2 - h * frac, x2, y2) - if region == "upper": - return (x1, y1, x2, y1 + h * frac) - return bbox diff --git a/dataset_builder/inspect_lora.py b/dataset_builder/inspect_lora.py deleted file mode 100644 index 6251fa2a5ca8fab3aac31a38984743e69301b6f1..0000000000000000000000000000000000000000 --- a/dataset_builder/inspect_lora.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Inspect a LoRA .safetensors file: embedded training metadata + inferred -rank/target modules from tensor shapes. Read-only, no GPU/torch needed -- -just parses the safetensors header, so it's fast and safe to run on any -LoRA file (including ones you didn't train yourself). - -Usage: - python_embeded\\python.exe dataset_builder\\inspect_lora.py path\\to\\lora.safetensors -""" -import argparse -import json -import re -from collections import Counter - -from safetensors import safe_open - - -def parse_args(): - p = argparse.ArgumentParser(description="Inspect a LoRA safetensors file's metadata") - p.add_argument("lora_path") - return p.parse_args() - - -def infer_rank(keys, shapes): - for k in keys: - if k.endswith("lora_A.weight") or k.endswith("lora_down.weight"): - return shapes[k][0] - return None - - -def summarize_target_modules(keys): - """Collapses per-block keys (blocks.0, blocks.1, ...) into module families - so you can see what parts of the model were trained without a 800-line dump.""" - families = Counter() - for k in keys: - base = re.sub(r"\.(lora_A|lora_B|lora_down|lora_up|alpha)(\.weight)?$", "", k) - base = re.sub(r"\.\d+\.", ".N.", base) - families[base] += 1 - return families - - -def main(): - args = parse_args() - with safe_open(args.lora_path, framework="pt") as f: - meta = f.metadata() or {} - keys = list(f.keys()) - shapes = {k: f.get_slice(k).get_shape() for k in keys} - - print(f"=== {args.lora_path} ===") - print(f"Tensor count: {len(keys)}") - print(f"Inferred rank (lora_A/lora_down first dim): {infer_rank(keys, shapes)}") - - print("\n--- Embedded metadata ---") - if not meta: - print("(none)") - for k, v in meta.items(): - try: - parsed = json.loads(v) - print(f"{k}:") - print(json.dumps(parsed, indent=2, ensure_ascii=False)[:3000]) - except (json.JSONDecodeError, TypeError): - print(f"{k}: {v}") - - print("\n--- Target module families (per-block keys collapsed) ---") - for base, count in summarize_target_modules(keys).most_common(30): - print(f" {base} x{count}") - - -if __name__ == "__main__": - main() diff --git a/dataset_builder/scene_split.py b/dataset_builder/scene_split.py deleted file mode 100644 index de689ace60a1743aaa82b3d1084eda0aaa185c2a..0000000000000000000000000000000000000000 --- a/dataset_builder/scene_split.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Video scene-cut detection, clip extraction, and subject-centered crop/resize. - -Uses plain (non-relative) imports of sibling modules so this file works whether -invoked as ``python dataset_builder/build.py`` or ``python -m dataset_builder.build`` -(build.py inserts its own directory onto sys.path before importing). -""" -import math -import statistics - -import cv2 -import imageio -import numpy as np -from PIL import Image - -import crop_resize -from detect import detect_subject_bbox, subdivide_bbox - - -def _frame_hist(frame_bgr): - hsv = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2HSV) - hist = cv2.calcHist([hsv], [0, 1], None, [50, 60], [0, 180, 0, 256]) - cv2.normalize(hist, hist) - return hist - - -def find_scene_cuts(video_path, diff_thresh=0.45, sample_stride=1): - """Returns a sorted list of frame indices where a scene cut is detected, - via Bhattacharyya distance between consecutive HSV histograms.""" - cap = cv2.VideoCapture(video_path) - cuts = [] - prev_hist = None - idx = 0 - while True: - ok, frame = cap.read() - if not ok: - break - if idx % sample_stride == 0: - hist = _frame_hist(frame) - if prev_hist is not None: - score = cv2.compareHist(prev_hist, hist, cv2.HISTCMP_BHATTACHARYYA) - if score > diff_thresh: - cuts.append(idx) - prev_hist = hist - idx += 1 - cap.release() - return cuts - - -def build_clip_ranges(total_frames, fps, cuts, clip_min_sec=2.0, clip_max_sec=6.0): - """Turns scene-cut frame indices into (start, end) frame ranges honoring - clip_min/max seconds: too-short segments are merged forward, segments - longer than clip_max are chopped into roughly-equal sub-segments.""" - min_frames = max(1, int(clip_min_sec * fps)) - max_frames = max(min_frames, int(clip_max_sec * fps)) - - boundaries = [0] + sorted(set(c for c in cuts if 0 < c < total_frames)) + [total_frames] - raw_ranges = [] - start = boundaries[0] - for b in boundaries[1:]: - if b - start >= min_frames: - raw_ranges.append((start, b)) - start = b - if start < total_frames: - if raw_ranges and (total_frames - start) < min_frames: - s, _ = raw_ranges[-1] - raw_ranges[-1] = (s, total_frames) - else: - raw_ranges.append((start, total_frames)) - - ranges = [] - for s, e in raw_ranges: - length = e - s - if length <= max_frames: - ranges.append((s, e)) - continue - n_chunks = math.ceil(length / max_frames) - chunk_len = length // n_chunks - for i in range(n_chunks): - cs = s + i * chunk_len - ce = e if i == n_chunks - 1 else cs + chunk_len - ranges.append((cs, ce)) - - return [(s, e) for s, e in ranges if e - s >= max(1, min_frames // 2)] - - -def _sample_indices(start, end, n=3): - if end - start <= n: - return list(range(start, end)) - step = (end - start) / (n + 1) - return [int(start + step * (i + 1)) for i in range(n)] - - -def _median_bbox(bboxes): - return ( - statistics.median(b[0] for b in bboxes), - statistics.median(b[1] for b in bboxes), - statistics.median(b[2] for b in bboxes), - statistics.median(b[3] for b in bboxes), - ) - - -def _motion_tag(frames_gray): - if len(frames_gray) < 2: - return "static, minimal motion" - mags = [] - for a, b in zip(frames_gray, frames_gray[1:]): - flow = cv2.calcOpticalFlowFarneback(a, b, None, 0.5, 3, 15, 3, 5, 1.2, 0) - mag, _ = cv2.cartToPolar(flow[..., 0], flow[..., 1]) - mags.append(float(np.mean(mag))) - avg = sum(mags) / len(mags) - if avg < 0.5: - return "static, minimal motion" - if avg < 2.0: - return "subtle motion" - if avg < 5.0: - return "moderate motion" - return "fast, dynamic motion" - - -def extract_clip(video_path, start, end, target_fps, max_tier="1080p", pad_overrides=None, - body_region="auto", body_region_frac=0.45): - """Reads frames [start, end) from video_path, computes one stable subject - crop for the whole clip (median bbox over sampled frames), resamples to - target_fps, trims to a 4n+1 frame count, and returns - (frames_bgr, (target_w, target_h), representative_pil_image, motion_tag), - or None if no frames could be read.""" - cap = cv2.VideoCapture(video_path) - src_fps = cap.get(cv2.CAP_PROP_FPS) or target_fps - img_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - img_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - - bboxes, kinds = [], [] - for idx in _sample_indices(start, end, n=3): - cap.set(cv2.CAP_PROP_POS_FRAMES, idx) - ok, frame = cap.read() - if not ok: - continue - bbox, kind = detect_subject_bbox(frame) - bboxes.append(bbox) - kinds.append(kind) - - if not bboxes: - cap.release() - return None - - bbox = _median_bbox(bboxes) - kind = max(set(kinds), key=kinds.count) - if kind == "person" and body_region != "auto": - bbox = subdivide_bbox(bbox, body_region, body_region_frac) - kind = f"person_{body_region}" - crop_box, (tw, th), _bucket_name, _tier = crop_resize.compute_crop( - img_w, img_h, bbox, kind, pad_overrides, max_tier - ) - - step = src_fps / target_fps - raw_indices = [] - f = float(start) - while f < end: - raw_indices.append(int(round(f))) - f += step - if len(raw_indices) >= 5: - n = ((len(raw_indices) - 1) // 4) * 4 + 1 - raw_indices = raw_indices[:n] - - frames_out, frames_gray = [], [] - for idx in raw_indices: - cap.set(cv2.CAP_PROP_POS_FRAMES, idx) - ok, frame = cap.read() - if not ok: - continue - cropped = crop_resize.crop_resize_np(frame, crop_box, tw, th) - frames_out.append(cropped) - if len(frames_gray) < 6: - frames_gray.append(cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY)) - cap.release() - - if not frames_out: - return None - - motion_tag = _motion_tag(frames_gray) - mid = frames_out[len(frames_out) // 2] - representative = Image.fromarray(cv2.cvtColor(mid, cv2.COLOR_BGR2RGB)) - - return frames_out, (tw, th), representative, motion_tag - - -def write_clip_mp4(frames_bgr, out_path, fps): - with imageio.get_writer(str(out_path), fps=fps, codec="libx264", quality=8) as writer: - for frame in frames_bgr: - writer.append_data(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) diff --git a/dataset_builder/start_webui.bat b/dataset_builder/start_webui.bat deleted file mode 100644 index 5a3c50c15c5d81f992a1dd97d59154d118e2a39b..0000000000000000000000000000000000000000 --- a/dataset_builder/start_webui.bat +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -setlocal - -set "HERE=%~dp0" -set "PYTHON=%HERE%..\..\..\..\python_embeded\python.exe" - -if not exist "%PYTHON%" ( - echo Could not find python_embeded at: - echo %PYTHON% - echo This .bat expects to live at ComfyUI\custom_nodes\dolphin_nodes\dataset_builder\ - pause - exit /b 1 -) - -echo Starting Wan 2.2 LoRA web UI... -echo Once it says "Running on local URL", open http://127.0.0.1:7860 in your browser. -echo. - -"%PYTHON%" "%HERE%webui.py" %* - -pause diff --git a/dataset_builder/webui.py b/dataset_builder/webui.py deleted file mode 100644 index 13d1dabcbee8721e321170a0aedd19e59eb10eaf..0000000000000000000000000000000000000000 --- a/dataset_builder/webui.py +++ /dev/null @@ -1,402 +0,0 @@ -"""Local web UI for the Wan 2.2 LoRA dataset builder + trainer. - -Thin Gradio layer over the already-verified CLI scripts (build.py, -train/train.py, train/download_checkpoints.py, train/setup_env.ps1) -- this -file does not reimplement any of their logic, it only shells out to them and -streams stdout live. Heavy ML models (captioners, torch) never load inside -this long-lived server process; each run is its own subprocess so a crash or -this environment's known torch-teardown quirk can't take the UI down with it. - -Run: - python_embeded\\python.exe dataset_builder\\webui.py [--port 7860] -""" -import argparse -import os -import subprocess -import sys - -import gradio as gr - -HERE = os.path.dirname(os.path.abspath(__file__)) -TRAIN_DIR = os.path.join(HERE, "train") -VENV_PYTHON = os.path.join(TRAIN_DIR, "venv", "Scripts", "python.exe") -PYTHON = sys.executable # python_embeded's own interpreter (has build.py's deps) - -sys.path.insert(0, TRAIN_DIR) -from download_checkpoints import FILES, COMMON_FILES # noqa: E402 - -# ComfyUI/custom_nodes/dolphin_nodes/dataset_builder/webui.py -> ComfyUI/models -MODELS_ROOT = os.path.normpath(os.path.join(HERE, "..", "..", "..", "models")) - - -# Any python_embeded process that imports torch on this machine can exit with -# this NTSTATUS during CUDA teardown *after* finishing its real work (confirmed -# for both dataset_builder/train/train.py and build.py itself, e.g. via -# caption.py's torch import) -- see dataset_builder/train/README.md's bug list. -# Reported as either the signed or unsigned 32-bit form depending on context. -BENIGN_TORCH_TEARDOWN_NTSTATUS = 0xC0000409 # STATUS_STACK_BUFFER_OVERRUN - - -EMBEDED_DIR = os.path.dirname(PYTHON) - - -def child_env(): - """Environment for subprocesses, scrubbed of this server's own Python. - - python_embeded is Python 3.13 and ships python313.dll in its own directory. - A child running the training venv's Python 3.10 will happily load that - 3.13 DLL if python_embeded is reachable via PATH or the inherited working - directory, and then every stdlib C extension fails with "Module use of - python313.dll conflicts with this version of Python" (confirmed twice - through this UI). Dropping python_embeded from PATH -- combined with the - fixed cwd below -- keeps the child's DLL search away from it. - """ - env = {**os.environ, "PYTHONIOENCODING": "utf-8", "PYTHONUNBUFFERED": "1"} - parts = env.get("PATH", "").split(os.pathsep) - embeded = os.path.normcase(EMBEDED_DIR) - env["PATH"] = os.pathsep.join( - p for p in parts if not os.path.normcase(p).startswith(embeded) - ) - return env - - -def stream_subprocess(cmd): - """Runs cmd, yielding the accumulated stdout text as new lines arrive.""" - proc = subprocess.Popen( - cmd, env=child_env(), cwd=HERE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, encoding="utf-8", errors="replace", bufsize=1, - ) - log = "" - for line in proc.stdout: - log += line - yield log - proc.wait() - if proc.returncode not in (0, None): - if (proc.returncode & 0xFFFFFFFF) == BENIGN_TORCH_TEARDOWN_NTSTATUS: - log += "\n(non-fatal: torch/CUDA teardown crash after finishing its work -- known environment quirk)" - else: - log += f"\n[exit code {proc.returncode}]" - yield log - - -# --------------------------------------------------------------------------- -# Checkpoint scanning (shared by Setup + Training tabs) -# --------------------------------------------------------------------------- - -def checkpoint_status_rows(): - rows = [] - for _repo, path, subdir, size in FILES["fp16"] + COMMON_FILES: - dest = os.path.join(MODELS_ROOT, subdir, os.path.basename(path)) - ok = os.path.exists(dest) - actual = f"{os.path.getsize(dest) / 1e9:.1f}GB" if ok else "-" - rows.append([os.path.basename(path), "있음" if ok else "없음", f"{size / 1e9:.1f}GB", actual]) - return rows - - -def venv_status_text(): - return "설치됨" if os.path.exists(VENV_PYTHON) else "설치 안 됨" - - -def list_checkpoints(subdir, name_filter=None, exclude=None): - d = os.path.join(MODELS_ROOT, subdir) - if not os.path.isdir(d): - return [] - files = os.listdir(d) - if name_filter: - files = [f for f in files if name_filter in f.lower()] - if exclude: - files = [f for f in files if exclude not in f.lower()] - return [os.path.join(d, f) for f in sorted(files)] - - -def list_dit_choices(): - # fp8_scaled DiT files can't be used for training (confirmed this session: - # musubi-tuner's loader rejects ComfyUI's pre-quantized scaled-fp8 format - # both with and without --fp8-scaled) -- excluded here so the dropdown - # can't steer anyone into that dead end. - return list_checkpoints("diffusion_models", name_filter="wan2.2_i2v", exclude="fp8_scaled") - - -def list_t5_choices(): - return list_checkpoints("text_encoders", name_filter="t5") - - -def list_vae_choices(): - return list_checkpoints("vae", name_filter="wan2.1_vae") - - -# --------------------------------------------------------------------------- -# Setup tab -# --------------------------------------------------------------------------- - -def run_setup_env(): - yield from stream_subprocess(["powershell.exe", "-File", os.path.join(TRAIN_DIR, "setup_env.ps1"), "-Yes"]) - - -def run_download_checkpoints(): - yield from stream_subprocess([ - PYTHON, os.path.join(TRAIN_DIR, "download_checkpoints.py"), - "--models-root", MODELS_ROOT, "--precision", "fp16", "--yes", - ]) - - -def build_setup_tab(): - with gr.Tab("Setup"): - gr.Markdown( - "학습에는 fp16 체크포인트가 필요합니다 (fp8_scaled는 ComfyUI 추론 전용 — " - "musubi-tuner 학습 로더가 못 읽는 것으로 확인됨). 아래에서 학습 환경/체크포인트 " - "상태를 확인하고 없는 것만 받으세요." - ) - with gr.Row(): - venv_box = gr.Textbox(label="학습 환경(venv)", value=venv_status_text(), interactive=False, scale=1) - setup_btn = gr.Button("학습 환경 설치 (venv + musubi-tuner)", scale=2) - setup_log = gr.Textbox(label="설치 로그", lines=10, max_lines=20, interactive=False) - setup_btn.click(run_setup_env, outputs=setup_log).then(venv_status_text, outputs=venv_box) - - gr.Markdown("### 체크포인트") - ckpt_table = gr.Dataframe( - headers=["파일", "상태", "예상 크기", "실제 크기"], - value=checkpoint_status_rows(), interactive=False, - ) - with gr.Row(): - refresh_btn = gr.Button("상태 새로고침") - confirm_dl = gr.Checkbox(label="위 목록 중 없는 fp16 체크포인트를 다운로드하겠습니다 (총 수십GB)") - dl_btn = gr.Button("다운로드", interactive=False) - dl_log = gr.Textbox(label="다운로드 로그", lines=10, max_lines=20, interactive=False) - - refresh_btn.click(checkpoint_status_rows, outputs=ckpt_table) - confirm_dl.change(lambda v: gr.update(interactive=v), inputs=confirm_dl, outputs=dl_btn) - dl_btn.click(run_download_checkpoints, outputs=dl_log).then(checkpoint_status_rows, outputs=ckpt_table) - - -# --------------------------------------------------------------------------- -# Dataset Builder tab -# --------------------------------------------------------------------------- - -def run_build(input_dir, output_dir, trigger, captioner, max_tier, face_pad, person_pad, - video_fps, clip_min_sec, clip_max_sec, image_format, clips_per_video, - body_region, body_region_frac): - if not input_dir or not output_dir: - yield "입력/출력 폴더를 모두 입력하세요." - return - cmd = [ - PYTHON, os.path.join(HERE, "build.py"), - "--input", input_dir, "--output", output_dir, "--trigger", trigger, - "--captioner", captioner, "--max-tier", max_tier, - "--face-pad", str(face_pad), "--person-pad", str(person_pad), - "--video-fps", str(video_fps), "--clip-min-sec", str(clip_min_sec), - "--clip-max-sec", str(clip_max_sec), "--image-format", image_format, - "--body-region", body_region, "--body-region-frac", str(body_region_frac), - "--clips-per-video", str(int(clips_per_video or 0)), - ] - yield from stream_subprocess(cmd) - - -def preview_gallery(output_dir): - img_dir = os.path.join(output_dir or "", "images") - if not os.path.isdir(img_dir): - return [] - files = sorted(f for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".png", ".webp"))) - return [os.path.join(img_dir, f) for f in files[:12]] - - -def build_dataset_tab(dataset_dir_handoff): - with gr.Tab("Dataset Builder"): - gr.Markdown("임의의 이미지/영상 폴더를 크롭+캡션 데이터셋으로 변환합니다.") - with gr.Row(): - input_dir = gr.Textbox(label="입력 폴더", placeholder=r"D:\raw_media") - output_dir = gr.Textbox(label="출력 폴더", placeholder=r"D:\wan_lora_dataset") - with gr.Row(): - trigger = gr.Textbox(label="트리거 워드", placeholder="ohwx man") - captioner = gr.Radio(["joycaption", "florence2", "wd14"], value="joycaption", label="캡셔너") - - with gr.Accordion("고급 설정", open=False): - with gr.Row(): - max_tier = gr.Radio(["480p", "720p", "1080p"], value="1080p", label="최대 해상도 티어") - image_format = gr.Radio(["jpg", "png"], value="jpg", label="이미지 저장 포맷") - with gr.Row(): - face_pad = gr.Number(value=2.2, label="얼굴 크롭 패딩") - person_pad = gr.Number(value=1.3, label="사람 크롭 패딩") - with gr.Row(): - body_region = gr.Radio( - ["auto", "upper", "lower"], value="auto", label="신체 부위 우선 크롭", - info="대부분 소스가 이미 특정 부위 클로즈업이면 upper/lower로 그쪽만 좁혀서 크롭", - ) - body_region_frac = gr.Number(value=0.45, label="부위 크롭 비율 (bbox 높이 중 남길 비율)") - with gr.Row(): - video_fps = gr.Number(value=16, label="영상 출력 FPS") - clip_min_sec = gr.Number(value=2.0, label="클립 최소 길이(초)") - clip_max_sec = gr.Number(value=6.0, label="클립 최대 길이(초)") - clips_per_video = gr.Number( - value=5, label="영상 1개당 클립 수 제한 (0이면 제한 없음)", - info="긴 영상(예: 5분)에서 씬 컷이 많이 잡혀 클립이 과하게 늘어날 때, " - "영상 전체 구간에 걸쳐 균등하게 N개만 뽑도록 제한합니다.", - ) - - build_btn = gr.Button("데이터셋 만들기", variant="primary") - build_log = gr.Textbox(label="로그", lines=12, max_lines=25, interactive=False) - gallery = gr.Gallery(label="결과 미리보기 (이미지)", columns=4, height=300) - send_btn = gr.Button("→ 학습 탭에 이 출력 폴더 채우기") - - build_btn.click( - run_build, - inputs=[input_dir, output_dir, trigger, captioner, max_tier, face_pad, person_pad, - video_fps, clip_min_sec, clip_max_sec, image_format, clips_per_video, - body_region, body_region_frac], - outputs=build_log, - ).then(preview_gallery, inputs=output_dir, outputs=gallery) - - send_btn.click(lambda d: d, inputs=output_dir, outputs=dataset_dir_handoff) - - -# --------------------------------------------------------------------------- -# Training tab -# --------------------------------------------------------------------------- - -def pick_default(choices, name_hint): - """Prefer a choice whose filename contains name_hint (e.g. 'low_noise'); - plain alphabetical order would otherwise default the low-noise dropdown - to the high_noise file since 'h' < 'l'.""" - for c in choices: - if name_hint in os.path.basename(c).lower(): - return c - return choices[0] if choices else None - - -def infer_mixed_precision(dit_low_path): - name = (dit_low_path or "").lower() - if "bf16" in name: - return "bf16" - return "fp16" - - -def run_train(dataset_dir, dit_low, dit_high, t5, vae, network_dim, learning_rate, - use_steps, max_train_steps, max_train_epochs, mixed_precision, - separate_passes, skip_cache, output_dir): - if not all([dataset_dir, dit_low, t5, vae, output_dir]): - yield "dataset-dir / DiT-low / T5 / VAE / output-dir는 필수입니다." - return - if not os.path.exists(VENV_PYTHON): - yield f"학습 환경(venv)이 없습니다: {VENV_PYTHON}\nSetup 탭에서 먼저 설치하세요." - return - cmd = [ - # train.py itself has no heavy deps, but running it with python_embeded - # (which on this machine is Python 3.13, sitting on a PATH with several - # other Python installs) leaked into the venv subprocess's inherited - # environment and broke stdlib C-extension loading in the venv (Python - # 3.10) with "Module use of python313.dll conflicts with this version - # of Python" -- confirmed via a real run through this UI. Running - # train.py with the venv's own interpreter keeps the whole chain - # self-consistent, matching every successful CLI run this session. - VENV_PYTHON, os.path.join(TRAIN_DIR, "train.py"), - "--dataset-dir", dataset_dir, "--dit-low", dit_low, "--t5", t5, "--vae", vae, - "--network-dim", str(int(network_dim)), "--learning-rate", str(learning_rate), - "--mixed-precision", mixed_precision, "--output-dir", output_dir, - ] - if dit_high: - cmd += ["--dit-high", dit_high] - if use_steps: - cmd += ["--max-train-steps", str(int(max_train_steps))] - else: - cmd += ["--max-train-epochs", str(int(max_train_epochs))] - if separate_passes: - cmd.append("--separate-passes") - if skip_cache: - cmd.append("--skip-cache") - yield from stream_subprocess(cmd) - - -def build_training_tab(dataset_dir_handoff): - with gr.Tab("Training"): - gr.Markdown( - "⚠️ ComfyUI를 켜 둔 채로 학습하면 VRAM이 부족할 수 있습니다 — 가능하면 " - "학습 중엔 ComfyUI 서버를 내려두세요." - ) - dataset_dir = gr.Textbox(label="데이터셋 폴더 (dataset.toml이 있는 곳)") - dataset_dir_handoff.change(lambda d: d, inputs=dataset_dir_handoff, outputs=dataset_dir) - - dit_choices = list_dit_choices() - t5_choices = list_t5_choices() - vae_choices = list_vae_choices() - with gr.Row(): - dit_low = gr.Dropdown(label="DiT (low-noise)", choices=dit_choices, - value=pick_default(dit_choices, "low_noise")) - dit_high = gr.Dropdown(label="DiT (high-noise, 선택)", choices=dit_choices, - value=pick_default(dit_choices, "high_noise")) - with gr.Row(): - t5 = gr.Dropdown(label="T5 텍스트 인코더", choices=t5_choices, - value=t5_choices[0] if t5_choices else None) - vae = gr.Dropdown(label="VAE", choices=vae_choices, - value=pick_default(vae_choices, "wan2.1_vae")) - refresh_ckpt_btn = gr.Button("체크포인트 목록 새로고침 (Setup 탭에서 다운로드한 뒤 눌러주세요)") - - with gr.Row(): - network_dim = gr.Number(value=32, label="LoRA rank (network_dim)") - learning_rate = gr.Number(value=2e-4, label="Learning rate") - mixed_precision = gr.Radio(["fp16", "bf16", "no"], value="fp16", label="Mixed precision") - - with gr.Row(): - use_steps = gr.Checkbox(label="스텝 수로 제한 (스모크 테스트용)", value=False) - max_train_steps = gr.Number(value=5, label="max_train_steps") - max_train_epochs = gr.Number(value=16, label="max_train_epochs") - - with gr.Row(): - separate_passes = gr.Checkbox(label="high/low 따로 학습 (파일 2개, 시간 2배)", value=False) - skip_cache = gr.Checkbox(label="캐싱 단계 건너뛰기 (이미 캐싱된 데이터셋)", value=False) - - output_dir = gr.Textbox(label="출력 폴더", placeholder=r"D:\wan_lora_output") - train_btn = gr.Button("학습 시작", variant="primary") - train_log = gr.Textbox(label="로그", lines=16, max_lines=30, interactive=False) - - def refresh_dit_low(): - c = list_dit_choices() - return gr.update(choices=c, value=pick_default(c, "low_noise")) - - def refresh_dit_high(): - c = list_dit_choices() - return gr.update(choices=c, value=pick_default(c, "high_noise")) - - def refresh_t5(): - c = list_t5_choices() - return gr.update(choices=c, value=c[0] if c else None) - - def refresh_vae(): - c = list_vae_choices() - return gr.update(choices=c, value=pick_default(c, "wan2.1_vae")) - - refresh_ckpt_btn.click(refresh_dit_low, outputs=dit_low) - refresh_ckpt_btn.click(refresh_dit_high, outputs=dit_high) - refresh_ckpt_btn.click(refresh_t5, outputs=t5) - refresh_ckpt_btn.click(refresh_vae, outputs=vae) - dit_low.change(infer_mixed_precision, inputs=dit_low, outputs=mixed_precision) - - train_btn.click( - run_train, - inputs=[dataset_dir, dit_low, dit_high, t5, vae, network_dim, learning_rate, - use_steps, max_train_steps, max_train_epochs, mixed_precision, - separate_passes, skip_cache, output_dir], - outputs=train_log, - ) - - -# --------------------------------------------------------------------------- - -def build_app(): - with gr.Blocks(title="Wan 2.2 LoRA Builder") as app: - gr.Markdown("# 🐬 Wan 2.2 LoRA Dataset + Training") - build_setup_tab() - dataset_dir_handoff = gr.Textbox(visible=False) - build_dataset_tab(dataset_dir_handoff) - build_training_tab(dataset_dir_handoff) - return app - - -def parse_args(): - p = argparse.ArgumentParser() - p.add_argument("--port", type=int, default=7860) - return p.parse_args() - - -if __name__ == "__main__": - args = parse_args() - build_app().queue().launch(server_name="127.0.0.1", server_port=args.port, share=False) diff --git a/dolphin_tags_cache.json b/dolphin_tags_cache.json deleted file mode 100644 index b9963530381abfebfdb5091d164be2aba39e7634..0000000000000000000000000000000000000000 --- a/dolphin_tags_cache.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "wan2.1-i2v-480p-penis-arousal-v1.0.safetensors::359305272::1754578081": [ - "penis becomes erect" - ], - "wan2.1-i2v-480p-flaccid-v1.0.safetensors::359305272::1753134483": [ - "penis", - "bounce", - "swing" - ], - "PENISLORA_22_i2v_HIGH_e320.safetensors::306807976::1759901296": [ - "PENISLORA" - ], - "PENISLORA_22_i2v_LOW_e496.safetensors::306807976::1759901312": [ - "PENISLORA" - ], - "wa22-4n4lfi5tin6-40epoc-low-k3nk.safetensors::306807976::1783607997": [ - "4n4lfi5tin6", - "4n4lfi5tin6, a close-up rear view of a woman engaging in anal fisting with her arm inside her anus." - ], - "wa22-4n4lfi5tin6-93epoc-high-k3nk.safetensors::306807976::1783607988": [ - "4n4lfi5tin6", - "4n4lfi5tin6, a close-up rear view of a person engaging in anal fisting." - ], - "DR34ML4Y_I2V_14B_HIGH_V2.safetensors::306807976::1767355819": [ - "m15510n4ry", - "bl0wj0b", - "c0wg1rl", - "d0gg1e", - "d0ubl3_bj" - ], - "DR34ML4Y_I2V_14B_LOW_V2.safetensors::306807976::1767355809": [ - "m15510n4ry", - "bl0wj0b", - "d0gg1e", - "c0wg1rl", - "d0ubl3_bj" - ], - "sfbehind_v2.1_high_noise.safetensors::306808416::1767009289": [ - "sfb3hind, sfbehind" - ], - "sfbehind_v2.1_low_noise.safetensors::306808416::1767009298": [ - "sfb3hind, sfbehind" - ], - "side-doggy-wan22-hn-40.safetensors::306807976::1779664874": [ - "side view doggystyle sex" - ], - "side-doggy-wan22-ln-57.safetensors::306807976::1779664861": [ - "side view doggystyle sex" - ], - "Pornmaster_wan 2.2_14b_I2V_Creampie_v1_high_noise.safetensors::306808560::1762906783": [ - "A woman, the camera quickly pulls back to reveal her vagina, with sperm on her vagina, and the sperm continues to drip down from her vagina," - ], - "creampie_wan22_e50_low.safetensors::306807976::1758341193": [ - "creampie" - ], - "erect_penis_epoch_80.safetensors::306807976::1744443760": [ - "penis" - ], - "masturbation_cumshot_v1.1_e310.safetensors::359257680::1747718457": [ - "which lands on her {thighs|pelvis|stomach|face|breasts}", - "She moves her hand rapidly up and down the shaft of her penis and she ejaculates white sticky liquid from her penis" - ] -} \ No newline at end of file diff --git a/promp_logic_v28_stitch.py b/promp_logic_v28_stitch.py deleted file mode 100644 index 51c019b75ac242b009de78f0b8c5a2f34db803a0..0000000000000000000000000000000000000000 --- a/promp_logic_v28_stitch.py +++ /dev/null @@ -1,128 +0,0 @@ -import os -import random -import re -import json -import urllib.request -import urllib.error - -class DolphinMultiActionPromptNode_V28: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mode": (["🤖 Auto LLM (Timeline)", "✍️ Manual Override (수동)"], {"default": "🤖 Auto LLM (Timeline)"}), - "character_name": ("STRING", {"multiline": False, "default": "AUTO"}), - "tagger_context": ("STRING", {"multiline": True, "default": ""}), - "artistic_vibe": ("STRING", {"multiline": True, "default": "cinematic lighting, dynamic motion"}), - - "action_part_1": ("STRING", {"multiline": True, "default": "문을 열고 뛰어든다."}), - "action_part_2": ("STRING", {"multiline": True, "default": "무릎을 꿇고 방어한다."}), - "action_part_3": ("STRING", {"multiline": True, "default": "파이프를 집어 던진다."}), - "action_part_4": ("STRING", {"multiline": True, "default": "쓰러진 적을 바라보며 숨을 고른다."}), - - "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}), - "openrouter_model": ("STRING", {"multiline": False, "default": "meta-llama/llama-3.3-70b-instruct"}), - # 🔥 [추천] AI가 너무 앞서간다면 이 수치를 0.65 ~ 0.7 정도로 낮춰보세요. - "creativity": ("FLOAT", {"default": 0.85, "min": 0.1, "max": 1.5, "step": 0.05}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - }, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING") - 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)") - FUNCTION = "generate_sequence" - CATEGORY = "Dolphin" - - def generate_sequence(self, mode, character_name, tagger_context, artistic_vibe, action_part_1, action_part_2, action_part_3, action_part_4, openrouter_api_key, openrouter_model, creativity, seed): - - user_defined_name = "" if character_name.upper() in ["AUTO", ""] else character_name.strip() - - # 🔥 [줄바꿈 로직] 윗줄 태그와 아랫줄 자연어 액션을 엔터(\n)로 깔끔하게 분리 - def build_final(action, raw_tags, master_scene, name): - tags_list = [] - if name: tags_list.append(name) - if raw_tags.strip(): tags_list.append(raw_tags.strip().strip(",. ")) - tag_block = ", ".join(tags_list) - - sentence_list = [] - if master_scene.strip(): sentence_list.append(master_scene.strip().strip(",. ")) - if action.strip(): sentence_list.append(action.strip()) - sentence_block = " ".join(sentence_list) - - if tag_block and sentence_block: - return f"{tag_block}\n{sentence_block}" - elif tag_block: - return tag_block - else: - return sentence_block - - if mode == "✍️ Manual Override (수동)": - return ( - build_final(action_part_1, tagger_context, "", user_defined_name), - build_final(action_part_2, tagger_context, "", user_defined_name), - build_final(action_part_3, tagger_context, "", user_defined_name), - build_final(action_part_4, tagger_context, "", user_defined_name) - ) - - random.seed(seed) - - # 🔥 [완급조절 & 안티-슬로우모션 융합] 미래 액션 스포일러 방지 및 역동성 강제 주입 - sys_prompt = ( - "You are an Elite Cinematic Action Choreographer.\n" - f"1. CHARACTER: If NAME is 'AUTO', assign a culturally appropriate name based on TAGS. If '{user_defined_name}', use it.\n" - "2. INITIAL POSTURE: Analyze TAGS for the character's starting physical state. State it in 1 short sentence.\n" - "3. MASTER SCENE: A vivid 1-sentence environment description based on TAGS and VIBE.\n" - "4. CHOREOGRAPHY & PACING (CRITICAL): You will output 4 Parts. \n" - " - You MUST strictly map [BASE ACTION 1] ONLY to PART 1, [BASE ACTION 2] ONLY to PART 2, etc.\n" - " - DO NOT anticipate, rush, or perform future actions in earlier parts. Keep the pacing deliberate.\n" - " - 🔥 ANTI-SLOW MOTION RULE: Video models default to slow-motion. You MUST inject aggressive kinetic adverbs (e.g., swiftly, rapidly, suddenly, explosively) into EVERY action description to force real-time, fast-paced movement.\n" - " - Each Part is an independent 5-second video clip. Timestamps MUST reset to [0-1s], [2-3s], [4-5s] for EACH Part.\n" - "Format EXACTLY:\nCHARACTER: [Name]\nINITIAL POSTURE: [State]\nMASTER SCENE: [Description]\n" - "PART 1: [0-1s] [Action A] [2-3s] [Action B] [4-5s] [Action C]\n" - "PART 2: [0-1s] [Action D] [2-3s] [Action E] [4-5s] [Action F]\n" - "PART 3: [0-1s] [Action G] [2-3s] [Action H] [4-5s] [Action I]\n" - "PART 4: [0-1s] [Action J] [2-3s] [Action K] [4-5s] [Action L]" - ) - - # 유저 입력을 BASE ACTION 으로 명확히 라벨링하여 전송 - usr_prompt = f"NAME: {character_name}\nTAGS: {tagger_context}\nVIBE: {artistic_vibe}\n[BASE ACTION 1]: {action_part_1}\n[BASE ACTION 2]: {action_part_2}\n[BASE ACTION 3]: {action_part_3}\n[BASE ACTION 4]: {action_part_4}" - - url = "https://openrouter.ai/api/v1/chat/completions" - payload = {"model": openrouter_model.strip(), "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": usr_prompt}], "temperature": creativity} - - try: - req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers={'Authorization': f'Bearer {openrouter_api_key}', 'Content-Type': 'application/json'}) - response = urllib.request.urlopen(req, timeout=120) - llm_prompt = json.loads(response.read().decode('utf-8'))['choices'][0]['message']['content'].strip() - print(f"\n✅ [Dolphin V28 - Stitching Clips]\n{llm_prompt}\n") - except Exception as e: - print(f"❌ [에러] {e}") - llm_prompt = "" - - final_char_name = user_defined_name - r_master, p1, p2, p3, p4 = "", action_part_1, action_part_2, action_part_3, action_part_4 - - if llm_prompt and "[removed]" not in llm_prompt: - cl = re.sub(r'[*#]', '', llm_prompt) - m_char = re.search(r'CHARACTER:\s*(.*?)(?=INITIAL POSTURE|$)', cl, re.I | re.S) - m_master = re.search(r'MASTER SCENE:\s*(.*?)(?=PART 1|$)', cl, re.I | re.S) - m1 = re.search(r'PART 1:\s*(.*?)(?=PART 2|$)', cl, re.I | re.S) - m2 = re.search(r'PART 2:\s*(.*?)(?=PART 3|$)', cl, re.I | re.S) - m3 = re.search(r'PART 3:\s*(.*?)(?=PART 4|$)', cl, re.I | re.S) - m4 = re.search(r'PART 4:\s*(.*)', cl, re.I | re.S) - - if not user_defined_name and m_char: - final_char_name = m_char.group(1).strip() - - r_master = m_master.group(1).strip() if m_master else "" - p1 = m1.group(1).strip() if m1 else action_part_1 - p2 = m2.group(1).strip() if m2 else action_part_2 - p3 = m3.group(1).strip() if m3 else action_part_3 - p4 = m4.group(1).strip() if m4 else action_part_4 - - return ( - build_final(p1, tagger_context, r_master, final_char_name), - build_final(p2, tagger_context, r_master, final_char_name), - build_final(p3, tagger_context, r_master, final_char_name), - build_final(p4, tagger_context, r_master, final_char_name) - ) \ No newline at end of file diff --git a/promp_logic_v30_story.py b/promp_logic_v30_story.py deleted file mode 100644 index e1b32c1de5f86a6957b2133b7db709d81731a2fb..0000000000000000000000000000000000000000 --- a/promp_logic_v30_story.py +++ /dev/null @@ -1,176 +0,0 @@ -import os -import random -import re -import json -import urllib.request -import urllib.error - -class DolphinMultiActionPromptNode_V30: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mode": (["🤖 Auto LLM (Story Splitter)", "✍️ Manual Override (수동)"], {"default": "🤖 Auto LLM (Story Splitter)"}), - "character_name": ("STRING", {"multiline": False, "default": "AUTO"}), - "tagger_context": ("STRING", {"multiline": True, "default": ""}), - "artistic_vibe": ("STRING", {"multiline": True, "default": "cinematic lighting, dynamic motion"}), - - "master_story": ("STRING", { - "multiline": True, - "default": "어두운 골목길. 갑자기 나타난 적들을 향해 돌진한다, 화려하게 검을 휘둘러 적을 쓰러뜨린다, 날아오는 총알을 튕겨낸다, 적에게 다가가 숨통을 끊는다." - }), - - "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}), - "openrouter_model": ("STRING", {"multiline": False, "default": "meta-llama/llama-3.3-70b-instruct"}), - "creativity": ("FLOAT", {"default": 0.85, "min": 0.1, "max": 1.5, "step": 0.05}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - }, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING") - 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)") - FUNCTION = "generate_sequence" - CATEGORY = "Dolphin" - - def generate_sequence(self, mode, character_name, tagger_context, artistic_vibe, master_story, openrouter_api_key, openrouter_model, creativity, seed): - - user_defined_name = "" if character_name.upper() in ["AUTO", ""] else character_name.strip() - - def build_final(action, raw_tags, master_scene, name): - tags_list = [] - if name: tags_list.append(name) - if raw_tags.strip(): tags_list.append(raw_tags.strip().strip(",. ")) - tag_block = ", ".join(tags_list) - - sentence_list = [] - if master_scene.strip(): sentence_list.append(master_scene.strip().strip(",. ")) - if action.strip(): sentence_list.append(action.strip()) - sentence_block = " ".join(sentence_list) - - if tag_block and sentence_block: - return f"{tag_block}\n{sentence_block}" - elif tag_block: - return tag_block - else: - return sentence_block - - if mode == "✍️ Manual Override (수동)": - return ( - build_final(master_story, tagger_context, "", user_defined_name), - build_final(master_story, tagger_context, "", user_defined_name), - build_final(master_story, tagger_context, "", user_defined_name), - build_final(master_story, tagger_context, "", user_defined_name) - ) - - random.seed(seed) - - # 1차: 마침표, 쉼표, 느낌표, 줄바꿈 기준으로 확실하게 쪼갬 - clean_story = re.sub(r'([.!?,\n])', r'\1|', master_story) - raw_sentences = clean_story.split('|') - sentences = [s.strip() for s in raw_sentences if len(s.strip()) > 1] - - # 🔥 [핵심 로직] 문장 개수에 따른 '시네마틱 호흡(Pacing)' 자동 설계 - - if len(sentences) == 1: - # 단 1개의 문장: 1~3번은 분위기 빌드업, 4번에 폭발! - 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]})" - chunk_2 = f"Rising Tension: Subtle character movements or camera panning. Anticipation builds. DO NOT execute the main action yet. (Upcoming target: {sentences[0]})" - 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]})" - chunk_4 = f"CLIMAX: Explosively and swiftly execute this exact action -> {sentences[0]}" - - elif len(sentences) == 2: - # 2개의 문장: 1번 빌드업 -> 2번 첫 액션 -> 3번 호흡 고르기 -> 4번 피니시 - chunk_1 = f"Atmospheric Build-up: Focus strictly on the environment and tension. DO NOT execute the action yet. (Upcoming target: {sentences[0]})" - chunk_2 = f"Phase 1 Action: Execute this action -> {sentences[0]}" - chunk_3 = f"Tension / Transition: Character prepares for the final blow. (Upcoming target: {sentences[1]})" - chunk_4 = f"CLIMAX Action: Explosively execute this action -> {sentences[1]}" - - elif len(sentences) == 3: - # 3개의 문장: 첫 문장을 10초(1,2번 파트)에 걸쳐 부드럽게 배분 - chunk_1 = f"Phase 1: Start this action slowly -> {sentences[0]}" - chunk_2 = f"Phase 2: Complete this action -> {sentences[0]}" - chunk_3 = sentences[1] - chunk_4 = sentences[2] - - else: - # 4개 이상: 기존의 수학적 4등분 배분 - k, m = divmod(len(sentences), 4) - chunks = [] - start = 0 - for i in range(4): - end = start + k + (1 if i < m else 0) - chunks.append(" ".join(sentences[start:end])) - start = end - chunk_1, chunk_2, chunk_3, chunk_4 = chunks - - sys_prompt = ( - "You are an Elite Cinematic Action Choreographer.\n" - f"1. CHARACTER: If NAME is 'AUTO', assign a culturally appropriate name based on TAGS. If '{user_defined_name}', use it.\n" - "2. INITIAL POSTURE: Analyze TAGS for the character's starting physical state. State it in 1 short sentence.\n" - "3. MASTER SCENE: A vivid 1-sentence environment description based on TAGS and VIBE.\n" - "4. STRICT CHOREOGRAPHY ISOLATION (CRITICAL):\n" - " - You will receive 4 explicit target events. You MUST translate them into English cinematic actions.\n" - " - 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" - " - 🔥 OUTPUT RULE: DO NOT quote the original Korean text. ONLY output the English descriptions.\n" - " - 🔥 ANTI-SLOW MOTION RULE: Inject aggressive kinetic adverbs (swiftly, suddenly, explosively) into EVERY action description.\n" - " - Each Part MUST reset timestamps to [0-1s], [2-3s], [4-5s].\n" - "Format EXACTLY:\nCHARACTER: [Name]\nINITIAL POSTURE: [State]\nMASTER SCENE: [Description]\n" - "PART 1: [0-1s] [Action A] [2-3s] [Action B] [4-5s] [Action C]\n" - "PART 2: [0-1s] [Action D] [2-3s] [Action E] [4-5s] [Action F]\n" - "PART 3: [0-1s] [Action G] [2-3s] [Action H] [4-5s] [Action I]\n" - "PART 4: [0-1s] [Action J] [2-3s] [Action K] [4-5s] [Action L]" - ) - - usr_prompt = ( - f"NAME: {character_name}\nTAGS: {tagger_context}\nVIBE: {artistic_vibe}\n\n" - "=== ACTION ISOLATION SCRIPT ===\n" - f"▶ For PART 1 (0-5s), you MUST ONLY animate this specific event: \"{chunk_1}\"\n" - f"▶ For PART 2 (5-10s), you MUST ONLY animate this specific event: \"{chunk_2}\"\n" - f"▶ For PART 3 (10-15s), you MUST ONLY animate this specific event: \"{chunk_3}\"\n" - f"▶ For PART 4 (15-20s), you MUST ONLY animate this specific event: \"{chunk_4}\"\n" - "CRITICAL REMINDER: Do NOT allow any actions from Part 2, 3, or 4 to bleed into Part 1!" - ) - - url = "https://openrouter.ai/api/v1/chat/completions" - payload = {"model": openrouter_model.strip(), "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": usr_prompt}], "temperature": creativity} - - try: - req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers={'Authorization': f'Bearer {openrouter_api_key}', 'Content-Type': 'application/json'}) - response = urllib.request.urlopen(req, timeout=120) - llm_prompt = json.loads(response.read().decode('utf-8'))['choices'][0]['message']['content'].strip() - print(f"\n✅ [Dolphin V30 - Cinematic Pacing Applied]\n{llm_prompt}\n") - except Exception as e: - print(f"❌ [에러] {e}") - llm_prompt = "" - - final_char_name = user_defined_name - r_master, p1, p2, p3, p4 = "", "", "", "", "" - - if llm_prompt and "[removed]" not in llm_prompt: - cl = re.sub(r'[*#]', '', llm_prompt) - m_char = re.search(r'CHARACTER:\s*(.*?)(?=INITIAL POSTURE|$)', cl, re.I | re.S) - m_master = re.search(r'MASTER SCENE:\s*(.*?)(?=PART 1|$)', cl, re.I | re.S) - - m1 = re.search(r'PART 1:\s*(.*?)(?=PART 2|$)', cl, re.I | re.S) - m2 = re.search(r'PART 2:\s*(.*?)(?=PART 3|$)', cl, re.I | re.S) - m3 = re.search(r'PART 3:\s*(.*?)(?=PART 4|$)', cl, re.I | re.S) - m4 = re.search(r'PART 4:\s*(.*?)(?=\n\n|===|Note:|$)', cl, re.I | re.S) - - if not user_defined_name and m_char: - final_char_name = m_char.group(1).strip() - - r_master = m_master.group(1).strip() if m_master else "" - - p1 = m1.group(1).strip() if m1 else "⚠️ Formatting Error: Part 1" - p2 = m2.group(1).strip() if m2 else "⚠️ Formatting Error: Part 2" - p3 = m3.group(1).strip() if m3 else "⚠️ Formatting Error: Part 3" - p4 = m4.group(1).strip() if m4 else "⚠️ Formatting Error: Part 4" - else: - p1 = p2 = p3 = p4 = "⚠️ API Error: LLM request failed." - - return ( - build_final(p1, tagger_context, r_master, final_char_name), - build_final(p2, tagger_context, r_master, final_char_name), - build_final(p3, tagger_context, r_master, final_char_name), - build_final(p4, tagger_context, r_master, final_char_name) - ) \ No newline at end of file diff --git a/promp_logic_v32_story.py b/promp_logic_v32_story.py deleted file mode 100644 index c5d23cb4d7c4c7ed14524c7a43029f99323232a2..0000000000000000000000000000000000000000 --- a/promp_logic_v32_story.py +++ /dev/null @@ -1,275 +0,0 @@ -import re -import json -import urllib.request -import urllib.error - - -# ====================================================================== -# OpenRouter 모델 프리셋 (NSFW/무삭제 대응) -# 라벨 -> 실제 슬러그. '⚙️ custom (아래 텍스트 사용)' 선택 시 텍스트 필드값 사용. -# 슬러그는 OpenRouter 기준으로 검증됨. 변경/신모델은 custom 으로 입력 가능. -# ====================================================================== -MODEL_PRESETS = { - "🔞 DeepSeek V3.2 (무삭제·JSON안정·추천)": "deepseek/deepseek-v3.2", - "🔞 DeepSeek V3 0324 (안정 버전)": "deepseek/deepseek-chat-v3-0324", - "🔞 Magnum v4 72B (표현 자연스러움)": "anthracite-org/magnum-v4-72b", - "🔞 Cydonia 24B v4.1 (경량·빠름·131K)": "thedrummer/cydonia-24b-v4.1", - # 2026-09-03: :free 슬러그가 404 됨 - 유료로 교체. - "🔞 Dolphin Mistral 24B Venice (무삭제)": "cognitivecomputations/dolphin-mistral-24b-venice-edition", - "🔞 Lumimaid 70B (RP 무삭제)": "neversleep/llama-3-lumimaid-70b", - # 2026-09-03 추가 (모델 페이지로만 확인, chat completions 스모크테스트 미완). - "🔞 Hermes 4 405B (스티어러블·포맷 엄격)": "nousresearch/hermes-4-405b", - # 2026-09-03: grok-4 / grok-4-fast deprecated(404) - grok-4.3으로 교체. - "🔞 Grok 4.3 (엣지·$1.25/$2.5)": "x-ai/grok-4.3", - "🔞 Euryale 70B L3.3 (감각적 산문·포맷 느슨)": "sao10k/l3.3-euryale-70b", - "SFW Llama 3.3 70B (정렬 강함)": "meta-llama/llama-3.3-70b-instruct", - "⚙️ custom (아래 텍스트 사용)": "__custom__", -} -MODEL_PRESET_LABELS = list(MODEL_PRESETS.keys()) - - -def resolve_model(preset_label, custom_text): - """프리셋 라벨과 커스텀 텍스트에서 실제 사용할 슬러그를 결정.""" - slug = MODEL_PRESETS.get(preset_label, "__custom__") - if slug == "__custom__": - return custom_text.strip() or "deepseek/deepseek-v3.2" - return slug - - -class DolphinMultiActionPromptNode_V32: - """ - 4개 연속 클립(각 5초, 총 20초)을 위한 시네마틱 프롬프트 분배 노드. - - 설계 원칙 (V30 대비 변경점): - - 서사 -> 4샷 분배를 LLM이 직접 판단한다. (문장 개수 하드코딩 pacing 분기 제거) - - 타임코드([0-1s] 등) 강제 삽입 제거. WAN I2V에서 노이즈만 됨. - - LLM은 JSON으로만 응답하게 강제 -> 파싱이 정규식보다 훨씬 견고. - - 파싱 실패 시 1회 자동 재시도, 그래도 실패하면 로컬 균등 분배로 폴백. - - seed를 실제 payload에 전달하여 재현성 확보. - """ - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mode": (["🤖 Auto LLM (Story Splitter)", "✍️ Manual Override (수동)"], - {"default": "🤖 Auto LLM (Story Splitter)"}), - "character_name": ("STRING", {"multiline": False, "default": "AUTO"}), - "tagger_context": ("STRING", {"multiline": True, "default": ""}), - "artistic_vibe": ("STRING", {"multiline": True, - "default": "cinematic lighting, dynamic motion"}), - "master_story": ("STRING", { - "multiline": True, - "default": ("어두운 골목길. 갑자기 나타난 적들을 향해 돌진한다, " - "화려하게 검을 휘둘러 적을 쓰러뜨린다, 날아오는 총알을 튕겨낸다, " - "적에게 다가가 숨통을 끊는다.") - }), - "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}), - "model_preset": (MODEL_PRESET_LABELS, - {"default": "🔞 DeepSeek V3.2 (무삭제·JSON안정·추천)"}), - "openrouter_model": ("STRING", {"multiline": False, - "default": "deepseek/deepseek-v3.2"}), - "creativity": ("FLOAT", {"default": 0.7, "min": 0.1, "max": 1.5, "step": 0.05}), - "pacing": (["auto", "slow-burn (1 beat)", "balanced (multi beat)", "rapid (4 distinct)"], - {"default": "auto"}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - }, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") - RETURN_NAMES = ("prompt_1", "prompt_2", "prompt_3", "prompt_4", "debug_info") - FUNCTION = "generate_sequence" - CATEGORY = "Dolphin" - - # ------------------------------------------------------------------ helpers - - def _build_final(self, action, raw_tags, master_scene, name): - """태그 블록 + 문장 블록을 한 프롬프트로 합친다. (V30 build_final 계승)""" - tags = [] - if name: - tags.append(name) - if raw_tags.strip(): - tags.append(raw_tags.strip().strip(",. ")) - tag_block = ", ".join(tags) - - sents = [] - if master_scene.strip(): - sents.append(master_scene.strip().strip(",. ")) - if action.strip(): - sents.append(action.strip()) - sentence_block = " ".join(sents) - - if tag_block and sentence_block: - return f"{tag_block}\n{sentence_block}" - return tag_block or sentence_block - - def _local_split(self, story): - """LLM 폴백용: 의미 단위(마침표/느낌표/줄바꿈 우선, 쉼표는 보조)로 4등분.""" - primary = re.split(r'(?<=[.!?。])\s+|\n+', story) - primary = [p.strip() for p in primary if len(p.strip()) > 1] - if len(primary) < 4: - expanded = [] - for p in primary: - expanded.extend([c.strip() for c in p.split(',') if len(c.strip()) > 1]) - primary = expanded or [story.strip()] - - n = len(primary) - if n == 0: - return ["", "", "", ""] - k, m = divmod(n, 4) - chunks, idx = [], 0 - for i in range(4): - end = idx + k + (1 if i < m else 0) - chunks.append(" ".join(primary[idx:end]).strip()) - idx = end - if n < 4: - filled = [c for c in chunks if c] - target = filled[-1] if filled else story.strip() - build = [ - f"Atmospheric build-up before the action. (upcoming: {target})", - f"Rising tension, subtle movement. (upcoming: {target})", - ] - combined = build + filled - if len(combined) >= 4: - chunks = combined[-4:] - else: - chunks = combined + [target] * (4 - len(combined)) - return chunks[:4] - - def _pacing_hint(self, pacing): - return { - "auto": "Decide pacing yourself based on how many distinct beats the story contains.", - "slow-burn (1 beat)": ("The story is essentially ONE climactic beat. Spend clips 1-3 on " - "atmosphere, tension and wind-up; detonate the action only in clip 4."), - "balanced (multi beat)": ("Distribute the beats so early clips build and later clips " - "deliver, keeping motion continuous across the 20 seconds."), - "rapid (4 distinct)": ("Treat the story as 4 distinct consecutive actions, one per clip, " - "each kinetic and self-contained."), - }.get(pacing, "Decide pacing yourself.") - - def _call_llm(self, url, payload, api_key, timeout=120): - req = urllib.request.Request( - url, data=json.dumps(payload).encode('utf-8'), - headers={'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'}) - resp = urllib.request.urlopen(req, timeout=timeout) - data = json.loads(resp.read().decode('utf-8')) - return data['choices'][0]['message']['content'].strip() - - def _extract_json(self, text): - """LLM 응답에서 JSON 객체만 안전하게 추출.""" - if not text: - return None - text = re.sub(r'```(?:json)?', '', text).strip() - start = text.find('{') - end = text.rfind('}') - if start == -1 or end == -1 or end <= start: - return None - blob = text[start:end + 1] - try: - return json.loads(blob) - except json.JSONDecodeError: - blob2 = re.sub(r',\s*([}\]])', r'\1', blob) - try: - return json.loads(blob2) - except json.JSONDecodeError: - return None - - # ------------------------------------------------------------------ main - - def generate_sequence(self, mode, character_name, tagger_context, artistic_vibe, - master_story, openrouter_api_key, model_preset, openrouter_model, - creativity, pacing, seed): - - # 프리셋 라벨 -> 실제 슬러그 (custom 이면 텍스트 필드 사용) - openrouter_model = resolve_model(model_preset, openrouter_model) - - user_name = "" if character_name.strip().upper() in ["AUTO", ""] else character_name.strip() - - # ---- Manual override: 4칸 동일 출력 ---- - if mode == "✍️ Manual Override (수동)": - p = self._build_final(master_story, tagger_context, "", user_name) - return (p, p, p, p, "manual override") - - # ---- API 키 검증 ---- - api_key = openrouter_api_key.strip() - if not api_key: - fb = self._local_split(master_story) - outs = tuple(self._build_final(c, tagger_context, "", user_name) for c in fb) - return outs + ("⚠️ No API key. Used local fallback split.",) - - # ---- 프롬프트 구성 (JSON 강제) ---- - sys_prompt = ( - "You are an elite cinematic action choreographer and shot planner.\n" - "You will be given a short story (any language), character tags, and a visual vibe.\n" - "Your job: turn it into a 4-shot sequence (four consecutive 5-second video clips, 20s total).\n\n" - "RULES:\n" - "1. CHARACTER: if the name is 'AUTO', invent a culturally appropriate name from the tags; " - "otherwise use the given name.\n" - "2. MASTER_SCENE: one vivid English sentence describing the environment/lighting, " - "derived from tags + vibe. Stays constant across all 4 clips.\n" - "3. SHOT SPLIT: YOU decide how to distribute the narrative across the 4 clips. " - "Keep each clip's action temporally isolated - do NOT let a later clip's action appear early.\n" - "4. Never quote the original non-English text. Output English cinematic descriptions only.\n" - "5. Inject kinetic adverbs (swiftly, suddenly, explosively) - avoid unintended slow motion.\n" - "6. Do NOT add timestamps like [0-1s]; write natural continuous action prose per clip.\n\n" - "OUTPUT: respond with ONLY a JSON object, no prose, no code fences:\n" - '{"character": "...", "master_scene": "...", ' - '"clip_1": "...", "clip_2": "...", "clip_3": "...", "clip_4": "..."}' - ) - - usr_prompt = ( - f"NAME: {character_name}\n" - f"TAGS: {tagger_context}\n" - f"VIBE: {artistic_vibe}\n" - f"PACING DIRECTIVE: {self._pacing_hint(pacing)}\n\n" - f"STORY:\n{master_story}\n" - ) - - url = "https://openrouter.ai/api/v1/chat/completions" - payload = { - "model": openrouter_model.strip(), - "messages": [ - {"role": "system", "content": sys_prompt}, - {"role": "user", "content": usr_prompt}, - ], - "temperature": creativity, - "response_format": {"type": "json_object"}, - } - if seed: - payload["seed"] = seed - - parsed, debug = None, "" - for attempt in (1, 2): - try: - raw = self._call_llm(url, payload, api_key) - parsed = self._extract_json(raw) - if parsed and all(parsed.get(k) for k in - ("clip_1", "clip_2", "clip_3", "clip_4")): - debug = f"✅ LLM ok (attempt {attempt})" - break - debug = f"⚠️ Parse failed (attempt {attempt}), retrying..." - except (urllib.error.URLError, urllib.error.HTTPError, - TimeoutError, KeyError, ValueError) as e: - debug = f"❌ API error (attempt {attempt}): {e}" - payload["temperature"] = min(creativity, 0.4) - - # ---- 성공: LLM 결과 조립 ---- - if parsed and all(parsed.get(k) for k in ("clip_1", "clip_2", "clip_3", "clip_4")): - final_name = user_name or str(parsed.get("character", "")).strip() - master = str(parsed.get("master_scene", "")).strip() - clips = [str(parsed[f"clip_{i}"]).strip() for i in range(1, 5)] - outs = tuple(self._build_final(c, tagger_context, master, final_name) for c in clips) - print(f"\n✅ [Dolphin V32] {debug}\nCHAR: {final_name}\nSCENE: {master}") - for i, c in enumerate(clips, 1): - print(f" CLIP {i}: {c}") - return outs + (f"{debug} | char={final_name}",) - - # ---- 실패: 로컬 폴백 ---- - fb = self._local_split(master_story) - outs = tuple(self._build_final(c, tagger_context, "", user_name) for c in fb) - print(f"\n⚠️ [Dolphin V32] LLM failed, local fallback. {debug}") - return outs + (f"⚠️ Fallback used. {debug}",) - - -NODE_CLASS_MAPPINGS = {"DolphinMultiActionPromptNode_V32": DolphinMultiActionPromptNode_V32} -NODE_DISPLAY_NAME_MAPPINGS = {"DolphinMultiActionPromptNode_V32": "🐬 Dolphin Story Splitter V32"} diff --git a/promp_logic_v32_vision.py b/promp_logic_v32_vision.py deleted file mode 100644 index aaab62c6719ee577ef0deb1b5f3487579473a14b..0000000000000000000000000000000000000000 --- a/promp_logic_v32_vision.py +++ /dev/null @@ -1,247 +0,0 @@ -import os -import random -import re -import json -import urllib.request -import urllib.error -import base64 -import io -import time -import numpy as np -from PIL import Image -import torch - - -class DolphinMultiActionPromptNode_V32: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "mode": (["🤖 Auto Vision+LLM", "✍️ Manual Override"], {"default": "🤖 Auto Vision+LLM"}), - "character_name": ("STRING", {"multiline": False, "default": "AUTO"}), - "artistic_vibe": ("STRING", {"multiline": True, "default": "cinematic lighting, high-speed action, dark fantasy"}), - - "master_story": ("STRING", { - "multiline": True, - "default": "어두운 골목길. 갑자기 나타난 적들을 향해 돌진한다, 화려하게 검을 휘둘러 적을 쓰러뜨린다, 날아오는 총알을 튕겨낸다, 적에게 다가가 숨통을 끊는다." - }), - - "openrouter_api_key": ("STRING", {"multiline": False, "default": ""}), - "openrouter_model": ("STRING", {"multiline": False, "default": "qwen/qwen-2-vl-72b-instruct"}), - "creativity": ("FLOAT", {"default": 0.85, "min": 0.1, "max": 1.5, "step": 0.05}), - "max_tokens": ("INT", {"default": 1500, "min": 256, "max": 8192, "step": 64}), - "retries": ("INT", {"default": 2, "min": 0, "max": 5}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - }, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") - 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)", - "raw_llm_output", - ) - FUNCTION = "generate_sequence" - CATEGORY = "Dolphin" - - # ----------------------------------------------------------------- - def _encode_image(self, image): - img_tensor = image[0] - i = 255. * img_tensor.cpu().numpy() - img_pil = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) - if img_pil.mode != "RGB": - img_pil = img_pil.convert("RGB") - buffered = io.BytesIO() - img_pil.save(buffered, format="JPEG", quality=90) - b64 = base64.b64encode(buffered.getvalue()).decode('utf-8') - return f"data:image/jpeg;base64,{b64}" - - def _split_sentences(self, master_story): - clean_story = re.sub(r'([.!?,\n])', r'\1|', master_story) - raw_sentences = clean_story.split('|') - return [s.strip() for s in raw_sentences if len(s.strip()) > 1] - - def _build_chunks(self, sentences): - n = len(sentences) - if n == 0: - base = "dynamic high-speed action" - return (base, base, base, base) - if n == 1: - s = sentences[0] - return ( - f"Phase 1: Rapid approach and high-speed dynamic movement. DO NOT stand still. (Target: {s})", - f"Phase 2: Swift, explosive execution of the action. (Target: {s})", - f"Phase 3: The climax at full 1x real-time speed. Lightning fast! (Target: {s})", - f"Phase 4: Fast-paced completion and quick recovery. (Target: {s})", - ) - if n == 2: - return ( - f"Phase 1: High-speed buildup and rapid preparation. (Target: {sentences[0]})", - f"Phase 2: Explosively execute -> {sentences[0]}", - f"Phase 3: Rapid transition, sprinting or moving quickly. (Target: {sentences[1]})", - f"Phase 4: Lightning-fast execution -> {sentences[1]}", - ) - if n == 3: - return ( - f"Phase 1: Start this action rapidly -> {sentences[0]}", - f"Phase 2: Explosively complete -> {sentences[0]}", - sentences[1], - sentences[2], - ) - # n >= 4: 균등 분배 - k, m = divmod(n, 4) - chunks = [] - start = 0 - for idx in range(4): - end = start + k + (1 if idx < m else 0) - chunks.append(" ".join(sentences[start:end])) - start = end - return tuple(chunks) - - def _call_llm(self, url, payload, api_key, retries, timeout=120): - last_err = None - for attempt in range(retries + 1): - try: - req = urllib.request.Request( - url, - data=json.dumps(payload).encode('utf-8'), - headers={'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'} - ) - response = urllib.request.urlopen(req, timeout=timeout) - body = json.loads(response.read().decode('utf-8')) - return body['choices'][0]['message']['content'].strip(), None - except Exception as e: - last_err = e - if attempt < retries: - time.sleep(1.5 * (attempt + 1)) - return None, last_err - - # ----------------------------------------------------------------- - def generate_sequence(self, image, mode, character_name, artistic_vibe, master_story, - openrouter_api_key, openrouter_model, creativity, max_tokens, retries, seed): - - user_defined_name = "" if character_name.upper() in ["AUTO", ""] else character_name.strip() - - def build_final(action, master_scene, name): - tags_list = [] - if name: - tags_list.append(name) - tag_block = ", ".join(tags_list) - - sentence_list = [] - if master_scene.strip(): - sentence_list.append(master_scene.strip().strip(",. ")) - if action.strip(): - sentence_list.append(action.strip()) - sentence_block = " ".join(sentence_list) - - if tag_block and sentence_block: - return f"{tag_block}\n{sentence_block}" - elif tag_block: - return tag_block - return sentence_block - - # ---- Manual Override ---- - if mode == "✍️ Manual Override": - fp = build_final(master_story, "", user_defined_name) - return (fp, fp, fp, fp, "[Manual Override]") - - random.seed(seed) - - base64_image = self._encode_image(image) - sentences = self._split_sentences(master_story) - chunk_1, chunk_2, chunk_3, chunk_4 = self._build_chunks(sentences) - - sys_prompt = ( - "You are an Elite Action Director prioritizing RAW SPEED and KINETIC ENERGY.\n" - f"1. CHARACTER: If NAME is 'AUTO', assign a name. If '{user_defined_name}', use it.\n" - "2. VISUAL ANALYSIS: You MUST base your descriptions EXACTLY on the character's clothing and weapons in the attached IMAGE.\n" - "3. MASTER SCENE: Write a 1-sentence environment description (lighting, weather).\n" - "4. SPEED-FOCUSED CHOREOGRAPHY (CRITICAL):\n" - " - 🚫 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" - " - ✅ FORCE 1x REAL-TIME SPEED: Describe large, sweeping, high-velocity movements. Use aggressive verbs (dashing, sprinting, whipping, snapping).\n" - " - ✅ KINETIC ADVERBS: Inject phrases like 'in a flash', 'at lightning speed', 'with explosive real-time velocity' into EVERY part.\n" - " - Example: 'suddenly dashes forward at full speed and delivers a lightning-fast horizontal strike, moving so quickly the rain splatters'.\n" - " - Strictly confine the actions. DO NOT animate future events early.\n" - " - 🔥 OUTPUT RULE: DO NOT quote the Korean text. Only output English.\n" - "Format EXACTLY:\nCHARACTER: [Name]\nMASTER SCENE: [Description]\n" - "PART 1: [0-1s] [Action A] [2-3s] [Action B] [4-5s] [Action C]\n" - "PART 2: [0-1s] [Action D] [2-3s] [Action E] [4-5s] [Action F]\n" - "PART 3: [0-1s] [Action G] [2-3s] [Action H] [4-5s] [Action I]\n" - "PART 4: [0-1s] [Action J] [2-3s] [Action K] [4-5s] [Action L]" - ) - - usr_text = ( - f"NAME: {character_name}\n" - f"VIBE: {artistic_vibe}\n\n" - "=== HIGH-SPEED ACTION SCRIPT ===\n" - f"▶ For PART 1 (0-5s), ONLY animate this: \"{chunk_1}\"\n" - f"▶ For PART 2 (5-10s), ONLY animate this: \"{chunk_2}\"\n" - f"▶ For PART 3 (10-15s), ONLY animate this: \"{chunk_3}\"\n" - f"▶ For PART 4 (15-20s), ONLY animate this: \"{chunk_4}\"\n" - "CRITICAL: Keep the action moving FAST. Avoid still poses or micro-details that look like slow-mo!" - ) - - url = "https://openrouter.ai/api/v1/chat/completions" - payload = { - "model": openrouter_model.strip(), - "messages": [ - {"role": "system", "content": sys_prompt}, - {"role": "user", "content": [ - {"type": "text", "text": usr_text}, - {"type": "image_url", "image_url": {"url": base64_image}} - ]} - ], - "temperature": creativity, - "max_tokens": max_tokens, - } - - if not openrouter_api_key.strip(): - err_msg = "⚠️ API Error: OpenRouter API key is empty." - return (err_msg, err_msg, err_msg, err_msg, err_msg) - - llm_prompt, err = self._call_llm(url, payload, openrouter_api_key, retries) - if llm_prompt: - print(f"\n✅ [Dolphin V32 - Action Speed Optimized]\n{llm_prompt}\n") - else: - print(f"❌ [에러] API 호출 실패: {err}") - llm_prompt = "" - - final_char_name = user_defined_name - r_master = "" - p1 = p2 = p3 = p4 = "" - - if llm_prompt and "[removed]" not in llm_prompt: - cl = re.sub(r'[*#]', '', llm_prompt) - m_char = re.search(r'CHARACTER:\s*(.*?)(?=MASTER SCENE|$)', cl, re.I | re.S) - m_master = re.search(r'MASTER SCENE:\s*(.*?)(?=PART 1|$)', cl, re.I | re.S) - - m1 = re.search(r'PART 1:\s*(.*?)(?=PART 2|$)', cl, re.I | re.S) - m2 = re.search(r'PART 2:\s*(.*?)(?=PART 3|$)', cl, re.I | re.S) - m3 = re.search(r'PART 3:\s*(.*?)(?=PART 4|$)', cl, re.I | re.S) - m4 = re.search(r'PART 4:\s*(.*?)(?=\n\n|===|Note:|$)', cl, re.I | re.S) - - if not user_defined_name and m_char: - final_char_name = m_char.group(1).strip() - - r_master = m_master.group(1).strip() if m_master else "" - - # 파싱 실패 시 해당 청크(영문 지시문)를 폴백으로 사용해 비디오 프롬프트가 비지 않도록 함 - p1 = m1.group(1).strip() if m1 else chunk_1 - p2 = m2.group(1).strip() if m2 else chunk_2 - p3 = m3.group(1).strip() if m3 else chunk_3 - p4 = m4.group(1).strip() if m4 else chunk_4 - else: - # API 실패 시에도 스크립트 청크를 폴백으로 반환 (완전 실패보다 유용) - p1, p2, p3, p4 = chunk_1, chunk_2, chunk_3, chunk_4 - - return ( - build_final(p1, r_master, final_char_name), - build_final(p2, r_master, final_char_name), - build_final(p3, r_master, final_char_name), - build_final(p4, r_master, final_char_name), - llm_prompt if llm_prompt else "⚠️ API Error / empty response", - ) diff --git a/tools/reapply_symlink_output_patch.ps1 b/tools/reapply_symlink_output_patch.ps1 deleted file mode 100644 index 72c53bd084f35366454e3f8296c47068a8b8ad8e..0000000000000000000000000000000000000000 --- a/tools/reapply_symlink_output_patch.ps1 +++ /dev/null @@ -1,91 +0,0 @@ -<# -.SYNOPSIS - Re-applies the local revert of ComfyUI upstream commit 41a3e160d (#15216). - -.DESCRIPTION - #15216 changed get_save_image_path()'s containment check from a lexical - abspath comparison to is_within_directory(), which realpath()s both operands. - That resolves symlinks, so saving into a symlinked subfolder of the output - directory (e.g. D:\SD\output\I2V\RAW -> D:\iCloud\iCloudDrive\img\RAW) fails - with "Saving image outside the output folder is not allowed". - - The GHSA-779p-m5rp-r4h4 security fix (96e0e3585, 2026-07-02) is NOT touched -- - this only reverts the later refactor. ".." traversal stays blocked, and the - /view endpoint in server.py uses the same abspath check, so save and preview - remain consistent. - - Idempotent: safe to run any number of times. - - Update flow: folder_paths.py is tracked by ComfyUI's own git, so this patch - makes it dirty and `git pull` will refuse to overwrite it. Discard the patch, - pull, then re-apply: - - git -C checkout -- folder_paths.py - git -C pull - .\reapply_symlink_output_patch.ps1 - -.EXAMPLE - .\reapply_symlink_output_patch.ps1 - .\reapply_symlink_output_patch.ps1 -Verify -#> -[CmdletBinding()] -param( - # Path to ComfyUI's folder_paths.py. Defaults to the ComfyUI root three - # levels above this script (tools -> dolphin_nodes -> custom_nodes -> ComfyUI). - [string]$FolderPathsPy = (Join-Path $PSScriptRoot '..\..\..\folder_paths.py'), - - # Only report the current state; make no changes. - [switch]$Verify -) - -$ErrorActionPreference = 'Stop' - -$marker = '# DOLPHIN LOCAL PATCH: revert of upstream 41a3e160d' -$upstream = ' if not is_within_directory(output_dir, full_output_folder):' -$patched = @" - # DOLPHIN LOCAL PATCH: revert of upstream 41a3e160d (#15216), which swapped - # this lexical check for is_within_directory()'s realpath() one. That change - # breaks saving into a symlinked subfolder of the output dir (D:\SD\output\ - # I2V\RAW -> D:\iCloud\...). The /view endpoint in server.py still uses this - # same abspath-based check, so reverting keeps save and preview consistent. - # Re-apply after a ComfyUI update: - # custom_nodes/dolphin_nodes/tools/reapply_symlink_output_patch.ps1 - if os.path.commonpath((output_dir, os.path.abspath(full_output_folder))) != output_dir: -"@ - -if (-not (Test-Path $FolderPathsPy)) { - Write-Error "folder_paths.py not found at: $FolderPathsPy" - exit 1 -} -$FolderPathsPy = (Resolve-Path $FolderPathsPy).Path -$text = Get-Content $FolderPathsPy -Raw - -if ($text.Contains($marker)) { - Write-Host "[OK] Already patched: $FolderPathsPy" -ForegroundColor Green - exit 0 -} - -if (-not $text.Contains($upstream)) { - Write-Host "[FAIL] Neither the patch marker nor the expected upstream line was found." -ForegroundColor Red - Write-Host " Upstream probably rewrote this check. Inspect get_save_image_path() in:" - Write-Host " $FolderPathsPy" - exit 1 -} - -if ($Verify) { - Write-Host "[NEEDS PATCH] Upstream check is active; symlinked output subfolders will fail." -ForegroundColor Yellow - exit 2 -} - -$backup = "$FolderPathsPy.orig-$(Get-Date -Format yyyyMMdd-HHmmss)" -Copy-Item $FolderPathsPy $backup - -# Preserve the file's existing newline style rather than forcing CRLF. -$eol = if ($text -match "`r`n") { "`r`n" } else { "`n" } -$text = $text.Replace($upstream, ($patched -replace "`r?`n", $eol).TrimEnd($eol)) -Set-Content $FolderPathsPy $text -NoNewline - -Write-Host "[PATCHED] $FolderPathsPy" -ForegroundColor Green -Write-Host " backup: $backup" -Write-Host " Restart ComfyUI for the change to take effect." -exit 0 diff --git a/wan_h3_refine.py b/wan_h3_refine.py deleted file mode 100644 index 708f24cb88e7ba9283a2c2ecff22dfb37bdc5707..0000000000000000000000000000000000000000 --- a/wan_h3_refine.py +++ /dev/null @@ -1,354 +0,0 @@ -"""MiniMax H3 출력을 Wan 2.2 LOW noise expert로 저-denoise 리파인하는 올인원 노드. - -H3(24fps, 768 short edge, 17k+5 프레임 그리드)와 Wan(16fps, 4n+1 그리드, Wan2.1 VAE) -사이를 넘길 때 매번 손으로 맞춰야 하는 것들을 한 노드에 몰아넣는다: - - - 프레임 수를 4n+1로 정렬 (H3 기본 124프레임은 Wan에서 불법. 141/209/277/345는 합법) - - VAE 오배선 차단 (Wan2.2 VAE=48ch, H3 VAE=24ch를 꽂으면 여기서 막는다. 필요한 건 Wan2.1 VAE=16ch) - - 소스 영상을 인코딩해서 그대로 샘플링 라텐트로 사용 (WanImageToVideo가 뱉는 zeros 라텐트가 아님) - - I2V concat 컨디셔닝을 코어 WanImageToVideo와 동일한 규약으로 재구성 - - sigma_shift 패치를 샘플링 직전에 직접 적용 (그래프에 ModelSamplingSD3를 따로 놓는 방식은 - 상류에서 model_sampling을 통째로 교체하는 노드가 있으면 무시되므로 여기서 처리) - - VAE 왕복 + 저denoise 리페인트가 만드는 색 드리프트를 원본 기준으로 되돌림 - - H3가 같이 뽑아준 오디오를 정렬된 프레임 수에 맞춰 트림/패드해서 통과시킴 - -컨텍스트 윈도우(81프레임 초과 클립)는 이 노드가 아니라 상류의 코어 `WanContextWindowsManual` -노드가 담당한다 - MODEL을 패치하는 방식이라 이 노드는 아무것도 안 해도 자동으로 먹는다. -""" - -import torch -import torch.nn.functional as F - -import comfy.model_management -import comfy.model_sampling -import comfy.samplers -import comfy.utils -import node_helpers -import nodes - -# Wan 2.2 MoE의 high/low expert 경계 시그마. 시작 시그마가 이 값 아래면 LOW 단독으로 충분하다. -MOE_BOUNDARY_SIGMA = 0.9 - -# Wan2.1 VAE의 라텐트 채널 수. Wan2.2(5B) VAE는 48, MiniMax H3 VAE는 24라서 이걸로 구분된다. -WAN21_LATENT_CHANNELS = 16 - - -def _shifted_sigma(shift, t): - """flow matching의 time_snr_shift. comfy/model_sampling.py의 스케줄과 동일한 식.""" - if shift <= 0: - return t - return shift * t / (1.0 + (shift - 1.0) * t) - - -def _align_4n1(n, mode): - """Wan의 프레임 그리드(4n+1)로 맞춘 목표 프레임 수.""" - rem = (n - 1) % 4 - if rem == 0: - return n - if mode == "trim": - return n - rem - if mode == "pad": - return n + (4 - rem) - return n # off - - -def _color_match_per_frame(out, src, strength): - """프레임별/채널별 mean-std 매칭. 클립 전체 통계가 아니라 프레임별로 맞춰야 - H3 쪽 조명 변화(페이드, 그림자 통과 등)를 리파인이 눌러버리지 않는다.""" - if strength <= 0.0: - return out - eps = 1e-5 - o = out.float() - s = src.float() - o_mean = o.mean(dim=(1, 2), keepdim=True) - o_std = o.std(dim=(1, 2), keepdim=True) - s_mean = s.mean(dim=(1, 2), keepdim=True) - s_std = s.std(dim=(1, 2), keepdim=True) - matched = (o - o_mean) / (o_std + eps) * s_std + s_mean - return torch.lerp(o, matched, float(strength)).clamp(0.0, 1.0) - - -class WanH3Refine_Dolphin: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "model": ("MODEL", {"tooltip": "Wan 2.2 I2V A14B LOW noise + 캐릭터 LoRA까지 적용된 모델. " - "denoise가 낮으면 HIGH expert는 아예 안 쓰이므로 LOW만 로드하면 된다."}), - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "vae": ("VAE", {"tooltip": "Wan2.1 VAE (16채널). Wan2.2 VAE나 H3 VAE를 꽂으면 에러로 막는다."}), - "images": ("IMAGE", {"tooltip": "H3가 뽑은 프레임들."}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - "steps": ("INT", {"default": 6, "min": 1, "max": 200, - "tooltip": "실제로 실행되는 스텝 수. denoise가 낮아도 이 횟수만큼 돈다 " - "(스케줄 총 길이가 steps/denoise로 늘어나고 그 꼬리에서 이 수만큼 밟는 구조). " - "리파인은 4~8이면 충분."}), - "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 30.0, "step": 0.1, - "tooltip": "증류(lightning/lightx2v) LoRA를 뺐다면 2.5~3.5, 걸었다면 1.0."}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler"}), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "simple"}), - "denoise": ("FLOAT", {"default": 0.30, "min": 0.01, "max": 1.0, "step": 0.01, - "tooltip": "0.2~0.35 권장. Wan은 16fps 기준으로 학습돼서 24fps 소스를 " - "'덜 움직이는 영상'으로 읽는다 - 높일수록 모션을 죽인다."}), - "sigma_shift": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 20.0, "step": 0.1, - "tooltip": "0이면 모델 기본값 유지. 생성용 5는 스텝을 고시그마 쪽으로 쏠리게 " - "하는데 리파인은 저시그마를 촘촘히 밟아야 하므로 2~3이 맞다."}), - "frame_align": (["trim", "pad", "off"], {"default": "trim", - "tooltip": "Wan의 4n+1 그리드로 정렬. trim=뒤를 자름, " - "pad=마지막 프레임 복제. H3 length를 141/209/277/345로 " - "뽑으면 애초에 정렬이 필요 없다."}), - "conditioning_mode": (["first_frame", "full_clip", "none"], {"default": "first_frame", - "tooltip": "first_frame=코어 WanImageToVideo와 동일(학습 분포 안). " - "full_clip=전 프레임을 known으로 주는 강한 소스 고정이지만 " - "I2V-A14B 학습 분포 밖이라 실험용. none=컨디셔닝 없음."}), - "upscale_by": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.05, - "tooltip": "리파인 전 확대 배율. 1.3을 넘기면 Wan 학습 해상도를 벗어나 " - "중복 패턴이 생긴다 - 큰 업스케일은 SeedVR2/Topaz로 따로."}), - "color_match": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05, - "tooltip": "VAE 왕복이 만드는 채도/밝기 드리프트를 원본 기준으로 되돌리는 강도."}), - "fps": ("INT", {"default": 24, "min": 1, "max": 120, - "tooltip": "소스 fps. 오디오 길이를 정렬된 프레임 수에 맞출 때만 쓴다. H3는 24."}), - }, - "optional": { - "audio": ("AUDIO", {"tooltip": "H3가 같이 뽑은 오디오. 정렬된 프레임 수에 맞춰 트림/패드해서 통과시킨다."}), - }, - } - - RETURN_TYPES = ("IMAGE", "AUDIO", "INT", "INT", "INT", "STRING") - RETURN_NAMES = ("IMAGE", "audio", "frames", "width", "height", "info") - FUNCTION = "refine" - CATEGORY = "Dolphin Node/Wan Video" - DESCRIPTION = "MiniMax H3 출력을 Wan 2.2 LOW noise로 저-denoise 리파인 (그리드 정렬 + VAE 검증 + 색보정 + 오디오 패스스루)" - - def refine(self, model, positive, negative, vae, images, seed, steps, cfg, - sampler_name, scheduler, denoise, sigma_shift, frame_align, - conditioning_mode, upscale_by, color_match, fps, audio=None): - - notes = [] - - # --- 1. VAE 검증 --------------------------------------------------- - latent_channels = getattr(vae, "latent_channels", None) - if latent_channels != WAN21_LATENT_CHANNELS: - hint = {24: "MiniMax H3 VAE(24ch)", 48: "Wan2.2 5B VAE(48ch)", 4: "SD VAE(4ch)"}.get( - latent_channels, f"라텐트 {latent_channels}채널 VAE") - raise ValueError( - f"[Dolphin] Wan 2.2 I2V A14B 리파인에는 Wan2.1 VAE(16채널)가 필요한데 {hint}가 연결됐다. " - f"models/vae의 Wan2_1_VAE_bf16.safetensors 계열로 바꿀 것." - ) - - # --- 2. 프레임 그리드 정렬 (Wan은 4n+1만 받는다) --------------------- - n_in = int(images.shape[0]) - if n_in < 5: - raise ValueError(f"[Dolphin] 프레임이 {n_in}장뿐이다. 최소 5장 필요.") - - n = _align_4n1(n_in, frame_align) - if n < 5: - raise ValueError(f"[Dolphin] 4n+1로 정렬하면 {n}프레임이 된다. 소스를 더 길게 뽑을 것.") - - if n < n_in: - images = images[:n] - notes.append(f"{n_in}→{n}프레임 트림") - elif n > n_in: - tail = images[-1:].repeat(n - n_in, 1, 1, 1) - images = torch.cat([images, tail], dim=0) - notes.append(f"{n_in}→{n}프레임 패드(마지막 프레임 복제)") - elif frame_align == "off" and (n_in - 1) % 4 != 0: - notes.append(f"⚠ {n_in}프레임은 4n+1이 아니다 (frame_align=off)") - - images = images[:, :, :, :3] - - # --- 3. 해상도 정렬 (16의 배수) -------------------------------------- - src_h, src_w = int(images.shape[1]), int(images.shape[2]) - tgt_w = max(16, int(round(src_w * upscale_by / 16.0)) * 16) - tgt_h = max(16, int(round(src_h * upscale_by / 16.0)) * 16) - - if (tgt_w, tgt_h) != (src_w, src_h): - images = comfy.utils.common_upscale( - images.movedim(-1, 1), tgt_w, tgt_h, "lanczos", "disabled" - ).movedim(1, -1).clamp(0.0, 1.0) - notes.append(f"{src_w}x{src_h}→{tgt_w}x{tgt_h}") - if upscale_by > 1.3: - notes.append("⚠ upscale_by>1.3은 Wan 학습 해상도 밖 (중복 패턴 위험)") - - # --- 4. 소스 인코딩: 이게 샘플링 시작 라텐트가 된다 -------------------- - source_latent = self._encode(vae, images) - - # --- 5. I2V concat 컨디셔닝 (코어 WanImageToVideo와 동일 규약) -------- - if conditioning_mode != "none": - known = images[:1] if conditioning_mode == "first_frame" else images - canvas = torch.ones((n, tgt_h, tgt_w, 3), - device=images.device, dtype=images.dtype) * 0.5 - canvas[:known.shape[0]] = known - concat_latent = self._encode(vae, canvas) - - mask = torch.ones((1, 1, source_latent.shape[2], - concat_latent.shape[-2], concat_latent.shape[-1]), - device=images.device, dtype=images.dtype) - mask[:, :, :((known.shape[0] - 1) // 4) + 1] = 0.0 - - values = {"concat_latent_image": concat_latent, "concat_mask": mask} - positive = node_helpers.conditioning_set_values(positive, values) - negative = node_helpers.conditioning_set_values(negative, values) - - if conditioning_mode == "full_clip": - notes.append("⚠ full_clip은 I2V-A14B 학습 분포 밖(첫 프레임만 known으로 학습됨)") - - # --- 6. sigma_shift 패치 -------------------------------------------- - effective_shift = self._model_shift(model) - if sigma_shift > 0: - model = self._patch_shift(model, sigma_shift) - effective_shift = sigma_shift - - # --- 7. 샘플링 ------------------------------------------------------- - out_latent, = nodes.common_ksampler( - model, seed, steps, cfg, sampler_name, scheduler, - positive, negative, {"samples": source_latent}, denoise=denoise, - ) - - # --- 8. 디코드 ------------------------------------------------------- - result = self._decode(vae, out_latent["samples"]) - if result.shape[0] > n: - result = result[:n] - elif result.shape[0] < n: - images = images[:result.shape[0]] - n = int(result.shape[0]) - - # --- 9. 색 드리프트 되돌리기 ----------------------------------------- - if result.shape[1:3] != images.shape[1:3]: - ref = F.interpolate(images.movedim(-1, 1), - size=(result.shape[1], result.shape[2]), - mode="bicubic", antialias=True).clamp(0, 1).movedim(1, -1) - else: - ref = images - result = _color_match_per_frame(result[:, :, :, :3], ref, color_match) - - # --- 10. 오디오를 프레임 수에 맞춰 정렬 ------------------------------- - audio_out = audio - if audio is not None and isinstance(audio, dict) and "waveform" in audio: - sr = int(audio.get("sample_rate", 48000)) - waveform = audio["waveform"] - target_samples = int(round(n / float(fps) * sr)) - cur = int(waveform.shape[-1]) - if cur > target_samples: - waveform = waveform[..., :target_samples] - notes.append(f"오디오 {cur}→{target_samples}샘플 트림") - elif cur < target_samples: - waveform = F.pad(waveform, (0, target_samples - cur)) - notes.append(f"오디오 {cur}→{target_samples}샘플 패드") - audio_out = {"waveform": waveform, "sample_rate": sr} - - # --- 11. 리포트 ------------------------------------------------------ - start_sigma = _shifted_sigma(effective_shift, denoise) - expert = "LOW 단독으로 충분" if start_sigma < MOE_BOUNDARY_SIGMA else "HIGH expert 구간 진입 (리파인이 아니라 재생성)" - info_lines = [ - f"frames {n_in} → {n} (4n+1, n={(n - 1) // 4}) {tgt_w}x{tgt_h} {n / float(fps):.2f}s @ {fps}fps", - f"denoise {denoise:.2f} / shift {effective_shift:.2f} → 시작 σ {start_sigma:.3f} (MoE 경계 {MOE_BOUNDARY_SIGMA}) → {expert}", - f"실행 스텝 {steps} (스케줄 총 길이 ~{int(round(steps / max(denoise, 1e-3)))})", - ] - if notes: - info_lines.append("· " + " / ".join(notes)) - info = "\n".join(info_lines) - print("[Dolphin] Wan H3 Refine\n " + "\n ".join(info_lines)) - - return (result, audio_out, n, tgt_w, tgt_h, info) - - # ---------------------------------------------------------------- helpers - - def _encode(self, vae, pixels): - try: - return vae.encode(pixels) - except comfy.model_management.OOM_EXCEPTION: - print("[Dolphin] VAE 인코딩 OOM → 타일 인코딩으로 폴백") - return vae.encode_tiled(pixels, tile_x=512, tile_y=512, - overlap=64, tile_t=32, overlap_t=8) - - def _decode(self, vae, samples): - try: - images = vae.decode(samples) - except comfy.model_management.OOM_EXCEPTION: - print("[Dolphin] VAE 디코딩 OOM → 타일 디코딩으로 폴백") - compression = vae.temporal_compression_decode() or 4 - images = vae.decode_tiled(samples, tile_x=512, tile_y=512, overlap=64, - tile_t=max(2, 32 // compression), - overlap_t=max(1, 8 // compression)) - if len(images.shape) == 5: # 배치 합치기 - images = images.reshape(-1, images.shape[-3], images.shape[-2], images.shape[-1]) - return images.clamp(0.0, 1.0) - - def _model_shift(self, model): - try: - return float(getattr(model.get_model_object("model_sampling"), "shift", 1.0)) - except Exception: - return 1.0 - - def _patch_shift(self, model, shift): - """ModelSamplingSD3와 동일한 패치를 샘플링 직전에 직접 적용. - 그래프에 따로 놓는 ModelSamplingSD3 노드는 model_sampling을 통째로 교체하는 - 다른 노드(TripleKSampler 등)가 뒤에 있으면 무시되므로 여기서 하는 게 확실하다.""" - m = model.clone() - - class ModelSamplingAdvanced(comfy.model_sampling.ModelSamplingDiscreteFlow, - comfy.model_sampling.CONST): - pass - - original = m.get_model_object("model_sampling") - model_sampling = ModelSamplingAdvanced(model.model.model_config) - model_sampling.set_parameters(shift=shift, multiplier=1000) - if hasattr(original, "noise_scale"): - model_sampling.set_noise_scale(original.noise_scale) - m.add_object_patch("model_sampling", model_sampling) - return m - - -class WanH3FrameGrid_Dolphin: - """H3의 length를 Wan과 호환되는 값으로 미리 뽑아주는 계산기. - - H3 그리드는 n % 17 == 5, Wan 그리드는 n % 4 == 1이다. 둘이 겹치는 건 68프레임 주기 - - 5 / 73 / 141 / 209 / 277 / 345 ... H3 노드의 length를 여기서 나온 값으로 넣으면 - 리파인 단계에서 트림/패드가 아예 필요 없어진다.""" - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "seconds": ("FLOAT", {"default": 5.9, "min": 0.2, "max": 60.0, "step": 0.1, - "tooltip": "원하는 길이(초). 24fps 기준으로 가장 가까운 호환 프레임 수를 찾는다."}), - "mode": (["wan_compatible", "h3_only"], {"default": "wan_compatible", - "tooltip": "wan_compatible=H3(17k+5)와 Wan(4n+1) 둘 다 만족하는 값. " - "h3_only=H3 그리드만 만족(리파인 안 할 때)."}), - } - } - - RETURN_TYPES = ("INT", "FLOAT", "STRING") - RETURN_NAMES = ("length", "seconds", "info") - FUNCTION = "compute" - CATEGORY = "Dolphin Node/Wan Video" - DESCRIPTION = "H3 length를 Wan 리파인과 호환되는 프레임 수(5/73/141/209/277/345…)로 계산" - - def compute(self, seconds, mode): - fps = 24 - want = max(5, int(round(seconds * fps))) - - # h3_only는 17k+5, wan_compatible은 17k+5 ∩ 4n+1 = 68m+5 (k가 4의 배수일 때). - period = 17 if mode == "h3_only" else 68 - below = 5 + period * max(0, (want - 5) // period) - above = below + period - n = below if (want - below) <= (above - want) else above - n = max(5, n) - - dur = n / float(fps) - wan_ok = (n - 1) % 4 == 0 - info = (f"length={n} ({dur:.2f}s @24fps) H3 그리드 OK" - f" / Wan 4n+1 {'OK (n=%d)' % ((n - 1) // 4) if wan_ok else '불가 - 리파인 시 트림 필요'}") - return (n, dur, info) - - -NODE_CLASS_MAPPINGS = { - "WanH3Refine_Dolphin": WanH3Refine_Dolphin, - "WanH3FrameGrid_Dolphin": WanH3FrameGrid_Dolphin, -} -NODE_DISPLAY_NAME_MAPPINGS = { - "WanH3Refine_Dolphin": "🐬🅜 Wan Refine H3 (low-denoise pass)", - "WanH3FrameGrid_Dolphin": "🐬🅜 H3↔Wan Frame Grid", -} diff --git a/wan_i2v_continuity.py b/wan_i2v_continuity.py deleted file mode 100644 index 37496c9043e25c6ac251ad01ac2df9c07750922c..0000000000000000000000000000000000000000 --- a/wan_i2v_continuity.py +++ /dev/null @@ -1,108 +0,0 @@ -import torch -import comfy.model_management -import comfy.utils -import node_helpers - - -class WanI2VContinuity_Dolphin: - """ - Wan2.2 I2V 노드 — 클립 이어붙이기(연속성)용. - - 기존 접근(WanImageToVideoSVIPro)의 문제: anchor 이미지를 따로 인코딩한 latent와, - 이전 클립 latent의 뒷부분을 그냥 잘라낸 조각(motion_latent)을 이어붙였다. WAN VAE는 - 인과적(causal) 시간 압축이라 각 latent 프레임이 "그 시점까지의 시퀀스 문맥"을 전제로 - 인코딩되는데, 서로 다른 문맥에서 나온 latent 조각을 이어붙이면 통계적으로 불연속이라 - 화질이 튀고 동작 연속성도 못 살렸다. - - 이 노드는 ComfyUI 코어 `WanImageToVideo`의 검증된 방식을 따른다: - 이전 클립에서 디코딩한 마지막 N프레임(픽셀)을 start_image로 받아, 회색(0.5) filler와 - 함께 하나의 시퀀스로 만들어 **한 번의 통짜 VAE 인코딩**을 한다. 그래서 known 구간과 - 생성 구간의 latent 문맥이 자연스럽게 이어진다. 마스크도 프레임수→latent수 변환을 - 정확히 해서 known 프레임 전체를 고정한다. - - 여기에 PainterI2V의 motion_amplitude(슬로우모션 방지)를 이식했다. 단 known 프레임은 - 건드리지 않고, 뒤에 붙는 filler(생성 대상) latent만 마지막 known latent 대비 차이를 - 증폭한다. motion_amplitude=1.0이면 코어 WanImageToVideo와 동일하게 동작한다. - """ - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "vae": ("VAE",), - "width": ("INT", {"default": 832, "min": 16, "max": 4096, "step": 16}), - "height": ("INT", {"default": 480, "min": 16, "max": 4096, "step": 16}), - "length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 4}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), - "motion_amplitude": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.05, - "tooltip": "1.0=코어 WanImageToVideo와 동일. >1.0이면 filler 구간에 슬로우모션 방지 증폭."}), - }, - "optional": { - "clip_vision_output": ("CLIP_VISION_OUTPUT",), - "start_image": ("IMAGE", {"tooltip": "known 프레임(들). 이전 클립의 디코딩된 마지막 N프레임을 넣으면 연속성이 생김. 1장이면 일반 I2V."}), - }, - } - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - CATEGORY = "Dolphin Node/Wan Video" - - def encode(self, positive, negative, vae, width, height, length, batch_size, - motion_amplitude=1.0, clip_vision_output=None, start_image=None): - latent = torch.zeros( - [batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], - device=comfy.model_management.intermediate_device(), - ) - - if start_image is not None: - start_image = comfy.utils.common_upscale( - start_image[:length].movedim(-1, 1), width, height, "bilinear", "center" - ).movedim(1, -1) - n_known_frames = start_image.shape[0] - - # known 프레임 뒤를 회색(0.5) filler로 채운 뒤 통짜로 인코딩 (코어 방식) - image = torch.ones( - (length, height, width, start_image.shape[-1]), - device=start_image.device, dtype=start_image.dtype, - ) * 0.5 - image[:n_known_frames] = start_image - - concat_latent_image = vae.encode(image[:, :, :, :3]) - - # 프레임수 -> latent수 (WAN VAE 4x 시간 압축) - n_known_latents = ((n_known_frames - 1) // 4) + 1 - - # motion_amplitude: filler(생성 대상) latent만 증폭. known 프레임은 그대로. - if motion_amplitude > 1.0 and concat_latent_image.shape[2] > n_known_latents: - base_latent = concat_latent_image[:, :, n_known_latents - 1:n_known_latents] - filler = concat_latent_image[:, :, n_known_latents:] - diff = filler - base_latent - diff_mean = diff.mean(dim=(1, 3, 4), keepdim=True) - diff_centered = diff - diff_mean - scaled = base_latent + diff_centered * motion_amplitude + diff_mean - scaled = torch.clamp(scaled, -6, 6) - concat_latent_image = torch.cat( - [concat_latent_image[:, :, :n_known_latents], scaled], dim=2 - ) - - mask = torch.ones( - (1, 1, latent.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), - device=start_image.device, dtype=start_image.dtype, - ) - mask[:, :, :n_known_latents] = 0.0 - - positive = node_helpers.conditioning_set_values( - positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask} - ) - negative = node_helpers.conditioning_set_values( - negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask} - ) - - if clip_vision_output is not None: - positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output}) - negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output}) - - return (positive, negative, {"samples": latent}) diff --git a/wan_resizer.py b/wan_resizer.py deleted file mode 100644 index 1775814e25bdb7a0e50c757716fee1af7bf180a7..0000000000000000000000000000000000000000 --- a/wan_resizer.py +++ /dev/null @@ -1,56 +0,0 @@ -import math -import torch -import comfy.utils - -class WanNativeResize_Dolphin: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "image": ("IMAGE",), - "target_model": (["480P (832x480)", "720P (1280x720)", "1080P (1920x1080)"], {"default": "720P (1280x720)"}), - "upscale_method": (["nearest-exact", "bilinear", "area", "bicubic", "lanczos"], {"default": "lanczos"}), - "alignment": ("INT", {"default": 16, "min": 8, "max": 64, "step": 8}), - } - } - - RETURN_TYPES = ("IMAGE", "INT", "INT") - RETURN_NAMES = ("IMAGE", "width", "height") - FUNCTION = "resize_for_wan" - CATEGORY = "Dolphin Node/Wan Video" - - def resize_for_wan(self, image, target_model, upscale_method, alignment): - # 1. ComfyUI 이미지 텐서의 형태를 가져옵니다: (Batch/Frames, Height, Width, Channels) - b, h, w, c = image.shape - - # 2. 타겟 모델에 따른 총 픽셀 면적(Area) 세팅 - if "480P" in target_model: - target_area = 832 * 480 - elif "720P" in target_model: - target_area = 1280 * 720 - else: - target_area = 1920 * 1080 - - # 3. Byungjoo님의 수학 로직 (비율 유지 계산) - aspect_ratio = w / h - new_h = math.sqrt(target_area / aspect_ratio) - new_w = new_h * aspect_ratio - - # 4. [핵심] VAE가 좋아하는 배수(Alignment, 보통 16)로 반올림 처리 - new_w = int(round(new_w / alignment) * alignment) - new_h = int(round(new_h / alignment) * alignment) - - # 5. [최적화] 이미 목표 해상도와 일치한다면, 무거운 연산 없이 원본 통과 (패스스루) - if w == new_w and h == new_h: - return (image, new_w, new_h) - - # 6. [핵심] 텐서 차원 변경: ComfyUI (B, H, W, C) -> 리사이즈 엔진용 (B, C, H, W) - image = image.movedim(-1, 1) - - # 7. ComfyUI 내장 엔진을 사용해 초고화질 리사이즈 (Lanczos 지원) - resized_image = comfy.utils.common_upscale(image, new_w, new_h, upscale_method, "disabled") - - # 8. 텐서 차원 원상 복구: (B, C, H, W) -> ComfyUI (B, H, W, C) - resized_image = resized_image.movedim(1, -1) - - return (resized_image, new_w, new_h) \ No newline at end of file diff --git a/wan_svipro_fixed.py b/wan_svipro_fixed.py deleted file mode 100644 index a8e451be40448ef7852a6dab98d5913effa5c708..0000000000000000000000000000000000000000 --- a/wan_svipro_fixed.py +++ /dev/null @@ -1,95 +0,0 @@ -import torch -import comfy.model_management as model_management -import comfy.latent_formats -import node_helpers - - -class WanImageToVideoSVIProFixed_Dolphin: - """ - WanImageToVideoSVIPro(comfyui-kjnodes)의 마스크 버그를 고친 버전. - - 원본은 anchor_samples + prev_samples(motion_latent_count 프레임)를 이어붙여 - concat_latent_image를 만들면서도, concat_mask는 mask[:, :, :1] = 0.0으로 - 앵커 프레임 1장만 "known"으로 표시한다. 그래서 실제로 이어붙인 모션 컨텍스트 - 프레임들이 "생성 대상(mask=1)"으로 취급돼 모델이 그 내용을 무시/이탈하고, - 클립이 몇 프레임 반복되다가 전혀 다른 영상으로 튀는 현상이 생긴다. - - 이 버전은 anchor + motion_latent 프레임 수 전체를 known(0.0)으로 마스킹해서 - 이어붙인 모션 컨텍스트가 실제로 고정된 참고 정보로 취급되게 한다. - - 추가로 PainterI2V/PainterFLF2V(ComfyUI-PainterI2V, Comfyui-PainterFLF2V)의 - "motion_amplitude" 슬로우모션 방지 트릭을 이식했다: anchor 뒤에 붙는 순수 - filler(생성 대상) 구간과 anchor의 latent 차이를 증폭해서, 4-step lightning - 계열 로라가 정지 이미지를 보고 동작을 과소평가하는 경향을 보정한다. 실제 - motion_latent(진짜 이전 프레임)는 건드리지 않고 filler 구간에만 적용한다. - """ - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 4}), - "anchor_samples": ("LATENT",), - "motion_latent_count": ("INT", {"default": 1, "min": 0, "max": 128, "step": 1, - "tooltip": "prev_samples 끝에서 몇 프레임을 모션 컨텍스트로 이어붙일지."}), - "motion_amplitude": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.05, - "tooltip": "1.0=비활성(원본과 동일). >1.0이면 PainterI2V식 슬로우모션 방지 증폭을 filler 구간에 적용."}), - }, - "optional": { - "prev_samples": ("LATENT", {"tooltip": "이전 클립의 (디코드 안 된) latent. 끝의 motion_latent_count 프레임만 사용."}), - }, - } - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - CATEGORY = "Dolphin Node/Wan Video" - - def encode(self, positive, negative, length, anchor_samples, motion_latent_count, - motion_amplitude=1.0, prev_samples=None): - anchor_latent = anchor_samples["samples"].clone() - B, C, T, H, W = anchor_latent.shape - - total_latents = ((length - 1) // 4) + 1 - empty_latent = torch.zeros([B, 16, total_latents, H, W], device=model_management.intermediate_device()) - device = anchor_latent.device - dtype = anchor_latent.dtype - - if prev_samples is None or motion_latent_count == 0: - known_frames = anchor_latent.shape[2] - image_cond_latent = anchor_latent - else: - motion_latent = prev_samples["samples"][:, :, -motion_latent_count:].clone() - known_frames = anchor_latent.shape[2] + motion_latent.shape[2] - image_cond_latent = torch.cat([anchor_latent, motion_latent], dim=2) - - padding_size = total_latents - known_frames - padding = torch.zeros(1, C, padding_size, H, W, dtype=dtype, device=device) - padding = comfy.latent_formats.Wan21().process_out(padding) - - # PainterI2V식 증폭: anchor 대비 filler(padding)의 차이를 키워서 - # "여기서부터는 크게 움직여야 한다"는 신호를 강하게 준다. 실제 - # motion_latent(진짜 이전 프레임)는 그대로 두고 filler에만 적용. - if motion_amplitude > 1.0 and padding_size > 0: - base_latent = anchor_latent[:, :, -1:].expand(-1, -1, padding_size, -1, -1) - diff = padding - base_latent - diff_mean = diff.mean(dim=(1, 3, 4), keepdim=True) - diff_centered = diff - diff_mean - padding = base_latent + diff_centered * motion_amplitude + diff_mean - padding = torch.clamp(padding, -6, 6) - - image_cond_latent = torch.cat([image_cond_latent, padding], dim=2) - - mask = torch.ones((1, 1, total_latents, H, W), device=device, dtype=dtype) - mask[:, :, :known_frames] = 0.0 # anchor + motion context 프레임 전부 known 처리 (버그 수정 지점) - - positive = node_helpers.conditioning_set_values( - positive, {"concat_latent_image": image_cond_latent, "concat_mask": mask} - ) - negative = node_helpers.conditioning_set_values( - negative, {"concat_latent_image": image_cond_latent, "concat_mask": mask} - ) - - return (positive, negative, {"samples": empty_latent}) diff --git a/wan_svipro_gray.py b/wan_svipro_gray.py deleted file mode 100644 index 0f78d2a0df88ed162037b21aba0f2ed6d70914bd..0000000000000000000000000000000000000000 --- a/wan_svipro_gray.py +++ /dev/null @@ -1,145 +0,0 @@ -import torch -import comfy.model_management as model_management -import node_helpers - - -class WanImageToVideoSVIProGray_Dolphin: - """ - WanImageToVideoSVIPro(comfyui-kjnodes)의 드롭인 교체 노드 — 슬로우모션 원인 수정판. - - 원본 SVIPro는 아직 생성되지 않은 구간(filler)을 latent 공간에서 직접 만든다: - - padding = comfy.latent_formats.Wan21().process_out(torch.zeros(...)) - - `process_out(x) = x * std + mean` 이므로 `process_out(0)`은 **latents_mean 상수**다. - 즉 81프레임(=latent 21프레임) 기준으로 conditioning의 20/21이 시간축으로 전혀 - 변하지 않는 상수 블록이 되고, 모델은 "앞으로 아무 변화 없음" 신호를 받아 동작을 - 억제한다(슬로우모션). 게다가 이 값은 학습 때 그 자리에서 본 적 없는 분포 밖 입력이다. - - 코어 `WanImageToVideo`와 PainterI2V는 filler를 **픽셀 공간의 회색(0.5)** 으로 만든 뒤 - VAE 인코딩한다 — 모델이 학습 중 "unknown 구간"으로 실제 학습한 표현이다. 실측상 - PainterI2V의 슬로우모션 발생률이 ~0%인 반면 SVIPro는 ~70%였다. - - 이 노드는 SVIPro의 latent 기반 인터페이스(anchor_samples/prev_samples/ - motion_latent_count)를 그대로 유지하면서 filler를 회색 VAE 인코딩으로 바꾼다. - - v1(첫 시도)은 known latent와 회색 filler latent를 **따로따로** vae.encode()해서 - 이어붙였는데, 이게 "처음부터 회색 노이즈만 나옴" 버그로 이어졌다. WAN VAE는 인과적 - (causal) 압축이라 각 latent 프레임이 그 시점까지의 시퀀스 문맥을 전제로 인코딩되고, - WAN은 3D self-attention이라 시퀀스 전체에 걸쳐 정보가 섞인다 — 별도 인코딩된 두 - 구간을 이어붙이면 모델이 학습 때 한 번도 못 본 이음매가 생기고, 그게 anchor 프레임 - 까지 포함해서 시퀀스 전체를 오염시킨 것으로 보인다. - - 이번 버전은 PainterI2V/코어 WanImageToVideo와 동일하게, known 구간을 **픽셀로 - 디코딩**한 뒤 회색 filler 픽셀과 하나의 시퀀스로 합쳐서 **단 한 번**만 인코딩한다. - known 쪽에 디코드->재인코드 왕복이 한 번 더 생기지만(코드 리뷰로 이미 알려진 트레이드 - 오프), 이음매 불일치 문제는 구조적으로 사라진다. - - 추가로 원본의 마스크 버그도 고쳤다: 원본은 `mask[:, :, :1] = 0.0`으로 앵커 1프레임만 - known 처리해서 prev_samples로 이어붙인 모션 프레임이 생성 대상으로 취급됐다. - 여기서는 anchor + motion 프레임 전체를 known으로 마스킹한다. - """ - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "vae": ("VAE", {"tooltip": "회색 filler를 인코딩하는 데 사용. 원본 SVIPro엔 없던 입력."}), - "length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 4}), - "anchor_samples": ("LATENT",), - "motion_latent_count": ("INT", {"default": 1, "min": 0, "max": 128, "step": 1, - "tooltip": "prev_samples 끝에서 몇 latent 프레임을 모션 컨텍스트로 이어붙일지."}), - }, - "optional": { - "prev_samples": ("LATENT", {"tooltip": "이전 클립의 latent. 끝의 motion_latent_count 프레임만 사용."}), - }, - } - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("positive", "negative", "latent") - FUNCTION = "encode" - CATEGORY = "Dolphin Node/Wan Video" - - def encode(self, positive, negative, vae, length, anchor_samples, motion_latent_count, - prev_samples=None): - anchor_latent = anchor_samples["samples"].clone() - B, C, T, H, W = anchor_latent.shape - device, dtype = anchor_latent.device, anchor_latent.dtype - log = lambda *a: print("[SVIProGray]", *a) - log(f"anchor_latent shape={tuple(anchor_latent.shape)} dtype={dtype} " - f"min={anchor_latent.min().item():.4f} max={anchor_latent.max().item():.4f} " - f"mean={anchor_latent.mean().item():.4f}") - - total_latents = ((length - 1) // 4) + 1 - empty_latent = torch.zeros( - [B, 16, total_latents, H, W], device=model_management.intermediate_device() - ) - - if prev_samples is None or motion_latent_count == 0: - known = anchor_latent - else: - motion_latent = prev_samples["samples"][:, :, -motion_latent_count:].clone() - log(f"motion_latent shape={tuple(motion_latent.shape)} " - f"min={motion_latent.min().item():.4f} max={motion_latent.max().item():.4f}") - known = torch.cat([anchor_latent, motion_latent.to(device=device, dtype=dtype)], dim=2) - - known_frames = min(known.shape[2], total_latents) - known = known[:, :, :known_frames] - padding_size = total_latents - known_frames - log(f"total_latents={total_latents} known_frames={known_frames} padding_size={padding_size}") - - if padding_size > 0: - # known을 픽셀로 디코딩 -> 회색(0.5) filler 픽셀과 하나의 시퀀스로 합쳐서 - # 단 한 번만 인코딩 (PainterI2V/코어 WanImageToVideo와 동일한 방식). - # - # decode와 encode를 한 노드 안에서 연속 호출하면 decode가 남긴 중간 버퍼가 - # 안 비워진 채로 encode가 (최대 length 프레임 전체를 다시 인코딩하는) 큰 - # 버퍼를 새로 잡으려다 OOM이 날 수 있다. 그래서 decode 결과를 CPU로 옮겨 - # GPU 버퍼를 바로 비우고, encode 직전에 캐시를 명시적으로 정리한다. - known_pixels = vae.decode(known) - del known - log(f"known_pixels (raw decode) shape={tuple(known_pixels.shape)}") - if known_pixels.ndim == 5: - known_pixels = known_pixels[0] # [B,T,H,W,C] -> [T,H,W,C], B==1 가정 - known_pixels = known_pixels[:, :, :, :3].to("cpu") - log(f"known_pixels shape={tuple(known_pixels.shape)} " - f"min={known_pixels.min().item():.4f} max={known_pixels.max().item():.4f} " - f"mean={known_pixels.mean().item():.4f}") - model_management.soft_empty_cache() - - n_known_px = known_pixels.shape[0] - n_gray_px = max(length - n_known_px, 0) - log(f"n_known_px={n_known_px} n_gray_px={n_gray_px}") - if n_gray_px > 0: - gray = torch.ones( - (n_gray_px, known_pixels.shape[1], known_pixels.shape[2], 3), - dtype=known_pixels.dtype, - ) * 0.5 - combined = torch.cat([known_pixels, gray], dim=0) - else: - combined = known_pixels - del known_pixels - log(f"combined shape={tuple(combined.shape)}") - - image_cond_latent = vae.encode(combined).to(device=device, dtype=dtype) - del combined - model_management.soft_empty_cache() - log(f"image_cond_latent shape={tuple(image_cond_latent.shape)} " - f"min={image_cond_latent.min().item():.4f} max={image_cond_latent.max().item():.4f} " - f"mean={image_cond_latent.mean().item():.4f} std={image_cond_latent.std().item():.4f}") - else: - image_cond_latent = known - - mask = torch.ones((1, 1, total_latents, H, W), device=device, dtype=dtype) - mask[:, :, :known_frames] = 0.0 # anchor + motion 프레임 전부 known (원본 마스크 버그 수정) - - positive = node_helpers.conditioning_set_values( - positive, {"concat_latent_image": image_cond_latent, "concat_mask": mask} - ) - negative = node_helpers.conditioning_set_values( - negative, {"concat_latent_image": image_cond_latent, "concat_mask": mask} - ) - - return (positive, negative, {"samples": empty_latent}) diff --git a/wan_svipro_motion.py b/wan_svipro_motion.py deleted file mode 100644 index fb9d0322c705e08fa35dfd04cee8bd1d43355554..0000000000000000000000000000000000000000 --- a/wan_svipro_motion.py +++ /dev/null @@ -1,152 +0,0 @@ -import torch -import comfy.latent_formats -import comfy.model_management as model_management -import node_helpers - - -class WanImageToVideoSVIProMotion_Dolphin: - """ - WanImageToVideoSVIPro(comfyui-kjnodes)의 드롭인 교체 노드 — filler 표현은 원본 그대로 두고 - **모션 증폭만** 얹은 버전. - - [wan_svipro_gray.py](wan_svipro_gray.py)와의 차이 — 그쪽은 filler를 회색(0.5) 픽셀 VAE - 인코딩으로 바꾸고 마스크도 anchor+motion 전체 known으로 바꿨다. 두 가지를 동시에 바꾸는 - 바람에 A/B가 안 깨끗했고, 무엇보다 SVI Pro LoRA는 **원본 규약**(process_out(zeros) 패딩 + - mask 첫 프레임만 known) 위에서 파인튜닝된 가중치라, 회색 filler는 base Wan에겐 - in-distribution이어도 SVI LoRA에겐 분포 밖 입력이 된다. 실제로 회색 버전은 Clip1부터 - 그레이 노이즈가 났다. - - 이 노드는 그래서 **패딩과 마스크를 원본과 비트 단위로 동일하게** 두고, PainterI2V가 실제로 - 슬로우모션을 잡는 그 연산 하나만 추가한다: - - diff = filler - base_latent # 마지막 known latent 기준 - diff_mean = diff.mean(dim=(1,3,4)) # 밝기 성분 분리 (증폭에서 제외) - scaled = base + (diff - diff_mean) * amplitude + diff_mean - - 코어 `WanImageToVideo`도 이미 회색 filler를 쓰는데 슬로우모션이 난다 — - [slowmo_ab_test_A_core_wanimagetovideo.json](slowmo_ab_test_A_core_wanimagetovideo.json)에서 - 확인됐다. 즉 filler를 뭘로 채우냐가 아니라 **앵커 대비 거리를 얼마나 벌려주냐**가 모션 - 진폭을 결정한다. 그 거리를 키우는 게 이 증폭이다. - - amplitude_mode: - - "constant": PainterI2V와 동일. filler 전 구간을 같은 배율로 증폭. - 원본 패딩은 시간축으로 상수라 증폭해도 상수 — "앵커에서 이만큼 멀어진다"는 - 거리 신호만 커진다. - - "ramp": 첫 filler 프레임 1.0 → 마지막 프레임 motion_amplitude로 선형 증가. - 상수 블록에 실제 시간 기울기를 만들어 "뒤로 갈수록 더 멀어진다"는 - 신호를 준다. 원본 패딩의 "미래에 변화 없음" 결함을 직접 겨냥한 변형. - - 출력이 conditioning 2쌍인 이유(PainterI2VAdvanced와 동일한 설계): 모션은 high-noise - 스테이지가 결정하고 low-noise는 디테일·색을 정리한다. low까지 증폭본을 먹이면 색 드리프트와 - 아티팩트만 늘어난다. high_* 는 증폭본, low_* 는 원본 그대로다. - - motion_amplitude=1.0이면 두 쌍 모두 상류 원본 SVIPro의 출력과 완전히 동일하다(회귀 확인용). - """ - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 4}), - "anchor_samples": ("LATENT",), - "motion_latent_count": ("INT", {"default": 1, "min": 0, "max": 128, "step": 1, - "tooltip": "prev_samples 끝에서 몇 latent 프레임을 모션 컨텍스트로 이어붙일지."}), - "motion_amplitude": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.05, - "tooltip": "1.0=상류 SVIPro와 동일. 1.15부터 시작해서 1.3/1.5로 스윕."}), - "amplitude_mode": (["constant", "ramp"], {"default": "constant", - "tooltip": "constant=PainterI2V와 동일. ramp=뒤로 갈수록 증폭을 키워 시간 기울기를 만듦."}), - }, - "optional": { - "prev_samples": ("LATENT", {"tooltip": "이전 클립의 latent. 끝의 motion_latent_count 프레임만 사용."}), - }, - } - - RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("high_positive", "high_negative", "low_positive", "low_negative", "latent") - FUNCTION = "encode" - CATEGORY = "Dolphin Node/Wan Video" - - def encode(self, positive, negative, length, anchor_samples, motion_latent_count, - motion_amplitude, amplitude_mode, prev_samples=None): - log = lambda *a: print("[SVIProMotion]", *a) - - anchor_latent = anchor_samples["samples"].clone() - B, C, T, H, W = anchor_latent.shape - device, dtype = anchor_latent.device, anchor_latent.dtype - - total_latents = ((length - 1) // 4) + 1 - empty_latent = torch.zeros( - [B, 16, total_latents, H, W], device=model_management.intermediate_device() - ) - - # ---- 여기부터 상류 kjnodes WanImageToVideoSVIPro와 동일 ---- - if prev_samples is None or motion_latent_count == 0: - known_frames = anchor_latent.shape[2] - image_cond_latent = anchor_latent - else: - motion_latent = prev_samples["samples"][:, :, -motion_latent_count:].clone() - known_frames = anchor_latent.shape[2] + motion_latent.shape[2] - image_cond_latent = torch.cat( - [anchor_latent, motion_latent.to(device=device, dtype=dtype)], dim=2 - ) - - padding_size = total_latents - known_frames - log(f"total_latents={total_latents} known_frames={known_frames} padding_size={padding_size} " - f"amplitude={motion_amplitude} mode={amplitude_mode}") - - if padding_size > 0: - padding = torch.zeros(1, C, padding_size, H, W, dtype=dtype, device=device) - padding = comfy.latent_formats.Wan21().process_out(padding) - image_cond_latent = torch.cat([image_cond_latent, padding], dim=2) - elif padding_size < 0: - # known이 total보다 길면 상류는 그냥 크래시한다. 잘라서 진행. - log(f"known_frames > total_latents — {total_latents}프레임으로 자름") - image_cond_latent = image_cond_latent[:, :, :total_latents] - known_frames = total_latents - - mask = torch.ones((1, 1, total_latents, H, W), device=device, dtype=dtype) - mask[:, :, :1] = 0.0 - # ---- 상류와 동일한 부분 끝 ---- - - cond_low = image_cond_latent - cond_high = self._amplify(cond_low, known_frames, motion_amplitude, amplitude_mode, log) - - high_positive = node_helpers.conditioning_set_values( - positive, {"concat_latent_image": cond_high, "concat_mask": mask}) - high_negative = node_helpers.conditioning_set_values( - negative, {"concat_latent_image": cond_high, "concat_mask": mask}) - low_positive = node_helpers.conditioning_set_values( - positive, {"concat_latent_image": cond_low, "concat_mask": mask}) - low_negative = node_helpers.conditioning_set_values( - negative, {"concat_latent_image": cond_low, "concat_mask": mask}) - - return (high_positive, high_negative, low_positive, low_negative, - {"samples": empty_latent}) - - @staticmethod - def _amplify(cond, known_frames, amplitude, mode, log): - """filler 구간만 마지막 known latent 대비 차이를 증폭. known 프레임은 안 건드림.""" - if amplitude <= 1.0 or cond.shape[2] <= known_frames: - return cond - - base_latent = cond[:, :, known_frames - 1:known_frames] - filler = cond[:, :, known_frames:] - n_filler = filler.shape[2] - - diff = filler - base_latent - diff_mean = diff.mean(dim=(1, 3, 4), keepdim=True) # 밝기 성분 — 증폭 대상 아님 - diff_centered = diff - diff_mean - - if mode == "ramp" and n_filler > 1: - scale = torch.linspace(1.0, amplitude, n_filler, - device=cond.device, dtype=cond.dtype).view(1, 1, n_filler, 1, 1) - else: - scale = amplitude - - scaled = torch.clamp(base_latent + diff_centered * scale + diff_mean, -6, 6) - log(f"amplified filler[{known_frames}:{cond.shape[2]}] " - f"|diff| {diff.abs().mean().item():.4f} -> {(scaled - base_latent).abs().mean().item():.4f}") - - return torch.cat([cond[:, :, :known_frames], scaled], dim=2) diff --git a/web/dolphin_lora_rows.js b/web/dolphin_lora_rows.js deleted file mode 100644 index 4141d382d01aec469ad05452a3b7c9dff5a5db0c..0000000000000000000000000000000000000000 --- a/web/dolphin_lora_rows.js +++ /dev/null @@ -1,541 +0,0 @@ -import { app } from "../../scripts/app.js"; - -// Compacts DolphinVisionDirector's 16 lora slots (each: combo + on/off + weight -// string = 3 separate stock widgets) into single power-lora-loader-style rows: -// [toggle] [lora name] [weight]. The 3 original widgets are kept exactly where -// they are in node.widgets (never reordered/removed) so saved-workflow -// widgets_values arrays keep working; they are only visually hidden (0 height). -// The compact rows are appended at the END of node.widgets via addCustomWidget, -// which never shifts the index of any pre-existing widget. - -const VISION_NODE_NAME = "DolphinVisionDirector"; -const ARCH_NODE_NAME = "DolphinArchitectV16"; -const NUM_CLIPS = 4; -const NUM_LORAS = 4; -const NUM_ARCH_LORAS = 6; -const ROW_HEIGHT = 24; -const HEADER_HEIGHT = 18; - -function fitText(ctx, str, maxWidth) { - if (!str) return ""; - const full = ctx.measureText(str).width; - if (full <= maxWidth) return str; - const ellipsis = "…"; - let lo = 0, hi = str.length; - while (lo < hi) { - const mid = Math.ceil((lo + hi) / 2); - if (ctx.measureText(str.slice(0, mid) + ellipsis).width <= maxWidth) lo = mid; - else hi = mid - 1; - } - return str.slice(0, lo) + ellipsis; -} - -function hideWidget(widget, suffix = "") { - if (!widget.__dolphinHidden) { - widget.__dolphinHidden = true; - widget.origType = widget.type; - widget.origComputeSize = widget.computeSize; - widget.origSerializeValue = widget.serializeValue; - // Zero, not negative: with 48 widgets hidden at once, the usual "-4" - // margin-collapse trick (fine for a single hidden widget) compounds to a - // large negative offset that throws off LiteGraph's remaining-space - // distribution to flexible multiline widgets (essential_actions, - // artistic_vibe), ballooning their height. - widget.computeSize = () => [0, 0]; - widget.type = "converted-widget" + suffix; - } - // This ComfyUI build renders stock combo/string/boolean widgets as real DOM - // elements (widget.element) absolutely positioned over the canvas, not pure - // canvas draws — computeSize/type alone only affects canvas layout and does - // NOT hide the floating DOM element. Same fix the bundled tripleksampler - // custom node's own UI extension uses for its dynamic widgets. - widget.hidden = true; - if (widget.options) widget.options.hidden = true; - if (widget.element) { - widget.element.style.display = "none"; - const label = widget.element.previousElementSibling; - if (label && label.textContent === widget.name + ":") { - label.style.display = "none"; - } - } -} - -// SERIALIZE_FALSE: every custom widget in this file is presentation-only — the real -// values live on the stock widgets it wraps. LGraphNode.serialize() in this build does -// NOT call serializeValue(); it reads `widget.value` directly and writes `value ?? null`, -// skipping a widget only when `widget.serialize === false` (note: the plain property, -// not `options.serialize`). Without this flag every custom widget appended a trailing -// null to widgets_values — the Architect node was saving 26 entries where 19 are real. -// The nulls were harmless (custom widgets are appended AFTER the stock ones, so no index -// shifted) but they grow every saved workflow, and old files with the extra trailing -// values still load correctly since only the tail is affected. -class DolphinSectionHeaderWidget { - constructor(label) { - this.type = "custom"; - this.name = "dolphin_header_" + label; - this.__dolphinHidden = true; // never touched by hideWidget, just marks "not a real value widget" - this.serialize = false; - this.label = label; - } - computeSize(width) { - return [width, HEADER_HEIGHT]; - } - draw(ctx, node, widgetWidth, posY, height) { - ctx.save(); - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - ctx.globalAlpha = 0.75; - ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - ctx.font = "bold 11px Arial"; - ctx.fillText(this.label, 15, posY + height * 0.5); - ctx.restore(); - } - mouse() { - return false; - } - serializeValue() { - return undefined; - } -} - -class DolphinLoraRowWidget { - constructor(name, comboW, onW, weightW) { - this.type = "custom"; - this.name = name; - this.serialize = false; // SERIALIZE_FALSE 참고 - this.comboW = comboW; - this.onW = onW; - this.weightW = weightW; - this.hitAreas = { - toggle: [0, 0], - lora: [0, 0], - weight: [0, 0], - }; - } - computeSize(width) { - return [width, ROW_HEIGHT]; - } - serializeValue() { - return undefined; // real values live on comboW/onW/weightW, not on this row - } - draw(ctx, node, widgetWidth, posY, height) { - const margin = 15; - const inner = 6; - const on = this.onW.value !== false; - ctx.save(); - - // row background - ctx.beginPath(); - ctx.fillStyle = LiteGraph.WIDGET_BGCOLOR; - ctx.roundRect(margin, posY + 1, widgetWidth - margin * 2, height - 2, [height * 0.25]); - ctx.fill(); - ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR; - ctx.stroke(); - - const midY = posY + height * 0.5; - let x = margin + inner; - - // toggle switch - const toggleH = height - 10; - const toggleW = toggleH * 1.7; - ctx.beginPath(); - ctx.fillStyle = "#00000055"; - ctx.roundRect(x, posY + 5, toggleW, toggleH, [toggleH * 0.5]); - ctx.fill(); - ctx.beginPath(); - ctx.fillStyle = on ? "#8ab4ff" : "#777"; - const knobX = on ? x + toggleW - toggleH * 0.5 : x + toggleH * 0.5; - ctx.arc(knobX, posY + height * 0.5, toggleH * 0.36, 0, Math.PI * 2); - ctx.fill(); - this.hitAreas.toggle = [x, toggleW]; - x += toggleW + inner * 1.5; - - ctx.globalAlpha = on ? 1 : 0.45; - - // weight field (measure first, right-aligned) - ctx.font = "11px Arial"; - const weightText = (this.weightW.value ?? "").toString(); - const weightBoxW = Math.min(100, Math.max(46, ctx.measureText(weightText).width + 14)); - const weightX = widgetWidth - margin - inner - weightBoxW; - - // lora name (fills remaining space) - const nameX = x; - const nameW = Math.max(20, weightX - inner - nameX); - const hasLora = this.comboW.value && this.comboW.value !== "None"; - ctx.fillStyle = hasLora ? LiteGraph.WIDGET_TEXT_COLOR : LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - ctx.fillText(fitText(ctx, hasLora ? this.comboW.value : "— pick lora —", nameW), nameX, midY); - this.hitAreas.lora = [nameX, nameW]; - - // weight box - ctx.beginPath(); - ctx.fillStyle = "#00000055"; - ctx.roundRect(weightX, posY + 4, weightBoxW, height - 8, [4]); - ctx.fill(); - ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - ctx.textAlign = "center"; - ctx.fillText(fitText(ctx, weightText, weightBoxW - 10), weightX + weightBoxW / 2, midY); - this.hitAreas.weight = [weightX, weightBoxW]; - - ctx.restore(); - } - _within(pos, bounds) { - return pos[0] >= bounds[0] && pos[0] <= bounds[0] + bounds[1]; - } - mouse(event, pos, node) { - if (event.type !== "pointerdown") return false; - if (this._within(pos, this.hitAreas.toggle)) { - this.onW.value = this.onW.value === false ? true : false; - node.setDirtyCanvas(true, true); - return true; - } - if (this._within(pos, this.hitAreas.lora)) { - let values = this.comboW.options?.values ?? []; - if (typeof values === "function") values = values(this.comboW, node) ?? []; - const canvas = app.canvas; - // Plain string array + single top-level callback is what makes - // LiteGraph.ContextMenu's built-in search/filter box appear (same - // pattern rgthree's showLoraChooser and KJNodes use) — passing - // {content, callback} objects per item silently skips that filter. - new LiteGraph.ContextMenu([...values], { - event, - scale: Math.max(1, canvas.ds?.scale ?? 1), - className: "dark", - title: "Choose a lora", - callback: (value) => { - if (typeof value !== "string") return; - this.comboW.value = value; - if (typeof this.comboW.callback === "function") { - this.comboW.callback(value, canvas, node, pos, event); - } - node.setDirtyCanvas(true, true); - }, - }); - return true; - } - if (this._within(pos, this.hitAreas.weight)) { - app.canvas.prompt( - "Weight (base, high, low)", - this.weightW.value ?? "", - (v) => { - if (v !== null) { - this.weightW.value = v; - node.setDirtyCanvas(true, true); - } - }, - event, - ); - return true; - } - return false; - } -} - -class DolphinTextFieldWidget { - constructor(label, targetW, multiline = false) { - this.type = "custom"; - this.name = "dolphin_field_" + targetW.name; - this.serialize = false; // SERIALIZE_FALSE 참고 - this.label = label; - this.targetW = targetW; - this.multiline = multiline; - this.hitAreas = { field: [0, 0] }; - } - computeSize(width) { - return [width, ROW_HEIGHT]; - } - serializeValue() { - return undefined; // real value lives on targetW - } - draw(ctx, node, widgetWidth, posY, height) { - const margin = 15; - const inner = 8; - ctx.save(); - ctx.beginPath(); - ctx.fillStyle = LiteGraph.WIDGET_BGCOLOR; - ctx.roundRect(margin, posY + 1, widgetWidth - margin * 2, height - 2, [height * 0.25]); - ctx.fill(); - ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR; - ctx.stroke(); - - const midY = posY + height * 0.5; - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - ctx.fillText(this.label, margin + inner, midY); - const labelW = ctx.measureText(this.label).width; - - const valueX = margin + inner + labelW + inner; - const valueW = Math.max(20, widgetWidth - margin - inner - valueX); - const text = (this.targetW.value ?? "").toString().replace(/\n/g, " ⏎ "); - ctx.fillStyle = text ? LiteGraph.WIDGET_TEXT_COLOR : LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - ctx.fillText(fitText(ctx, text || "(empty, click to edit)", valueW), valueX, midY); - this.hitAreas.field = [margin, widgetWidth - margin * 2]; - ctx.restore(); - } - _within(pos, bounds) { - return pos[0] >= bounds[0] && pos[0] <= bounds[0] + bounds[1]; - } - mouse(event, pos, node) { - if (event.type !== "pointerdown") return false; - if (this._within(pos, this.hitAreas.field)) { - app.canvas.prompt( - this.label, - this.targetW.value ?? "", - (v) => { - if (v !== null) { - this.targetW.value = v; - node.setDirtyCanvas(true, true); - } - }, - event, - this.multiline, - ); - return true; - } - return false; - } -} - -// 숨겨진 스톡 위젯은 .value만 바꿔도 직렬화(widgets_values)에는 제대로 반영되지만, -// 이 빌드는 combo/string/boolean을 실제 DOM 엘리먼트로도 들고 있어서 그쪽 값이 남아 -// 있으면 나중에 ComfyUI의 per-frame DOM 싱크가 옛 값을 되돌려 쓸 수 있다. 그래서 -// .value를 바꿀 때 엘리먼트도 같이 맞춰준다. -function syncWidgetElement(widget) { - const el = widget?.element; - if (!el) return; - try { - if (el.type === "checkbox") el.checked = widget.value !== false; - else if ("value" in el) el.value = widget.value ?? ""; - } catch (e) { - console.error("[Dolphin.LoraRows] element sync failed:", e); - } -} - -// 6개 슬롯을 Python 쪽 기본값(lora="None", on=True, weights="0.0, 0.0, 0.0")으로 -// 되돌리는 버튼. 되돌릴 수 없는 조작이라 ContextMenu로 한 번 확인을 받는다 - -// 파일 상단의 lora chooser와 같은 방식이라 별도 UI 의존성이 없다. -class DolphinResetButtonWidget { - constructor(label, slots, extraW) { - this.type = "custom"; - this.name = "dolphin_reset_button"; - this.serialize = false; // SERIALIZE_FALSE 참고 - this.label = label; - this.slots = slots; // [{comboW, onW, weightW}, ...] - this.extraW = extraW; // manual_triggers (없을 수도 있음) - this.hitAreas = { button: [0, 0] }; - } - computeSize(width) { - return [width, ROW_HEIGHT]; - } - serializeValue() { - return undefined; // 값이 없는 순수 액션 위젯 - } - draw(ctx, node, widgetWidth, posY, height) { - const margin = 15; - ctx.save(); - ctx.beginPath(); - ctx.fillStyle = "#00000055"; - ctx.roundRect(margin, posY + 1, widgetWidth - margin * 2, height - 2, [height * 0.25]); - ctx.fill(); - ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR; - ctx.stroke(); - - ctx.font = "11px Arial"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - ctx.fillText(this.label, widgetWidth * 0.5, posY + height * 0.5); - this.hitAreas.button = [margin, widgetWidth - margin * 2]; - ctx.restore(); - } - _within(pos, bounds) { - return pos[0] >= bounds[0] && pos[0] <= bounds[0] + bounds[1]; - } - _reset(node) { - for (const { comboW, onW, weightW } of this.slots) { - comboW.value = "None"; - onW.value = true; - weightW.value = "0.0, 0.0, 0.0"; - syncWidgetElement(comboW); - syncWidgetElement(onW); - syncWidgetElement(weightW); - } - if (this.extraW) { - this.extraW.value = ""; - syncWidgetElement(this.extraW); - } - node.setDirtyCanvas(true, true); - } - mouse(event, pos, node) { - if (event.type !== "pointerdown") return false; - if (!this._within(pos, this.hitAreas.button)) return false; - const CONFIRM = `Reset all ${this.slots.length} slots`; - new LiteGraph.ContextMenu([CONFIRM, "Cancel"], { - event, - scale: Math.max(1, app.canvas.ds?.scale ?? 1), - className: "dark", - title: "Clear every lora, weight and trigger?", - callback: (value) => { - if (value === CONFIRM) this._reset(node); - }, - }); - return true; - } -} - -// buildRows(node) does the type-specific widget lookup + addCustomWidget calls -// and returns the array of stock widgets that got compacted into rows (so they -// can be hidden). Everything past this point (hiding, retry timers, size snap) -// is identical for every node type this gets installed on. -function installLoraRows(nodeType, buildRows) { - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - const ret = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - const node = this; - - // Everything below is purely cosmetic (compact lora rows). If anything - // in here throws, it must NEVER propagate — an uncaught error here - // during graph.configure() can abort loading the rest of the workflow, - // leaving a blank canvas. Fail loud in the console, fail safe for the user. - try { - if (!node.__dolphinLoraRowsBuilt) { - node.__dolphinLoraRowsBuilt = true; - node.__dolphinHiddenWidgets = buildRows(node) || []; - } - - // widget.element (the real DOM ) may not exist yet at - // construction time, and ComfyUI's own per-frame DOM-sync can re-show - // it later — so (re-)apply hiding now and on a couple of deferred - // passes to make sure it sticks. This does NOT touch node.size, so it - // never fights a manual resize the user makes afterwards. - const applyHiding = () => { - try { - for (const w of node.__dolphinHiddenWidgets || []) hideWidget(w); - node.setDirtyCanvas(true, true); - } catch (e) { - console.error("[Dolphin.LoraRows] applyHiding failed:", e); - } - }; - applyHiding(); - setTimeout(applyHiding, 50); - setTimeout(applyHiding, 250); - - // Snap node.size to the freshly computed minimum ONCE on (re)build. - // Collapsing many stock widgets into compact rows frees up a lot of - // height; a stale saved node.size (from before this UI existed) would - // otherwise leave that freed space around, and flexible multiline - // widgets auto-grow to fill it. - setTimeout(() => { - try { - const computed = node.computeSize(); - node.size = node.size || [0, 0]; - node.size[1] = computed[1]; - node.setDirtyCanvas(true, true); - } catch (e) { - console.error("[Dolphin.LoraRows] size snap failed:", e); - } - }, 300); - } catch (e) { - console.error("[Dolphin.LoraRows] onNodeCreated hook failed:", e); - } - return ret; - }; -} - -function buildVisionDirectorRows(node) { - const hidden = []; - for (let c = 1; c <= NUM_CLIPS; c++) { - node.addCustomWidget(new DolphinSectionHeaderWidget(`── Clip ${c} Loras ──`)); - for (let n = 1; n <= NUM_LORAS; n++) { - const comboW = node.widgets.find((w) => w.name === `c${c}_lora_${n}`); - const onW = node.widgets.find((w) => w.name === `c${c}_lora_${n}_on`); - const weightW = node.widgets.find((w) => w.name === `c${c}_w_${n}`); - if (!comboW || !onW || !weightW) continue; - hidden.push(comboW, onW, weightW); - node.addCustomWidget(new DolphinLoraRowWidget(`c${c}_lora_${n}_row`, comboW, onW, weightW)); - } - } - const manualTriggersW = node.widgets.find((w) => w.name === "manual_triggers"); - if (manualTriggersW) { - hidden.push(manualTriggersW); - node.addCustomWidget(new DolphinTextFieldWidget("manual_triggers:", manualTriggersW, true)); - } - return hidden; -} - -// DolphinArchitectV16 (🐬 Dolphin Architect V16): one flat bank of lora_1..6 / -// lora_1_on..6_on / weights_1..6 (each already base+high+low as one comma -// string, e.g. "0.4, 0.7, 0") — no per-clip grouping, so no section headers. -// 파일명에 HIGH/LOW가 들어있으면 Wan2.2 분리 로라로 보고 그쪽 모델 슬롯만 켠다 -// (base,high,low). 둘 다 없으면 Wan2.1 등 범용 로라로 보고 셋 다 켠다. "high_noise"/ -// "low_noise" 같은 변형도 그냥 substring 매치라 자동으로 걸린다. -// HN/LN 약어(-HN-, _HN_, ...-HN.safetensors 식)는 앞뒤가 영숫자가 아닐 때만 매치하는 -// 토큰 경계 정규식을 쓴다 - 그냥 substring이면 "krea2DepthControlnet"(contro-L-N-et) -// 같은 무관한 파일명이 "ln"을 우연히 포함해서 Low Noise로 오인식된다. -const HN_TOKEN = /(?:^|[^a-z0-9])hn(?:[^a-z0-9]|$)/; -const LN_TOKEN = /(?:^|[^a-z0-9])ln(?:[^a-z0-9]|$)/; -// 한 글자 H/L 토큰(-H-, _H_, -L-, _L_, 그리고 확장자/이름 끝의 _H·-L 등). -// hn/ln보다 한 글자 짧아서 오탐 위험이 훨씬 크기 때문에 두 가지로 좁혔다: -// (1) 구분자를 '-'와 '_'로만 한정한다. hn/ln이 쓰는 일반 토큰 경계([^a-z0-9])까지 -// 허용하면 "flaccid-v1.0"의 점·공백·괄호 같은 문자도 경계가 되어 범위가 급격히 넓어진다. -// (2) 대문자만 매치한다(그래서 소문자화한 n이 아니라 원본 raw로 검사). HIGH/LOW 약어는 -// 관례적으로 대문자이고, 소문자까지 받으면 "..._v2_h_final" 같은 무관한 이름이 걸린다. -const H_TOKEN = /[-_]H(?=[-_]|\.[A-Za-z0-9]+$|$)/; -const L_TOKEN = /[-_]L(?=[-_]|\.[A-Za-z0-9]+$|$)/; -function guessLoraWeights(name) { - if (!name || name === "None") return null; - const raw = String(name); - const n = raw.toLowerCase(); - if (n.includes("high") || HN_TOKEN.test(n) || H_TOKEN.test(raw)) return "1, 1, 0"; - if (n.includes("low") || LN_TOKEN.test(n) || L_TOKEN.test(raw)) return "0, 0, 1"; - return "1, 1, 1"; -} - -// comboW.callback은 유저가 드롭다운/행-UI에서 실제로 선택했을 때만 불린다 - 워크플로우 -// 로드 시 configure()가 widgets_values를 복원하는 경로는 .value를 직접 대입할 뿐 이 -// callback을 거치지 않으므로, 저장된 수동 가중치를 자동으로 덮어쓸 위험이 없다. -function wireAutoWeight(comboW, weightW) { - const orig = comboW.callback; - comboW.callback = function (value, ...rest) { - const guess = guessLoraWeights(value); - if (guess !== null) weightW.value = guess; - return orig ? orig.apply(this, [value, ...rest]) : undefined; - }; -} - -function buildArchitectRows(node) { - const hidden = []; - const slots = []; - for (let n = 1; n <= NUM_ARCH_LORAS; n++) { - const comboW = node.widgets.find((w) => w.name === `lora_${n}`); - const onW = node.widgets.find((w) => w.name === `lora_${n}_on`); - const weightW = node.widgets.find((w) => w.name === `weights_${n}`); - if (!comboW || !onW || !weightW) continue; - wireAutoWeight(comboW, weightW); - hidden.push(comboW, onW, weightW); - slots.push({ comboW, onW, weightW }); - node.addCustomWidget(new DolphinLoraRowWidget(`lora_${n}_row`, comboW, onW, weightW)); - } - const manualTriggersW = node.widgets.find((w) => w.name === "manual_triggers"); - if (manualTriggersW) { - hidden.push(manualTriggersW); - node.addCustomWidget(new DolphinTextFieldWidget("manual_triggers:", manualTriggersW, true)); - } - if (slots.length) { - node.addCustomWidget(new DolphinResetButtonWidget("⟲ Reset all slots", slots, manualTriggersW)); - } - return hidden; -} - -app.registerExtension({ - name: "Dolphin.LoraRows", - async beforeRegisterNodeDef(nodeType, nodeData) { - if (nodeData?.name === VISION_NODE_NAME) { - installLoraRows(nodeType, buildVisionDirectorRows); - } else if (nodeData?.name === ARCH_NODE_NAME) { - installLoraRows(nodeType, buildArchitectRows); - } - }, -}); diff --git a/web/dolphin_preset_sync.js b/web/dolphin_preset_sync.js deleted file mode 100644 index bedcb43b35550bf79985dcff21fec31da5f46b68..0000000000000000000000000000000000000000 --- a/web/dolphin_preset_sync.js +++ /dev/null @@ -1,102 +0,0 @@ -import { app } from "../../scripts/app.js"; - -// When the user picks a llm_preset/vlm_preset dropdown value, mirror the -// resolved slug into the paired llm_model/vlm_model text widget so what's -// displayed matches what dolphin_vision_director.py's resolve_slug() will -// actually call. Picking the "custom" sentinel leaves the text field alone -// (that's the whole point of custom mode). -// -// Keep these two tables in sync with LLM_PRESETS / VLM_PRESETS in -// dolphin_vision_director.py — they're duplicated here only because the -// backend doesn't expose the label->slug mapping over the object_info API, -// just the label list. - -// 2026-08-28: 파이썬 쪽이 한글 -> 영어 라벨로 옮겨간 뒤 이 테이블을 안 맞춰서 -// table[value]가 항상 undefined가 되고(=키가 하나도 안 맞음) 프리셋을 바꿔도 -// llm_model/vlm_model 텍스트 칸이 전혀 갱신 안 되는 버그가 있었다. 실제 추론에 -// 쓰이는 슬러그는 백엔드 resolve_slug()가 콤보 선택값을 직접 보고 매핑하므로 -// 이 버그와 무관하게 정상이었을 가능성이 높지만, 사용자가 확인할 유일한 화면 -// 요소가 이 텍스트 칸이라 "모델이 안 바뀐다"는 리포트로 이어졌다. 라벨을 여기 -// 하드코딩하는 구조 자체가 파이썬 쪽 변경 때마다 또 어긋날 수 있으니, 프리셋 -// 목록을 고칠 땐 항상 두 파일(dolphin_vision_director.py의 LLM_PRESETS/ -// VLM_PRESETS, 그리고 dolphin_minimax_director.py의 동일 복제본)과 이 파일을 -// 세 군데 다 맞출 것. -const LLM_PRESETS = { - "🔞 DeepSeek V3.2 (uncensored · stable JSON · recommended)": "deepseek/deepseek-v3.2", - "🔞 Magnum v4 72B (natural phrasing)": "anthracite-org/magnum-v4-72b", - "🔞 Cydonia 24B v4.1 (lightweight · fast)": "thedrummer/cydonia-24b-v4.1", - // 2026-09-03: :free 슬러그 404 - 유료로 교체. - "🔞 Dolphin Mistral Venice (uncensored)": "cognitivecomputations/dolphin-mistral-24b-venice-edition", - // 2026-09-03 추가 (모델 페이지로만 확인, chat completions 스모크테스트 미완). - "🔞 Hermes 4 405B (steerable · strict format)": "nousresearch/hermes-4-405b", - // 2026-09-03: grok-4 / grok-4-fast deprecated(404) - grok-4.3으로 교체. - "🔞 Grok 4.3 (edgy prose · $1.25/$2.5)": "x-ai/grok-4.3", - "🔞 Euryale 70B L3.3 (sensory prose · loose format)": "sao10k/l3.3-euryale-70b", -}; -// Valkyrie 49B V1은 추가했다가 뺐다 (2026-08-28, 실제 API 404 "No endpoints -// found") - dolphin_vision_director.py의 LLM_PRESETS 옆 주석 참고. - -const VLM_PRESETS = { - "👁 Qwen2.5-VL 72B (special cases · recommended)": "qwen/qwen2.5-vl-72b-instruct", - "👁 Qwen2.5-VL 32B (balanced)": "qwen/qwen2.5-vl-32b-instruct", - "👁 Qwen2.5-VL 32B (free)": "qwen/qwen2.5-vl-32b-instruct:free", - "👁 Mistral Small 3.1 24B (Pixtral)": "mistralai/mistral-small-3.1-24b-instruct", - "👁 Gemma 3 27B (lightweight)": "google/gemma-3-27b-it", -}; - -const NODE_NAMES = [ - "DolphinVisionDirector", - "DolphinPromptDirector", - "DolphinPromptDirectorSingle", - "DolphinMinimaxDirector", -]; -const SYNC_PAIRS = [ - { presetName: "llm_preset", modelName: "llm_model", table: LLM_PRESETS }, - { presetName: "vlm_preset", modelName: "vlm_model", table: VLM_PRESETS }, -]; - -function wireSync(node, presetName, modelName, table) { - const presetW = node.widgets?.find((w) => w.name === presetName); - const modelW = node.widgets?.find((w) => w.name === modelName); - if (!presetW || !modelW || presetW.__dolphinPresetSynced) return; - presetW.__dolphinPresetSynced = true; - - const origCallback = presetW.callback; - presetW.callback = function (value, ...rest) { - const ret = origCallback ? origCallback.apply(this, [value, ...rest]) : undefined; - try { - const slug = table[value]; - if (slug) { - modelW.value = slug; - if (typeof modelW.callback === "function") modelW.callback(slug); - node.setDirtyCanvas(true, true); - } - } catch (e) { - console.error("[Dolphin.PresetSync] sync failed:", e); - } - return ret; - }; -} - -app.registerExtension({ - name: "Dolphin.PresetSync", - async beforeRegisterNodeDef(nodeType, nodeData) { - if (!NODE_NAMES.includes(nodeData?.name)) return; - - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - const ret = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - // Never let this cosmetic hook break graph loading (see dolphin_lora_rows.js - // for why: an uncaught error here during graph.configure() can abort - // loading the rest of the workflow). - try { - for (const { presetName, modelName, table } of SYNC_PAIRS) { - wireSync(this, presetName, modelName, table); - } - } catch (e) { - console.error("[Dolphin.PresetSync] onNodeCreated hook failed:", e); - } - return ret; - }; - }, -});