Download ComfyUI/custom_nodes/koolook/scripts/sync_to_dev_audio.py from vidfom/aimusic: direct link, hf CLI and curl.
- Browser
- Download file 8.39 kB
-
https://huggingface.co/vidfom/aimusic/resolve/main/ComfyUI/custom_nodes/koolook/scripts/sync_to_dev_audio.py
- Command line
-
hf download hf://vidfom/aimusic/ComfyUI/custom_nodes/koolook/scripts/sync_to_dev_audio.py
-
curl -L -o sync_to_dev_audio.py https://huggingface.co/vidfom/aimusic/resolve/main/ComfyUI/custom_nodes/koolook/scripts/sync_to_dev_audio.py
8.39 kB
| #!/usr/bin/env python3 | |
| """ | |
| Scoped variant of ``dev-sync`` for the LTX 2.3 audio-lipsync automation. | |
| Copies only what that automation's iteration loop touches: | |
| - forks/whatdreamscost_koolook/ the Koolook fork of LTXDirector + | |
| its modified prompt_relay + the | |
| verbatim-vendored patches.py | |
| - __init__.py root loader (registers the fork's | |
| node mappings; needs to be re-synced | |
| when the fork's NODE_CLASS_MAPPINGS | |
| set changes) | |
| - web/whatdreamscost_koolook/ Director timeline-editor extension | |
| - web/koolook_draft_guard.js global Comfy draft-quota guard | |
| (formerly embedded in the Director | |
| extension; shipped so a scoped sync | |
| never strands a guard-less install) | |
| It also ships ``koolook_install_guard.py`` + ``koolook_versioning.py`` — the | |
| loader gates ``__init__.py`` imports at load — so a scoped sync never leaves | |
| the loader pointing at a module the target lacks (#198 / #183). | |
| Everything else in the live install - ``forks/radiance_koolook/``, the | |
| root ``k_*.py`` nodes, unrelated ``web/`` assets, ``video_formats/`` - | |
| is left untouched. | |
| USER-INITIATED ONLY. Same rule as plain ``dev-sync`` (see project | |
| ``CLAUDE.md`` -> ``dev-sync`` section). Never run automatically: | |
| * after a commit | |
| * after a PR merge or ``/ship-pr`` | |
| * at session end / wrap-up | |
| * on hook completion or "task complete" cleanup | |
| * from any agent skill that doesn't explicitly require it | |
| Run only on the explicit user trigger phrase ``dev-sync-audio`` (or | |
| ``copy audio fork``, ``sync audio``, etc.). The maintainer typically has | |
| multiple parallel sessions across worktrees; an unsolicited sync from one | |
| silently destroys what another is reviewing. | |
| The target path comes from ``KOLOOK_COMFYUI_DEV_PATH`` in ``.env`` - | |
| identical to plain ``dev-sync``. No new env var. | |
| Usage: | |
| python scripts/sync_to_dev_audio.py | |
| python scripts/sync_to_dev_audio.py --dry-run | |
| python scripts/sync_to_dev_audio.py --scope "vstr=10 trial" | |
| Exit codes mirror ``sync_to_dev.py`` exactly: | |
| 0 success | |
| 2 KOLOOK_COMFYUI_DEV_PATH unset or target missing without --init | |
| 3 --init refused | |
| After copying Python files, restart ComfyUI manually so custom nodes are | |
| re-imported. This scoped sync only copies files. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import shutil | |
| import sys | |
| from pathlib import Path | |
| # Reuse the dev-sync infrastructure for the .env loader, target validation, | |
| # and chat-report shape. This scoped wrapper differs only in which paths get | |
| # copied, and it never tries to manage the running ComfyUI process. | |
| _SCRIPT_DIR = Path(__file__).resolve().parent | |
| if str(_SCRIPT_DIR) not in sys.path: | |
| sys.path.insert(0, str(_SCRIPT_DIR)) | |
| import sync_to_dev as _dev # noqa: E402 | |
| AUDIO_PATHS: tuple[str, ...] = ( | |
| "__init__.py", | |
| # __init__.py imports these at load, before any node group, and a missing | |
| # gate is catastrophic (not per-group-guarded): the install guard's | |
| # absolute-import fallback raises uncaught, and the koolook_versioning | |
| # context probe mislabels its ImportError as a non-Comfy context and | |
| # registers nothing. Ship them with __init__.py so a scoped sync never | |
| # leaves the loader pointing at a module the target lacks (#198 / #183). | |
| "koolook_install_guard.py", | |
| "koolook_versioning.py", | |
| "forks/whatdreamscost_koolook", | |
| "web/whatdreamscost_koolook", | |
| # The Comfy draft-quota guard used to be embedded in the Director web | |
| # extension above; it is global now. Ship it with every scoped audio | |
| # sync so replacing web/whatdreamscost_koolook/ can never leave a dev | |
| # install without the guard. | |
| "web/koolook_draft_guard.js", | |
| ) | |
| STALE_AUDIO_PATHS: tuple[str, ...] = ( | |
| "web/whatdreamscost_koolook_v1_3_2", | |
| ) | |
| def target_is_repo_root(target: Path) -> bool: | |
| """Return True when the configured dev target is this source repo.""" | |
| try: | |
| return target.resolve() == _dev.REPO_ROOT.resolve() | |
| except OSError: | |
| return False | |
| def remove_stale_paths(target: Path, *, dry_run: bool, verbose: bool) -> int: | |
| """Remove old audio-sync paths that were renamed. | |
| The v1.3.9 upgrade moved the Director web extension from a versioned | |
| folder to a stable one. Existing dev installs can still have the old | |
| folder on disk, causing ComfyUI to load two timeline extensions for | |
| legacy workflows. Keep this scoped to explicit known paths. | |
| """ | |
| removed = 0 | |
| for rel in STALE_AUDIO_PATHS: | |
| stale = target / rel | |
| if not stale.exists() and not stale.is_symlink(): | |
| continue | |
| if dry_run: | |
| if verbose: | |
| print(f"would remove stale: {rel}") | |
| removed += 1 | |
| continue | |
| if stale.is_symlink() or stale.is_file(): | |
| stale.unlink() | |
| elif stale.is_dir(): | |
| shutil.rmtree(stale) | |
| else: | |
| stale.unlink() | |
| if verbose: | |
| print(f"removed stale: {rel}") | |
| removed += 1 | |
| return removed | |
| def _find_dotenv() -> Path | None: | |
| direct = _dev.REPO_ROOT / ".env" | |
| if direct.exists(): | |
| return direct | |
| git_marker = _dev.REPO_ROOT / ".git" | |
| if not git_marker.is_file(): | |
| return None | |
| try: | |
| content = git_marker.read_text(encoding="utf-8").strip() | |
| except OSError: | |
| return None | |
| if not content.startswith("gitdir:"): | |
| return None | |
| gitdir = Path(content.split(":", 1)[1].strip()) | |
| if "worktrees" not in gitdir.parts: | |
| return None | |
| idx = gitdir.parts.index("worktrees") | |
| main_repo_root = Path(*gitdir.parts[:idx]).parent | |
| candidate = main_repo_root / ".env" | |
| return candidate if candidate.exists() else None | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "--dry-run", | |
| action="store_true", | |
| help="Print what would be copied without touching the target.", | |
| ) | |
| parser.add_argument( | |
| "--init", | |
| action="store_true", | |
| help=( | |
| "Create the target directory if it doesn't exist. The parent " | |
| "(typically a ComfyUI custom_nodes/ folder) must already be " | |
| "in place. Use on first-run only." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "--verbose", | |
| "-v", | |
| action="store_true", | |
| help="Print each file/dir as it's copied.", | |
| ) | |
| parser.add_argument( | |
| "--scope", | |
| type=str, | |
| default="audio-lipsync fork edit", | |
| help=( | |
| "Short (<=10 word) description for the chat report's second line " | |
| "and the in-browser footer in `web/_dev_build.json`. Defaults to " | |
| "'audio-lipsync fork edit' so the build identifier always names " | |
| "this module even when the maintainer doesn't pass a scope." | |
| ), | |
| ) | |
| args = parser.parse_args() | |
| env_path = _find_dotenv() | |
| if env_path is not None: | |
| _dev.load_dotenv(env_path) | |
| target_str = os.environ.get("KOLOOK_COMFYUI_DEV_PATH") | |
| if not target_str: | |
| print( | |
| "KOLOOK_COMFYUI_DEV_PATH not set. Add it to .env (see .env.example).", | |
| file=sys.stderr, | |
| ) | |
| return 2 | |
| target = Path(target_str).expanduser() | |
| if target_is_repo_root(target): | |
| print( | |
| "KOLOOK_COMFYUI_DEV_PATH points at this source repo; refusing dev-sync.", | |
| file=sys.stderr, | |
| ) | |
| return 2 | |
| err = _dev.ensure_target(target, init=args.init) | |
| if err is not None: | |
| return err | |
| n = _dev.sync( | |
| target, | |
| dry_run=args.dry_run, | |
| verbose=args.verbose, | |
| paths=AUDIO_PATHS, | |
| ) | |
| removed = remove_stale_paths(target, dry_run=args.dry_run, verbose=args.verbose) | |
| verb = "would sync" if args.dry_run else "synced" | |
| print(_dev.build_line()) | |
| stale_note = f"; removed {removed} stale" if removed else "" | |
| print(f"{verb} {n} entries{stale_note} -> {target} (dev-sync-audio)") | |
| if not args.dry_run: | |
| _dev.write_build_info(target, args.scope) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |