diff --git a/.github/workflows/package_wheels.yml b/.github/workflows/package_wheels.yml index 4f7eba5..3909865 100644 --- a/.github/workflows/package_wheels.yml +++ b/.github/workflows/package_wheels.yml @@ -27,15 +27,15 @@ jobs: steps: - name: ♻️ Checking out the repository uses: actions/checkout@v3 - - name: "🐍 Setting up Python" + - name: '🐍 Setting up Python' uses: actions/setup-python@v4 with: - python-version: "3.10.9" + python-version: '3.10.9' - name: 📦 Building and Bundling wheels shell: bash run: | - python -m pip wheel --no-cache-dir --no-deps -r requirements-wheels.txt -w ./wheels 2>&1 | tee build.log + python -m pip wheel --no-cache-dir -r reqs.txt -w ./wheels 2>&1 | tee build.log # find source wheels packages=$(cat build.log | awk -F 'Building wheels for collected packages: ' '{print $2}') @@ -43,6 +43,13 @@ jobs: IFS=', ' read -r -a package_array <<< "$packages" + # Save reversed package_array to wheel_order.txt + reversed_array=() + for ((idx=${#package_array[@]}-1; idx>=0; idx--)); do + reversed_array+=("${package_array[idx]}") + done + printf '%s\n' "${reversed_array[@]}" > ./wheels/wheel_order.txt + printf "Autodetect this source package: \e[32m%s\e[0m\n" "${package_array[@]}" # Iterate through the wheel files and remove those that are not source built @@ -69,4 +76,4 @@ jobs: uses: actions/cache/save@v3 with: path: ${{ env.archive_name }}.zip - key: ${{ env.archive_name }} + key: ${{ env.archive_name }}-${{ hashFiles('reqs.txt') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36bdfb0..67d97ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: name: description: Release tag / name ? required: true - default: "latest" + default: 'latest' type: string environment: description: Environment to run tests against @@ -27,9 +27,9 @@ jobs: - name: ♻️ Checking out the repository uses: actions/checkout@v3 with: - submodules: "recursive" + submodules: 'recursive' path: ${{ env.repo_name }} - + # - name: 📝 Prepare file with paths to remove # run: | # find ${{ env.repo_name }} -type f -size +10M > .release_ignore @@ -56,7 +56,7 @@ jobs: else echo "No .release_ignore file found. Skipping removal of files and directories." fi - + - name: 📦 Building custom comfy nodes shell: bash run: | @@ -98,10 +98,18 @@ jobs: id: cache with: path: ${{ env.archive_name }}.zip - key: ${{ env.archive_name }} + key: ${{ env.archive_name }}-${{ hashFiles('reqs.txt') }} + - name: 📦 Unzip wheels + shell: bash + run: | + mkdir -p wheels + unzip -j ${{ env.archive_name }}.zip "**/*.whl" -d wheels + unzip -j ${{ env.archive_name }}.zip "**/*.txt" -d wheels + if: success() - name: ✅ Add wheels to release uses: softprops/action-gh-release@v1 with: tag_name: ${{ inputs.name }} files: | - ${{ env.archive_name }}.zip + wheels/*.whl + wheels/wheel_order.txt diff --git a/.github/workflows/test_embedded.yml b/.github/workflows/test_embedded.yml new file mode 100644 index 0000000..ac4daef --- /dev/null +++ b/.github/workflows/test_embedded.yml @@ -0,0 +1,71 @@ +name: 🧪 Test Comfy Portable + +on: workflow_dispatch +jobs: + install-comfy: + runs-on: windows-latest + env: + repo_name: ${{ github.event.repository.name }} + steps: + - name: ⚡️ Restore Cache if Available + id: cache-comfy + uses: actions/cache/restore@v3 + with: + path: ComfyUI_windows_portable + key: ${{ runner.os }}-comfy-env + + - name: 🚡 Download and Extract Comfy + id: download-extract-comfy + if: steps.cache-comfy.outputs.cache-hit != 'true' + shell: bash + run: | + mkdir comfy_temp + curl -L -o comfy_temp/comfyui.7z https://github.com/comfyanonymous/ComfyUI/releases/download/latest/ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z + + 7z x comfy_temp/comfyui.7z -o./comfy_temp + + + # mv comfy_temp/ComfyUI_windows_portable/python_embeded . + # mv comfy_temp/ComfyUI_windows_portable/ComfyUI . + # mv comfy_temp/ComfyUI_windows_portable/update . + ls + mv comfy_temp/ComfyUI_windows_portable . + + - name: 💾 Store cache + uses: actions/cache/save@v3 + if: steps.cache-comfy.outputs.cache-hit != 'true' + with: + path: ComfyUI_windows_portable + key: ${{ runner.os }}-comfy-env + - name: ⏬ Install other extensions + shell: bash + run: | + export COMFY_PYTHON="${GITHUB_WORKSPACE}/ComfyUI_windows_portable/python_embeded/python.exe" + cd "${GITHUB_WORKSPACE}/ComfyUI_windows_portable/ComfyUI/custom_nodes" + + git clone https://github.com/Fannovel16/comfy_controlnet_preprocessors + cd comfy_controlnet_preprocessors + $COMFY_PYTHON -m pip install -r requirements.txt + + - name: ♻️ Checking out comfy_mtb to custom_nodes + uses: actions/checkout@v3 + with: + submodules: 'recursive' + path: ComfyUI_windows_portable/ComfyUI/custom_nodes/${{ env.repo_name }} + + - name: 📦 Install mtb nodes + shell: bash + run: | + # run install + export COMFY_PYTHON="${GITHUB_WORKSPACE}/ComfyUI_windows_portable/python_embeded/python.exe" + cd "${GITHUB_WORKSPACE}/ComfyUI_windows_portable/ComfyUI/custom_nodes" + $COMFY_PYTHON ${{ env.repo_name }}/install.py -w + + - name: ⏬ Import mtb_nodes + shell: bash + run: | + export COMFY_PYTHON="${GITHUB_WORKSPACE}/ComfyUI_windows_portable/python_embeded/python.exe" + cd "${GITHUB_WORKSPACE}/ComfyUI_windows_portable/ComfyUI" + $COMFY_PYTHON -s main.py --quick-test-for-ci --cpu + + $COMFY_PYTHON -m pip freeze diff --git a/INSTALL-CN.md b/INSTALL-CN.md index 934e540..32ef707 100644 --- a/INSTALL-CN.md +++ b/INSTALL-CN.md @@ -49,7 +49,7 @@ python scripts/download_models.py 1. 确保您处于用于 ComfyUI 的 Python 环境中。 2. 运行以下命令安装所需的依赖项: ```bash - pip install -r comfy_mtb/requirements.txt + pip install -r comfy_mtb/reqs.txt ``` @@ -77,7 +77,7 @@ python scripts/download_models.py !python custom_nodes/comfy_mtb/scripts/download_models.py -y # install the dependencies -!pip install -r custom_nodes/comfy_mtb/requirements.txt -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html +!pip install -r custom_nodes/comfy_mtb/reqs.txt -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html ``` 如果运行后 colab 抱怨需要重新启动运行时,请重新启动,然后不要重新运行之前的单元格,只运行运行本地隧道的单元格。(可能需要先添加一个包含 `%cd ComfyUI` 的单元格) diff --git a/INSTALL-JP.md b/INSTALL-JP.md index f06dc6f..c7c3f52 100644 --- a/INSTALL-JP.md +++ b/INSTALL-JP.md @@ -52,7 +52,7 @@ python scripts/download_models.py 1. ComfyUIで使用しているPython環境であることを確認してください。 2. 以下のコマンドを実行して、必要な依存関係をインストールします: ```bash - pip install -r comfy_mtb/requirements.txt + pip install -r comfy_mtb/reqs.txt ``` @@ -78,7 +78,7 @@ ComfyUI with localtunnel (Recommended Way)**ヘッダーのすぐ後(コード !python custom_nodes/comfy_mtb/scripts/download_models.py -y # install the dependencies -!pip install -r custom_nodes/comfy_mtb/requirements.txt -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html +!pip install -r custom_nodes/comfy_mtb/reqs.txt -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html ``` これを実行した後、colabがランタイムを再起動する必要があると文句を言ったら、それを実行し、それ以前のセルは再実行せず、localtunnelを実行するセルだけを再実行してください。(最初に`%cd ComfyUI`のセルを追加する必要があるかもしれません...) diff --git a/INSTALL.md b/INSTALL.md index 2bf392d..e3f7d65 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -48,7 +48,7 @@ On first run the script [tries to symlink](https://github.com/melMass/comfy_mtb/ 1. Make sure you are in the Python environment you use for ComfyUI. 2. Install the required dependencies by running the following command: ```bash - pip install -r comfy_mtb/requirements.txt + pip install -r comfy_mtb/reqs.txt ``` @@ -76,7 +76,7 @@ Add a new code cell just after the **Run ComfyUI with localtunnel (Recommended W !python custom_nodes/comfy_mtb/scripts/download_models.py -y # install the dependencies -!pip install -r custom_nodes/comfy_mtb/requirements.txt -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html +!pip install -r custom_nodes/comfy_mtb/reqs.txt -f https://download.openmmlab.com/mmcv/dist/cu118/torch2.0/index.html ``` If after running this, colab complains about needing to restart runtime, do it, and then do not rerun earlier cells, just the one to run the localtunnel. (you might have to add a cell with `%cd ComfyUI` first...) diff --git a/README.md b/README.md index 2dc6e55..aa444b7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # MTB Nodes + +[](https://github.com/melMass/comfy_mtb/actions/workflows/test_embedded.yml) + **Translated Readme (using DeepTranslate, PRs are welcome)**: diff --git a/__init__.py b/__init__.py index 11a713d..4440b9f 100644 --- a/__init__.py +++ b/__init__.py @@ -5,11 +5,13 @@ # Project: comfy_mtb # Author: Mel Massadian # Copyright (c) 2023 Mel Massadian -# +# ### import os +# todo: don't override this if the user has that setup already os.environ["TF_FORCE_GPU_ALLOW_GROWTH"] = "true" +os.environ["TF_GPU_ALLOCATOR"] = "cuda_malloc_async" import traceback from .log import log, blue_text, cyan_text, get_summary, get_label @@ -24,7 +26,7 @@ NODE_CLASS_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {} NODE_CLASS_MAPPINGS_DEBUG = {} -__version__ = "0.1.1" +__version__ = "0.1.4" def extract_nodes_from_source(filename): @@ -43,19 +45,15 @@ def extract_nodes_from_source(filename): if isinstance(target, ast.Name) and target.id == "__nodes__": value = ast.get_source_segment(source_code, node.value) node_value = ast.parse(value).body[0].value - if isinstance(node_value, ast.List) or isinstance( - node_value, ast.Tuple - ): - for element in node_value.elts: - if isinstance(element, ast.Name): - print(element.id) - nodes.append(element.id) - + if isinstance(node_value, (ast.List, ast.Tuple)): + nodes.extend( + element.id + for element in node_value.elts + if isinstance(element, ast.Name) + ) break except SyntaxError: log.error("Failed to parse") - pass # File couldn't be parsed - return nodes @@ -112,8 +110,16 @@ if web_mtb.exists(): elif web_extensions_root.exists(): web_tgt = here / "web" + src = web_tgt.as_posix() + dst = web_mtb.as_posix() try: - os.symlink(web_tgt.as_posix(), web_mtb.as_posix()) + if os.name == "nt": + import _winapi + + _winapi.CreateJunction(src, dst) + else: + os.symlink(web_tgt.as_posix(), web_mtb.as_posix()) + except OSError: log.warn(f"Failed to create symlink to {web_mtb}, trying to copy it") try: @@ -121,15 +127,17 @@ elif web_extensions_root.exists(): shutil.copytree(web_tgt, web_mtb) log.info(f"Successfully copied {web_tgt} to {web_mtb}") - except Exception: + except Exception as e: log.warn( f"Failed to symlink and copy {web_tgt} to {web_mtb}. Please copy the folder manually." ) + log.warn(e) - except Exception: # OSError + except Exception as e: log.warn( f"Failed to create symlink to {web_mtb}. Please copy the folder manually." ) + log.warn(e) else: log.warn( f"Comfy root probably not found automatically, please copy the folder {web_mtb} manually in the web/extensions folder of ComfyUI" @@ -174,123 +182,132 @@ from importlib import reload import logging from .endpoint import endlog +if hasattr(PromptServer, "instance"): + restore_deps = ["basicsr"] + swap_deps = ["insightface", "onnxruntime"] -@PromptServer.instance.routes.get("/mtb/status") -async def get_full_library(request): - from . import endpoint + node_dependency_mapping = { + "FaceSwap": swap_deps, + "LoadFaceSwapModel": swap_deps, + "LoadFaceAnalysisModel": restore_deps, + } - reload(endpoint) + @PromptServer.instance.routes.get("/mtb/status") + async def get_full_library(request): + from . import endpoint - endlog.debug("Getting node registration status") - # Check if the request prefers HTML content - if "text/html" in request.headers.get("Accept", ""): - # # Return an HTML page - html_response = endpoint.render_table( - NODE_CLASS_MAPPINGS_DEBUG, title="Registered" - ) - html_response += endpoint.render_table( - {k: "-" for k in failed}, title="Failed to load" + reload(endpoint) + + endlog.debug("Getting node registration status") + # Check if the request prefers HTML content + if "text/html" in request.headers.get("Accept", ""): + # # Return an HTML page + html_response = endpoint.render_table( + NODE_CLASS_MAPPINGS_DEBUG, title="Registered" + ) + html_response += endpoint.render_table( + { + k: {"dependencies": node_dependency_mapping.get(k)} + if node_dependency_mapping.get(k) + else "-" + for k in failed + }, + title="Failed to load", + ) + + return web.Response( + text=endpoint.render_base_template("MTB", html_response), + content_type="text/html", + ) + + return web.json_response( + { + "registered": NODE_CLASS_MAPPINGS_DEBUG, + "failed": failed, + } ) - return web.Response( - text=endpoint.render_base_template("MTB", html_response), - content_type="text/html", - ) + @PromptServer.instance.routes.post("/mtb/debug") + async def set_debug(request): + json_data = await request.json() + enabled = json_data.get("enabled") + if enabled: + os.environ["MTB_DEBUG"] = "true" + log.setLevel(logging.DEBUG) + log.debug("Debug mode set from API (/mtb/debug POST route)") - return web.json_response( - { - "registered": NODE_CLASS_MAPPINGS_DEBUG, - "failed": failed, - } - ) - - -@PromptServer.instance.routes.post("/mtb/debug") -async def set_debug(request): - json_data = await request.json() - enabled = json_data.get("enabled") - if enabled: - os.environ["MTB_DEBUG"] = "true" - log.setLevel(logging.DEBUG) - log.debug("Debug mode set from API (/mtb/debug POST route)") - - else: - if "MTB_DEBUG" in os.environ: + elif "MTB_DEBUG" in os.environ: # del os.environ["MTB_DEBUG"] os.environ.pop("MTB_DEBUG") log.setLevel(logging.INFO) - return web.json_response({"message": f"Debug mode {'set' if enabled else 'unset'}"}) - - -@PromptServer.instance.routes.get("/mtb") -async def get_home(request): - from . import endpoint - - reload(endpoint) - # Check if the request prefers HTML content - if "text/html" in request.headers.get("Accept", ""): - # # Return an HTML page - html_response = f""" -
- """ - return web.Response( - text=endpoint.render_base_template("MTB", html_response), - content_type="text/html", + return web.json_response( + {"message": f"Debug mode {'set' if enabled else 'unset'}"} ) - # Return JSON for other requests - return web.json_response({"message": "Welcome to MTB!"}) + @PromptServer.instance.routes.get("/mtb") + async def get_home(request): + from . import endpoint + reload(endpoint) + # Check if the request prefers HTML content + if "text/html" in request.headers.get("Accept", ""): + # # Return an HTML page + html_response = """ + + """ + return web.Response( + text=endpoint.render_base_template("MTB", html_response), + content_type="text/html", + ) -@PromptServer.instance.routes.get("/mtb/debug") -async def get_debug(request): - from . import endpoint + # Return JSON for other requests + return web.json_response({"message": "Welcome to MTB!"}) - reload(endpoint) - enabled = False - if "MTB_DEBUG" in os.environ: - enabled = True - # Check if the request prefers HTML content - if "text/html" in request.headers.get("Accept", ""): - # # Return an HTML page - html_response = f""" -