Compare commits

...
18 Commits
Author SHA1 Message Date
Mel Massadian 247fbfbc21 fix: 🔥 manage pip from install only, remove requirements.txt (#38) 2023-07-30 01:20:05 +02:00
melMass 9b24eddd9c chore: ✨ use wheel order if present 2023-07-29 00:55:10 +02:00
melMass 505314294f ci: ✨ store order of install for wheels 2023-07-29 00:25:24 +02:00
melMass f5cd56ce86 fix: 🎨 use image ratio for imagefeed 2023-07-28 22:14:22 +02:00
Mel Massadian cbcacbe3c9 docs: 📝 update imagefeed preview 2023-07-28 22:12:43 +02:00
Mel Massadian 7c020bab28 docs: 📝 fix typo and add more details 2023-07-28 21:33:39 +02:00
melMass 9e751a242f chore: 🎉 bump version 2023-07-28 20:54:44 +02:00
melMass 0e311cf2c6 fix: ✨ various small things
- removed border on imagefeed images.
- don't load mtb.imageFeed if the user has pythongoss's version already.
- fix the promptserver issue when importing mtb from a jupyter notebook
- fix: if the user doesn't have the facemodels downloaded it would crash
- added an internal counter to batchfromhistory to invalidate it at each
  frame, which might not be a good idea.
2023-07-28 20:47:27 +02:00
Mel Massadian 889f08c08b fix: 📝 last release (#36) 2023-07-28 20:26:37 +02:00
Mel Massadian 5d661b2509 fix: 📝 narrow requirements
The protobuf issue is only valid on windows as we must use the old
TF lib to get usable speeds for FILM interpolation. WSL, windows and mac don't need that trick.

Fixes #28
2023-07-27 01:30:13 +02:00
Mel Massadian be162a2047 docs: 📝 add readme for web extensions features 2023-07-25 15:20:57 +02:00
Doug White 4ea26ed8de Fix unclickable image gallery buttons in Firefox (#34) 2023-07-25 12:03:32 +02:00
melMass c237737420 chore: 👷 remove stale example 2023-07-25 02:30:37 +02:00
Mel Massadian 232cf8966c docs: 📝 link to the proper lang instructions (#33) 2023-07-25 02:05:57 +02:00
melMass 96a0618c59 docs: 📝 update readmes 2023-07-25 00:43:21 +02:00
melMass d143e83dba fix: ✨ Separate FaceAnalysis model loading
This closes #19

It is indeed much faster.
2023-07-25 00:29:39 +02:00
melMass 3dfe98c795 fix: ⚡️ update examples to match wiki 2023-07-24 23:43:02 +02:00
melMass c0cc5572d8 ci: 🐛 fix size
it was ignoring the last line, I also ignore the git folder itself
2023-07-24 22:24:38 +02:00
24 changed files with 1647 additions and 2352 deletions
+10 -3
View File
@@ -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
+13 -7
View File
@@ -39,14 +39,20 @@ jobs:
- name: 🗑️ Remove files and directories listed in .release_ignore
shell: bash
run: |
if [ -f "${{ env.repo_name }}/.release_ignore" ]; then
while IFS= read -r entry; do
if [ -f "${{ env.repo_name }}/$entry" ]; then
rm "${{ env.repo_name }}/$entry"
elif [ -d "${{ env.repo_name }}/$entry" ]; then
rm -r "${{ env.repo_name }}/$entry"
release_ignore="${{ env.repo_name }}/.release_ignore"
if [ -f "$release_ignore" ]; then
while IFS= read -r entry || [ -n "$entry" ]; do
target="${{ env.repo_name }}/$entry"
if [ -e "$target" ]; then
if [ -f "$target" ]; then
rm "$target"
elif [ -d "$target" ]; then
rm -r "$target"
fi
else
echo "Warning: $entry does not exist in the repository. Skipping removal."
fi
done < "${{ env.repo_name }}/.release_ignore"
done < "$release_ignore"
else
echo "No .release_ignore file found. Skipping removal of files and directories."
fi
+2 -1
View File
@@ -1,3 +1,4 @@
extern/frame_interpolation/moment.gif
extern/frame_interpolation/photos
extern/GFPGAN/inputs
extern/GFPGAN/inputs
.git
+2 -2
View File
@@ -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
```
</details>
@@ -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` 的单元格)
+2 -2
View File
@@ -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
```
</details>
@@ -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`のセルを追加する必要があるかもしれません...)
+2 -2
View File
@@ -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
```
</details>
@@ -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...)
+14 -17
View File
@@ -1,13 +1,13 @@
## MTB Nodes
# MTB Nodes
<a href="https://www.buymeacoffee.com/melmass" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 32px !important;width: 140px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
[** 安装指南**](./INSTALL-CN.md) | [** 示例**](https://github.com/melMass/comfy_mtb/wiki/Examples)
欢迎使用 MTB Nodes 项目!这个代码库是开放的,您可以自由地探索和利用。它的主要目的是构建用于 [MLOPs](https://github.com/Bismuth-Consultancy-BV/MLOPs) 中的概念验证(POCs)。该项目中的许多节点都是受到现有社区贡献或内置功能的启发而创建的。
在继续之前,请注意与此项目中使用的某些库相关的许可证。例如,`deepbump` 库采用 [GPLv3](https://github.com/HugoTini/DeepBump/blob/master/LICENSE) 许可证。
- [MTB Nodes](#mtb-nodes)
- [安装](#安装)
- [节点列表](#节点列表)
- [bbox](#bbox)
- [colors](#colors)
@@ -20,27 +20,24 @@
- [Comfy 资源](#comfy-资源)
## 安装
- 移至 [INSTALL-CN.md](./INSTALL-CN.md)
## 节点列表
# 节点列表
### bbox
## bbox
- `Bounding Box`: BBox 构造函数(自定义类型)
- `BBox From Mask`: 从遮罩中提取边界框
- `Crop`: 根据边界框裁剪图像
- `Uncrop`: 根据边界框还原图像
### colors
## colors
- `Colored Image`: 给定尺寸的纯色图像
- `RGB to HSV`: -
- `HSV to RGB`: -
- `Color Correct`: 基本颜色校正工具
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/7c20ac83-31ff-40ea-a1a0-06c2acefb2ef" width=345/>
### 人脸检测/交换
## 人脸检测/交换
- `Face Swap`: 使用 deepinsight/insightface 模型进行人脸交换(该节点在早期版本中称为 `Roop`,功能相同,`Roop` 只是使用这些模型的应用程序)
> **注意**
> 人脸索引允许您选择要替换的人脸,如下所示:
@@ -48,13 +45,13 @@
- `Load Face Swap Model`: 加载 insightface 模型用于人脸交换
- `Restore Face`: 使用 [GFPGan](https://github.com/TencentARC/GFPGAN) 还原人脸,与 `Face Swap` 配合使用效果很好,并支持 `bg_upscaler` 的 Comfy 原生放大器
### 图像插值(动画)
## 图像插值(动画)
- `Load Film Model`: 加载 [FILM](https://github.com/google-research/frame-interpolation) 模型
- `Film Interpolation`: 使用 [FILM](https://github.com/google-research/frame-interpolation) 处理输入帧
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/3afd1647-6634-4b92-a34b-51432e6a9834" width=400/>
- `Export to Prores (experimental)`: 将输入帧导出为 ProRes 4444 mov 文件。这使用 ffmpeg stdin 发送原始的 NumPy 数组,与 `Film Interpolation` 一起使用,目前很简单,但可以进一步扩展。
### 图像操作
## 图像操作
- `Blur`: 使用高斯滤波器对图像进行模糊处理。
- `Deglaze Image`: 从 [FN16](https://github.com/Fannovel16/FN16-ComfyUI-nodes/blob/main/DeglazeImage.py) 中提取
- `Denoise`: 对输入图像进行降噪处理
@@ -66,11 +63,11 @@
- `Mask To Image`: 将遮罩(Alpha)转换为带有颜色和背景的 RGB 图像
- `Save Image Grid`: 将输入批次中的所有图像保存为图像网格。
### 潜在变量工具
## 潜在变量工具
- `Latent Lerp`: 两个潜在变量之间的线性插值(混合)
### 其他工具
## 其他工具
- `Concat Images`: 接受两个图像流,并将它们合并为其他 Comfy 管道支持的图像批次。
- `Image Resize Factor`: **已弃用**,因为我后来发现了内
@@ -84,11 +81,11 @@
- `Int to Number`: 用于 WASSuite 数字节点的补充
- `Smart Step`: 使用百分比来控制 `KAdvancedSampler` 的步骤(开始/停止)
### 纹理
## 纹理
- `DeepBump`: 从单张图片生成法线图和高度图
## Comfy 资源
# Comfy 资源
**指南**:
- [官方示例(英文)](https://comfyanonymous.github.io/ComfyUI_examples/)
@@ -99,4 +96,4 @@
**扩展和自定义节点**:
- @WASasquatch 的[Comfy 列表插件(英文)](https://github.com/WASasquatch/comfyui-plugins)
- [CivitAI 上的 ComfyUI 标签(英文)](https://civitai.com/tag/comfyui)
- [CivitAI 上的 ComfyUI 标签(英文)](https://civitai.com/tag/comfyui)
+14 -19
View File
@@ -1,13 +1,13 @@
## MTB Nodes
# MTB Nodes
<a href="https://www.buymeacoffee.com/melmass" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 32px !important;width: 140px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
[**インストールガイド**](./INSTALL-JP.md) | [**サンプル**](https://github.com/melMass/comfy_mtb/wiki/Examples)
MTB Nodesプロジェクトへようこそ!このコードベースは、自由に探索し、利用することができます。主な目的は、[MLOPs](https://github.com/Bismuth-Consultancy-BV/MLOPs)の実装のための概念実証(POC)を構築することです。このプロジェクトの多くのノードは、既存のコミュニティの貢献や組み込みの機能に触発されています。
続行する前に、このプロジェクトで使用されている特定のライブラリに関連するライセンスに注意してください。たとえば、「deepbump」ライブラリは、[GPLv3](https://github.com/HugoTini/DeepBump/blob/master/LICENSE)の下でライセンスされています。
- [MTB Nodes](#mtb-nodes)
- [インストール](#インストール)
- [ノードリスト](#ノードリスト)
- [bbox](#bbox)
- [colors](#colors)
@@ -20,27 +20,22 @@ MTB Nodesプロジェクトへようこそ!このコードベースは、自
- [Comfyリソース](#comfyリソース)
## インストール
# ノードリスト
- [INSTALL-JP.md](./INSTALL-JP.md)に移動しました。
## ノードリスト
### bbox
## bbox
- `Bounding Box`: BBoxコンストラクタ(カスタムタイプ)
- `BBox From Mask`: マスクからバウンディングボックスを抽出
- `Crop`: BBoxから画像を切り抜く
- `Uncrop`: BBoxから画像を元に戻す
### colors
## colors
- `Colored Image`: 指定されたサイズの一定の色の画像
- `RGB to HSV`: -
- `HSV to RGB`: -
- `Color Correct`: 基本的なカラーコレクションツール
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/7c20ac83-31ff-40ea-a1a0-06c2acefb2ef" width=345/>
### 顔検出 / スワッピング
## 顔検出 / スワッピング
- `Face Swap`: deepinsight/insightfaceモデルを使用した顔の入れ替え(このノードは初期バージョンでは「Roop」と呼ばれていましたが、同じ機能を提供します。Roopは単にこれらのモデルを使用するアプリです)
> **注意**
> 顔のインデックスを使用して置き換える顔を選択できます。以下を参照してください:
@@ -48,13 +43,13 @@ MTB Nodesプロジェクトへようこそ!このコードベースは、自
- `Load Face Swap Model`: 顔の交換のためのinsightfaceモデルを読み込む
- `Restore Face`: [GFPGan](https://github.com/TencentARC/GFPGAN)を使用して顔を復元し、`Face Swap`と組み合わせて使用すると非常に効果的であり、`bg_upscaler`のComfyネイティブアップスケーラーもサポートしています。
### 画像補間(アニメーション)
## 画像補間(アニメーション)
- `Load Film Model`: [FILM](https://github.com/google-research/frame-interpolation)モデルを読み込む
- `Film Interpolation`: [FILM](https://github.com/google-research/frame-interpolation)を使用して入力フレームを処理する
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/3afd1647-6634-4b92-a34b-51432e6a9834" width=400/>
- `Export to Prores (experimental)`: 入力フレームをProRes 4444 movファイルにエクスポートします。これは現在は単純なものですが、`Film Interpolation`と組み合わせて使用するためのffmpegのstdinを使用して生のNumPy配列を送信するもので、拡張することもできます。
### 画像操作
## 画像操作
- `Blur`: ガウスフィルタを使用して画像をぼかす
- `Deglaze Image`: [FN16](https://github.com/Fannovel16/FN16-ComfyUI-nodes/blob/main/DeglazeImage.py)から取得
- `Denoise`: 入力画像のノイズを除去する
@@ -68,10 +63,10 @@ MTB Nodesプロジェクトへようこそ!このコードベースは、自
- `Mask To Image`: マスク(アルファ)をカラーと背景を持つRGBイメージに変換します。
- `Save Image Grid`: 入力バッチのすべての画像を画像グリッドとして保存します。
### 潜在的なユーティリティ
## 潜在的なユーティリティ
- `Latent Lerp`: 2つの潜在的なベクトルの間の線形補間(ブレンド)
### その他のユーティリティ
## その他のユーティリティ
- `Concat Images`: 2つの画像ストリームを取り、他のComfyパイプラインでサポートされている画像のバッチとしてマージします。
- `Image Resize Factor`: **非推奨**。組み込みの画像リサイズ機能を発見したため、削除される予定です。
- `Text To Image`: フォントを使用してテキストを画像に変換するためのユーティリティ
@@ -83,11 +78,11 @@ MTB Nodesプロジェクトへようこそ!このコードベースは、自
- `Int to Number`: WASSuiteの数値ノードの補完
- `Smart Step`: `KAdvancedSampler`のステップ(開始/停止)を制御するための非常に基本的なツールで、パーセンテージを使用します。
### テクスチャ
## テクスチャ
- `DeepBump`: 1枚の画像から法線マップと高さマップを生成します。
## Comfyリソース
# Comfyリソース
**ガイド**:
- [公式の例(英語)](https://comfyanonymous.github.io/ComfyUI_examples/)
@@ -98,4 +93,4 @@ MTB Nodesプロジェクトへようこそ!このコードベースは、自
**拡張機能とカスタムノード**:
- @WASasquatchによる[Comfyリスト用のプラグイン(英語)](https://github.com/WASasquatch/comfyui-plugins)
- [CivitAIのComfyUIタグ(英語)](https://civitai.com/tag/comfyui)
- [CivitAIのComfyUIタグ(英語)](https://civitai.com/tag/comfyui)
+14 -18
View File
@@ -1,4 +1,5 @@
## MTB Nodes
# MTB Nodes
<!-- omit in toc -->
**Translated Readme (using DeepTranslate, PRs are welcome)**:
![image](https://github.com/melMass/comfy_mtb/assets/7041726/f8429c14-3521-4e28-82a3-863d781976c0)
@@ -8,12 +9,12 @@
<a href="https://www.buymeacoffee.com/melmass" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 32px !important;width: 140px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
[**Install Guide**](./INSTALL.md) | [**Examples**](https://github.com/melMass/comfy_mtb/wiki/Examples)
Welcome to the MTB Nodes project! This codebase is open for you to explore and utilize as you wish. Its primary purpose is to build proof-of-concepts (POCs) for implementation in [MLOPs](https://github.com/Bismuth-Consultancy-BV/MLOPs). Many nodes in this project are inspired by existing community contributions or built-in functionalities.
Before proceeding, please be aware of the licenses associated with certain libraries used in this project. For example, the `deepbump` library is licensed under [GPLv3](https://github.com/HugoTini/DeepBump/blob/master/LICENSE).
- [MTB Nodes](#mtb-nodes)
- [Installation](#installation)
- [Node List](#node-list)
- [bbox](#bbox)
- [colors](#colors)
@@ -26,27 +27,22 @@ Before proceeding, please be aware of the licenses associated with certain libra
- [Comfy Resources](#comfy-resources)
## Installation
# Node List
- Moved to [INSTALL.md](./INSTALL.md)
## Node List
### bbox
## bbox
- `Bounding Box`: BBox constructor (custom type),
- `BBox From Mask`: From a mask extract the bounding box
- `Crop`: Crop image from BBox
- `Uncrop`: Uncrop image from BBox
### colors
## colors
- `Colored Image`: Constant color image of given size
- `RGB to HSV`: -,
- `HSV to RGB`: -,
- `Color Correct`: Basic color correction tools
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/7c20ac83-31ff-40ea-a1a0-06c2acefb2ef" width=345/>
### face detection / swapping
## face detection / swapping
- `Face Swap`: Face swap using deepinsight/insightface models (this node used to be called `Roop` in early versions, it does the same, roop is *just* an app that uses those model)
> **Note**
> The face index allow you to choose which face to replace as you can see here:
@@ -54,13 +50,13 @@ Before proceeding, please be aware of the licenses associated with certain libra
- `Load Face Swap Model`: Load an insightface model for face swapping
- `Restore Face`: Using [GFPGan](https://github.com/TencentARC/GFPGAN) to restore faces, works great in conjunction with `Face Swap` and supports Comfy native upscalers for the `bg_upscaler`
### image interpolation (animation)
## image interpolation (animation)
- `Load Film Model`: Loads a [FILM](https://github.com/google-research/frame-interpolation) model
- `Film Interpolation`: Process input frames using [FILM](https://github.com/google-research/frame-interpolation)
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/3afd1647-6634-4b92-a34b-51432e6a9834" width=400/>
- `Export to Prores (experimental)`: Exports the input frames to a ProRes 4444 mov file. This is using ffmpeg stdin to send raw numpy arrays, used with `Film Interpolation` and very simple for now but could be expanded upon.
### image ops
## image ops
- `Blur`: Blur an image using a Gaussian filter.
- `Deglaze Image`: taken from [FN16](https://github.com/Fannovel16/FN16-ComfyUI-nodes/blob/main/DeglazeImage.py),
- `Denoise`: Denoise the input image,
@@ -72,11 +68,11 @@ Before proceeding, please be aware of the licenses associated with certain libra
- `Mask To Image`: Converts a mask (alpha) to an RGB image with a color and background
- `Save Image Grid`: Save all the images in the input batch as a grid of images.
### latent utils
## latent utils
- `Latent Lerp`: Linear interpolation (blend) between two latent
### misc utils
## misc utils
- `Concat Images`: Takes two image stream and merge them as a batch of images supported by other Comfy pipelines.
- `Image Resize Factor`: **Deprecated**, I since discovered the builtin image resize.
- `Text To Image`: Utils to convert text to image using a font
@@ -88,11 +84,11 @@ Before proceeding, please be aware of the licenses associated with certain libra
- `Int to Number`: Supplement for WASSuite number nodes
- `Smart Step`: A very basic tool to control the steps (start/stop) of the `KAdvancedSampler` using percentage
### textures
## textures
- `DeepBump`: Normal & height maps generation from single pictures
## Comfy Resources
# Comfy Resources
**Guides**:
- [Official Examples (eng)](https://comfyanonymous.github.io/ComfyUI_examples/)
+111 -103
View File
@@ -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.2"
def extract_nodes_from_source(filename):
@@ -112,8 +114,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:
@@ -174,123 +184,121 @@ from importlib import reload
import logging
from .endpoint import endlog
if hasattr(PromptServer, "instance"):
@PromptServer.instance.routes.get("/mtb/status")
async def get_full_library(request):
from . import endpoint
@PromptServer.instance.routes.get("/mtb/status")
async def get_full_library(request):
from . import endpoint
reload(endpoint)
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: "-" for k in failed}, title="Failed to load"
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"
)
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)")
else:
if "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'}"}
)
return web.json_response(
{
"registered": NODE_CLASS_MAPPINGS_DEBUG,
"failed": failed,
}
)
@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"""
<div class="flex-container menu">
<a href="/mtb/debug">debug</a>
<a href="/mtb/status">status</a>
</div>
"""
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 JSON for other requests
return web.json_response({"message": "Welcome to MTB!"})
else:
@PromptServer.instance.routes.get("/mtb/debug")
async def get_debug(request):
from . import endpoint
reload(endpoint)
enabled = False
if "MTB_DEBUG" in os.environ:
# del os.environ["MTB_DEBUG"]
os.environ.pop("MTB_DEBUG")
log.setLevel(logging.INFO)
enabled = True
# Check if the request prefers HTML content
if "text/html" in request.headers.get("Accept", ""):
# # Return an HTML page
html_response = f"""
<h1>MTB Debug Status: {'Enabled' if enabled else 'Disabled'}</h1>
"""
return web.Response(
text=endpoint.render_base_template("Debug", 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({"enabled": enabled})
@PromptServer.instance.routes.get("/mtb/actions")
async def no_route(request):
from . import endpoint
@PromptServer.instance.routes.get("/mtb")
async def get_home(request):
from . import endpoint
if "text/html" in request.headers.get("Accept", ""):
html_response = f"""
<h1>Actions has no get for now...</h1>
"""
return web.Response(
text=endpoint.render_base_template("Actions", html_response),
content_type="text/html",
)
return web.json_response({"message": "actions has no get for now"})
reload(endpoint)
# Check if the request prefers HTML content
if "text/html" in request.headers.get("Accept", ""):
# # Return an HTML page
html_response = f"""
<div class="flex-container menu">
<a href="/mtb/debug">debug</a>
<a href="/mtb/status">status</a>
</div>
"""
return web.Response(
text=endpoint.render_base_template("MTB", html_response),
content_type="text/html",
)
@PromptServer.instance.routes.post("/mtb/actions")
async def do_action(request):
from . import endpoint
# Return JSON for other requests
return web.json_response({"message": "Welcome to MTB!"})
reload(endpoint)
@PromptServer.instance.routes.get("/mtb/debug")
async def get_debug(request):
from . import endpoint
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"""
<h1>MTB Debug Status: {'Enabled' if enabled else 'Disabled'}</h1>
"""
return web.Response(
text=endpoint.render_base_template("Debug", html_response),
content_type="text/html",
)
# Return JSON for other requests
return web.json_response({"enabled": enabled})
@PromptServer.instance.routes.get("/mtb/actions")
async def no_route(request):
from . import endpoint
if "text/html" in request.headers.get("Accept", ""):
html_response = f"""
<h1>Actions has no get for now...</h1>
"""
return web.Response(
text=endpoint.render_base_template("Actions", html_response),
content_type="text/html",
)
return web.json_response({"message": "actions has no get for now"})
@PromptServer.instance.routes.post("/mtb/actions")
async def do_action(request):
from . import endpoint
reload(endpoint)
return await endpoint.do_action(request)
return await endpoint.do_action(request)
# - WAS Dictionary
+274 -237
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 76,
"last_link_id": 161,
"last_node_id": 86,
"last_link_id": 178,
"nodes": [
{
"id": 59,
@@ -14,7 +14,7 @@
26
],
"flags": {},
"order": 12,
"order": 13,
"mode": 0,
"inputs": [
{
@@ -50,7 +50,7 @@
26
],
"flags": {},
"order": 8,
"order": 9,
"mode": 0,
"inputs": [
{
@@ -85,7 +85,7 @@
26
],
"flags": {},
"order": 11,
"order": 12,
"mode": 0,
"inputs": [
{
@@ -121,7 +121,7 @@
26
],
"flags": {},
"order": 6,
"order": 7,
"mode": 0,
"inputs": [
{
@@ -167,7 +167,7 @@
{
"name": "text",
"type": "STRING",
"link": 87,
"link": 164,
"widget": {
"name": "text",
"config": [
@@ -242,7 +242,7 @@
164.31304931640625
],
"flags": {},
"order": 7,
"order": 8,
"mode": 0,
"inputs": [
{
@@ -316,6 +316,114 @@
"revAnimated_v122.safetensors"
]
},
{
"id": 25,
"type": "PreviewImage",
"pos": [
2338.9229387812507,
-632.8275743593749
],
"size": [
726.28564453125,
475.3432312011719
],
"flags": {},
"order": 20,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 150
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 75,
"type": "PreviewImage",
"pos": [
1573.5946044921875,
-629.80322265625
],
"size": [
691.7459716796875,
479.6098327636719
],
"flags": {},
"order": 19,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 169
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 73,
"type": "PreviewImage",
"pos": [
760,
-630
],
"size": [
671.1859741210938,
483.1548156738281
],
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 153
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 79,
"type": "Text box",
"pos": [
-1400,
276
],
"size": [
400,
200
],
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
164
],
"shape": 3,
"slot_index": 0
}
],
"title": "❌Mel Negatives (general) (Negative)",
"properties": {
"Node name for S&R": "Text box"
},
"widgets_values": [
"embedding:EasyNegative, embedding:EasyNegativeV2, watermark, text, deformed, disfigured, blurry"
]
},
{
"id": 3,
"type": "KSampler",
@@ -328,7 +436,7 @@
474
],
"flags": {},
"order": 13,
"order": 14,
"mode": 0,
"inputs": [
{
@@ -366,7 +474,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
542821171532982,
542821171533322,
"fixed",
45,
8,
@@ -389,7 +497,7 @@
"flags": {
"collapsed": false
},
"order": 14,
"order": 15,
"mode": 0,
"inputs": [
{
@@ -410,7 +518,7 @@
"type": "IMAGE",
"links": [
153,
161
167
],
"shape": 3,
"slot_index": 0
@@ -421,74 +529,24 @@
}
},
{
"id": 25,
"type": "PreviewImage",
"id": 70,
"type": "Restore Face (mtb)",
"pos": [
2338.9229387812507,
-632.8275743593749
2320,
-50
],
"size": [
726.28564453125,
475.3432312011719
],
"flags": {},
"order": 19,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 150
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 75,
"type": "PreviewImage",
"pos": [
1573.5946044921875,
-629.80322265625
],
"size": [
691.7459716796875,
479.6098327636719
315,
186
],
"flags": {},
"order": 18,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 156
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 70,
"type": "Restore Face (mtb)",
"pos": [
1899,
-51
],
"size": [
315,
150
],
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 149
"link": 168
},
{
"name": "model",
@@ -512,78 +570,56 @@
"Node name for S&R": "Restore Face (mtb)"
},
"widgets_values": [
"false",
"false",
false,
false,
0.5,
"true"
true
]
},
{
"id": 67,
"type": "Face Swap (mtb)",
"id": 72,
"type": "UpscaleModelLoader",
"pos": [
1127,
-26
1715,
52
],
"size": [
315,
122
260.3902351585391,
58
],
"flags": {},
"order": 16,
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 161
},
{
"name": "reference",
"type": "IMAGE",
"link": 160,
"slot_index": 1
},
{
"name": "faceswap_model",
"type": "FACESWAP_MODEL",
"link": 147,
"slot_index": 2
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"name": "UPSCALE_MODEL",
"type": "UPSCALE_MODEL",
"links": [
149,
156
152
],
"shape": 3,
"slot_index": 0
"shape": 3
}
],
"properties": {
"Node name for S&R": "Face Swap (mtb)"
"Node name for S&R": "UpscaleModelLoader"
},
"widgets_values": [
"0",
"false"
"4x-UltraSharp.pth"
]
},
{
"id": 71,
"type": "Load Face Enhance Model (mtb)",
"pos": [
1476,
240
2005,
40
],
"size": [
315,
82
265.97600515853924,
87.31192548828142
],
"flags": {},
"order": 9,
"order": 11,
"mode": 0,
"inputs": [
{
@@ -611,82 +647,26 @@
2
]
},
{
"id": 72,
"type": "UpscaleModelLoader",
"pos": [
1110,
241
],
"size": [
315,
58
],
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "UPSCALE_MODEL",
"type": "UPSCALE_MODEL",
"links": [
152
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "UpscaleModelLoader"
},
"widgets_values": [
"4x-UltraSharp.pth"
]
},
{
"id": 73,
"type": "PreviewImage",
"pos": [
760,
-630
],
"size": [
671.1859741210938,
483.1548156738281
],
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 153
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 76,
"type": "Load Image From Url (mtb)",
"pos": [
161,
231
624,
39
],
"size": [
315,
58
],
"flags": {},
"order": 3,
"order": 4,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
160
166
],
"shape": 3,
"slot_index": 0
@@ -703,22 +683,22 @@
"id": 69,
"type": "Load Face Swap Model (mtb)",
"pos": [
172,
503
621,
252
],
"size": [
315,
58
],
"flags": {},
"order": 4,
"order": 5,
"mode": 0,
"outputs": [
{
"name": "FACESWAP_MODEL",
"type": "FACESWAP_MODEL",
"links": [
147
165
],
"shape": 3
}
@@ -731,43 +711,92 @@
]
},
{
"id": 20,
"type": "Styles Loader (mtb)",
"id": 81,
"type": "Load Face Analysis Model (mtb)",
"pos": [
-1341,
276
624,
144
],
"size": [
315,
78
58
],
"flags": {
"collapsed": false
},
"order": 5,
"flags": {},
"order": 6,
"mode": 0,
"outputs": [
{
"name": "positive",
"type": "STRING",
"links": [],
"shape": 3
},
{
"name": "negative",
"type": "STRING",
"name": "FACE_ANALYSIS_MODEL",
"type": "FACE_ANALYSIS_MODEL",
"links": [
87
170
],
"shape": 3,
"slot_index": 1
"shape": 3
}
],
"properties": {
"Node name for S&R": "Styles Loader (mtb)"
"Node name for S&R": "Load Face Analysis Model (mtb)"
},
"widgets_values": [
"❌Low Token"
"buffalo_l"
]
},
{
"id": 80,
"type": "Face Swap (mtb)",
"pos": [
1255,
-32
],
"size": [
210,
154
],
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 167
},
{
"name": "reference",
"type": "IMAGE",
"link": 166
},
{
"name": "faceanalysis_model",
"type": "FACE_ANALYSIS_MODEL",
"link": 170,
"slot_index": 2
},
{
"name": "faceswap_model",
"type": "FACESWAP_MODEL",
"link": 165,
"slot_index": 3
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
168,
169
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Face Swap (mtb)"
},
"widgets_values": [
"0",
false
]
}
],
@@ -796,14 +825,6 @@
2,
"CONDITIONING"
],
[
87,
20,
1,
7,
1,
"STRING"
],
[
117,
16,
@@ -876,22 +897,6 @@
0,
"CLIP"
],
[
147,
69,
0,
67,
2,
"FACESWAP_MODEL"
],
[
149,
67,
0,
70,
0,
"IMAGE"
],
[
150,
70,
@@ -925,28 +930,60 @@
"IMAGE"
],
[
156,
67,
164,
79,
0,
7,
1,
"STRING"
],
[
165,
69,
0,
80,
3,
"FACESWAP_MODEL"
],
[
166,
76,
0,
80,
1,
"IMAGE"
],
[
167,
66,
0,
80,
0,
"IMAGE"
],
[
168,
80,
0,
70,
0,
"IMAGE"
],
[
169,
80,
0,
75,
0,
"IMAGE"
],
[
160,
76,
170,
81,
0,
67,
1,
"IMAGE"
],
[
161,
66,
0,
67,
0,
"IMAGE"
80,
2,
"FACE_ANALYSIS_MODEL"
]
],
"groups": [
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16 -17
View File
@@ -1,5 +1,5 @@
{
"last_node_id": 39,
"last_node_id": 42,
"last_link_id": 69,
"nodes": [
{
@@ -93,7 +93,7 @@
],
"size": [
210,
36.36605696244669
36.366058349609375
],
"flags": {},
"order": 8,
@@ -347,7 +347,7 @@
114
],
"size": [
301.23302978515596,
301.2330322265625,
98
],
"flags": {},
@@ -469,7 +469,7 @@
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"A blonde doll with a pink dress sleeping, draping, wrinkles"
"A plastic Barbie doll with a pink dress sleeping, draping, wrinkles, shiny"
]
},
{
@@ -481,7 +481,7 @@
],
"size": [
294,
95.12843764123829
95.1284408569336
],
"flags": {},
"order": 3,
@@ -502,7 +502,7 @@
"Node name for S&R": "Text box"
},
"widgets_values": [
"embedding:EasyNegative, embedding:EasyNegativeV2, watermark, text, deformed"
"embedding:EasyNegative, embedding:EasyNegativeV2, watermark, text, deformed, NSFW, Cleavage, Pubic Hair, Nudity, Naked, censored"
]
},
{
@@ -690,8 +690,8 @@
259
],
"size": [
330.11123461913985,
378.1239961059566
330.1112365722656,
378.1239929199219
],
"flags": {},
"order": 19,
@@ -716,8 +716,8 @@
1256
],
"size": [
356.17758847656205,
471.87858076171824
356.1775817871094,
471.8785705566406
],
"flags": {},
"order": 14,
@@ -735,8 +735,7 @@
"widgets_values": [
12,
1,
true,
"/view?filename=7964e7cc8b.gif&subfolder=&type=output"
true
]
},
{
@@ -833,7 +832,7 @@
882
],
"size": [
232.28509687500036,
232.28509521484375,
166
],
"flags": {},
@@ -873,12 +872,12 @@
"id": 17,
"type": "Animation Builder (mtb)",
"pos": [
-1309,
-1310,
883
],
"size": [
211.60000610351562,
294
318
],
"flags": {},
"order": 7,
@@ -1175,8 +1174,8 @@
{
"title": "START THE QUEUE BY CLICKLING HERE 👆",
"bounding": [
-1611,
1196,
-1612,
1219,
521,
80
],
-932
View File
@@ -1,932 +0,0 @@
{
"last_node_id": 24,
"last_link_id": 30,
"nodes": [
{
"id": 11,
"type": "Get Batch From History (mtb)",
"pos": [
-828,
522
],
"size": [
235.1999969482422,
118
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "passthrough_image",
"type": "IMAGE",
"link": 10
},
{
"name": "enable",
"type": "BOOL",
"link": 9,
"widget": {
"name": "enable",
"config": [
"BOOL",
{
"default": true
}
]
},
"slot_index": 1
}
],
"outputs": [
{
"name": "i",
"type": "IMAGE",
"links": [
26
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Get Batch From History (mtb)"
},
"widgets_values": [
false,
1,
0
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 24,
"type": "Note",
"pos": [
-827,
406
],
"size": [
229.35427033319013,
69.73426380916959
],
"flags": {},
"order": 5,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"On first frame we get the init image, on all subsequent ones the feedback from the previous queue item"
],
"color": "#223",
"bgcolor": "#335",
"shape": 1
},
{
"id": 10,
"type": "LoadImage",
"pos": [
-1388,
321
],
"size": [
315,
314
],
"flags": {},
"order": 4,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
10
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 13,
"type": "PrimitiveNode",
"pos": [
-1679,
817
],
"size": [
210,
82
],
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "INT",
"type": "INT",
"links": [
8
],
"widget": {
"name": "int",
"config": [
"INT",
{
"default": 0
}
]
}
}
],
"title": "int",
"properties": {},
"widgets_values": [
0,
"increment"
]
},
{
"id": 12,
"type": "Int To Bool (mtb)",
"pos": [
-1290,
818
],
"size": [
210,
42.2801628112793
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "int",
"type": "INT",
"link": 8,
"widget": {
"name": "int",
"config": [
"INT",
{
"default": 0
}
]
},
"slot_index": 0
}
],
"outputs": [
{
"name": "BOOL",
"type": "BOOL",
"links": [
9
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Int To Bool (mtb)"
},
"widgets_values": [
0
]
},
{
"id": 14,
"type": "Transform Image (mtb)",
"pos": [
-54,
515
],
"size": [
315,
154
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 26
},
{
"name": "angle",
"type": "FLOAT",
"link": 30,
"widget": {
"name": "angle",
"config": [
"FLOAT",
{
"default": 0
}
]
}
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
24
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Transform Image (mtb)"
},
"widgets_values": [
0,
5,
1.02,
0,
0
]
},
{
"id": 20,
"type": "Export To Prores (mtb)",
"pos": [
394,
1803
],
"size": [
315,
82
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 17
}
],
"outputs": [
{
"name": "VIDEO",
"type": "VIDEO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "Export To Prores (mtb)"
},
"widgets_values": [
12,
"export"
]
},
{
"id": 19,
"type": "Save Gif (mtb)",
"pos": [
392,
1321
],
"size": [
210,
336
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 18
}
],
"properties": {
"Node name for S&R": "Save Gif (mtb)"
},
"widgets_values": [
12,
1,
true,
"/view?filename=8b62123f3c.gif&subfolder=&type=output"
]
},
{
"id": 16,
"type": "Styles Loader (mtb)",
"pos": [
72,
213
],
"size": [
315,
78
],
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "positive",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "negative",
"type": "STRING",
"links": [
15
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "Styles Loader (mtb)"
},
"widgets_values": [
"❌Low Token"
]
},
{
"id": 9,
"type": "CheckpointLoaderSimple",
"pos": [
64,
68
],
"size": [
315,
98
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
4
],
"shape": 3
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
5
],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
6
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"revAnimated_v122.safetensors"
]
},
{
"id": 22,
"type": "Fit Number (mtb)",
"pos": [
39,
842
],
"size": [
210,
166
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "value",
"type": "FLOAT",
"link": 27
}
],
"outputs": [
{
"name": "FLOAT",
"type": "FLOAT",
"links": [
28
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "Fit Number (mtb)"
},
"widgets_values": [
true,
0,
1,
0.9,
0.2
]
},
{
"id": 23,
"type": "Fit Number (mtb)",
"pos": [
-342,
621
],
"size": [
210,
166
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "value",
"type": "FLOAT",
"link": 29
}
],
"outputs": [
{
"name": "FLOAT",
"type": "FLOAT",
"links": [
30
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Fit Number (mtb)"
},
"widgets_values": [
true,
0.7,
1,
0,
3
]
},
{
"id": 8,
"type": "## i2i [cac944]",
"pos": [
567,
65
],
"size": [
400,
230
],
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 4,
"slot_index": 0
},
{
"name": "clip",
"type": "CLIP",
"link": 5
},
{
"name": "vae",
"type": "VAE",
"link": 6
},
{
"name": "image",
"type": "IMAGE",
"link": 24
},
{
"name": "negative",
"type": "STRING",
"link": 15,
"widget": {
"name": "negative",
"config": [
"STRING",
{
"multiline": true,
"placeholder": "negative",
"default": ""
}
]
},
"slot_index": 4
},
{
"name": "denoise",
"type": "FLOAT",
"link": 28,
"widget": {
"name": "denoise",
"config": [
"FLOAT",
{
"default": 0.6,
"min": 0,
"max": 1,
"step": 0.01
}
]
},
"slot_index": 5
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
25
],
"shape": 3,
"slot_index": 0
},
{
"name": "LATENT",
"type": "LATENT",
"links": null,
"shape": 3
}
],
"title": "## i2i",
"properties": {
"Node name for S&R": "## i2i [cac944]"
},
"widgets_values": [
"A blond girl in a pink dress, clear sky, 1958",
"",
0.2599999999999997
]
},
{
"id": 15,
"type": "SaveImage",
"pos": [
1054,
67
],
"size": [
1110.1168110198978,
630.0240418701173
],
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 25
}
],
"properties": {},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 17,
"type": "Animation Builder (mtb)",
"pos": [
-1032,
1274
],
"size": [
210,
306
],
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "frame",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "0-1 (scaled)",
"type": "FLOAT",
"links": [
27,
29
],
"shape": 3,
"slot_index": 1
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "loop_ended",
"type": "BOOL",
"links": [
16
],
"shape": 3,
"slot_index": 3
}
],
"properties": {
"Node name for S&R": "Animation Builder (mtb)"
},
"widgets_values": [
30,
1,
1,
90,
1,
"raw: 90\nframe: 0",
"Done 😎!",
"reset",
"queue"
]
},
{
"id": 18,
"type": "Get Batch From History (mtb)",
"pos": [
-369,
1511
],
"size": [
235.1999969482422,
118
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "passthrough_image",
"type": "IMAGE",
"link": null
},
{
"name": "enable",
"type": "BOOL",
"link": 16,
"widget": {
"name": "enable",
"config": [
"BOOL",
{
"default": true
}
]
},
"slot_index": 1
}
],
"outputs": [
{
"name": "i",
"type": "IMAGE",
"links": [
17,
18
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Get Batch From History (mtb)"
},
"widgets_values": [
false,
30,
0
]
}
],
"links": [
[
4,
9,
0,
8,
0,
"MODEL"
],
[
5,
9,
1,
8,
1,
"CLIP"
],
[
6,
9,
2,
8,
2,
"VAE"
],
[
8,
13,
0,
12,
0,
"INT"
],
[
9,
12,
0,
11,
1,
"BOOL"
],
[
10,
10,
0,
11,
0,
"IMAGE"
],
[
15,
16,
1,
8,
4,
"STRING"
],
[
16,
17,
3,
18,
1,
"BOOL"
],
[
17,
18,
0,
20,
0,
"IMAGE"
],
[
18,
18,
0,
19,
0,
"IMAGE"
],
[
24,
14,
0,
8,
3,
"IMAGE"
],
[
25,
8,
0,
15,
0,
"IMAGE"
],
[
26,
11,
0,
14,
0,
"IMAGE"
],
[
27,
17,
1,
22,
0,
"FLOAT"
],
[
28,
22,
0,
8,
5,
"FLOAT"
],
[
29,
17,
1,
23,
0,
"FLOAT"
],
[
30,
23,
0,
14,
1,
"FLOAT"
]
],
"groups": [
{
"title": "Init Image",
"bounding": [
-1730,
215,
716,
713
],
"color": "#3f789e",
"locked": false
},
{
"title": "Video Output",
"bounding": [
-426,
1176,
1223,
787
],
"color": "#3f789e",
"locked": false
},
{
"title": "Animation",
"bounding": [
-473,
363,
900,
718
],
"color": "#3f789e",
"locked": false
}
],
"config": {},
"extra": {},
"version": 0.4
}
+27 -13
View File
@@ -258,23 +258,16 @@ def import_or_install(requirement, dry=False):
# Install dependencies from requirements.txt
def install_dependencies(dry=False):
parsed_requirements = get_requirements(here / "requirements.txt")
parsed_requirements = get_requirements(here / "reqs.txt")
if not parsed_requirements:
return
print_formatted(
"Installing dependencies from requirements.txt...", "italic", color="yellow"
"Installing dependencies from reqs.txt...", "italic", color="yellow"
)
for requirement in parsed_requirements:
import_or_install(requirement, dry=dry)
if mode == "venv":
parsed_requirements = get_requirements(here / "requirements-wheels.txt")
if not parsed_requirements:
return
for requirement in parsed_requirements:
import_or_install(requirement, dry=dry)
if __name__ == "__main__":
full = False
@@ -328,7 +321,6 @@ if __name__ == "__main__":
# Install dependencies from requirements.txt
# if args.requirements or mode == "venv":
install_dependencies(dry=args.dry)
if (not args.wheels and mode not in ["colab", "embeded"]) and not full:
print_formatted(
@@ -336,6 +328,8 @@ if __name__ == "__main__":
"italic",
color="yellow",
)
install_dependencies(dry=args.dry)
sys.exit()
if mode in ["colab", "embeded"]:
@@ -349,7 +343,7 @@ if __name__ == "__main__":
# - Check the env before proceeding.
missing_wheels = False
parsed_requirements = get_requirements(here / "requirements-wheels.txt")
parsed_requirements = get_requirements(here / "reqs.txt")
if parsed_requirements:
for requirement in parsed_requirements:
installed, pip_name, import_name = try_import(requirement)
@@ -359,7 +353,7 @@ if __name__ == "__main__":
if not missing_wheels:
print_formatted(
f"All required wheels are already installed.", "italic", color="green"
f"All requirements are already installed.", "italic", color="green"
)
sys.exit()
@@ -408,7 +402,7 @@ if __name__ == "__main__":
)
wheels_directory.mkdir(exist_ok=True)
# - Install the wheels
for asset in matching_assets:
asset_name = asset["name"]
asset_download_url = asset["browser_download_url"]
@@ -418,6 +412,7 @@ if __name__ == "__main__":
# - Unzip to wheels dir
whl_files = []
whl_order = None
with zipfile.ZipFile(asset_dest, "r") as zip_ref:
for item in tqdm(zip_ref.namelist(), desc="Extracting", unit="file"):
if item.endswith(".whl"):
@@ -428,6 +423,14 @@ if __name__ == "__main__":
) as target:
whl_files.append(target_path)
shutil.copyfileobj(source, target)
elif item.endswith("order.txt"):
item_basename = os.path.basename(item)
target_path = wheels_directory / item_basename
with zip_ref.open(item) as source, open(
target_path, "wb"
) as target:
whl_order = target_path
shutil.copyfileobj(source, target)
print_formatted(
f"Wheels extracted for {current_platform} to the '{wheels_directory}' directory.",
@@ -436,6 +439,14 @@ if __name__ == "__main__":
)
if whl_files:
if whl_order:
with open(whl_order, "r") as order:
wheel_order_lines = [line.strip() for line in order]
whl_files = sorted(
whl_files,
key=lambda x: wheel_order_lines.index(x.split("-")[0]),
)
for whl_file in tqdm(whl_files, desc="Installing", unit="package"):
whl_path = wheels_directory / whl_file
@@ -470,3 +481,6 @@ if __name__ == "__main__":
print_formatted("Wheels installation completed.", color="green")
else:
print_formatted("No .whl files found. Nothing to install.", color="yellow")
# - Install all remainings
install_dependencies(dry=args.dry)
+4
View File
@@ -28,6 +28,10 @@ class LoadFaceEnhanceModel:
@classmethod
def get_models(cls):
models_path = cls.get_models_root()
if not models_path.exists():
log.warning(f"No models found at {models_path}")
return []
return [
x
+46 -14
View File
@@ -22,6 +22,37 @@ import comfy.model_management as model_management
log = mklog(__name__)
class LoadFaceAnalysisModel:
"""Loads a face analysis model"""
models = []
@staticmethod
def get_models() -> List[str]:
models_path = os.path.join(folder_paths.models_dir, "insightface/*")
models = glob.glob(models_path)
models = [Path(x).name for x in models if x.endswith(".onnx") or x.endswith(".pth")]
return models
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"faceswap_model": (
["antelopev2", "buffalo_l", "buffalo_m", "buffalo_sc"],
{"default": "buffalo_l"},
),
},
}
RETURN_TYPES = ("FACE_ANALYSIS_MODEL",)
FUNCTION = "load_model"
CATEGORY = "mtb/facetools"
def load_model(self, faceswap_model: str):
face_analyser = insightface.app.FaceAnalysis(
name=faceswap_model, root=os.path.join(folder_paths.models_dir, "insightface")
)
return (face_analyser,)
class LoadFaceSwapModel:
"""Loads a faceswap model"""
@@ -81,6 +112,7 @@ class FaceSwap:
"image": ("IMAGE",),
"reference": ("IMAGE",),
"faces_index": ("STRING", {"default": "0"}),
"faceanalysis_model": ("FACE_ANALYSIS_MODEL", {"default": "None"}),
"faceswap_model": ("FACESWAP_MODEL", {"default": "None"}),
"debug": ("BOOL", {"default": False}),
},
@@ -96,6 +128,7 @@ class FaceSwap:
image: torch.Tensor,
reference: torch.Tensor,
faces_index: str,
faceanalysis_model,
faceswap_model,
debug=False,
):
@@ -107,7 +140,7 @@ class FaceSwap:
int(x) for x in faces_index.strip(",").split(",") if x.isnumeric()
}
sys.stdout = NullWriter()
swapped = swap_face(ref, img, faceswap_model, face_ids)
swapped = swap_face(faceanalysis_model,ref, img, faceswap_model, face_ids)
sys.stdout = sys.__stdout__
return pil2tensor(swapped)
@@ -121,8 +154,8 @@ class FaceSwap:
image = do_swap(image)
else:
image = [do_swap(image[i]) for i in range(batch_count)]
image = torch.cat(image, dim=0)
image_batch = [do_swap(image[i]) for i in range(batch_count)]
image = torch.cat(image_batch, dim=0)
return (image,)
@@ -131,17 +164,15 @@ class FaceSwap:
# region face swap utils
def get_face_single(img_data: np.ndarray, face_index=0, det_size=(640, 640)):
face_analyser = insightface.app.FaceAnalysis(
name="buffalo_l", root=os.path.join(folder_paths.models_dir, "insightface")
)
def get_face_single(face_analyser,img_data: np.ndarray, face_index=0, det_size=(640, 640)):
face_analyser.prepare(ctx_id=0, det_size=det_size)
face = face_analyser.get(img_data)
if len(face) == 0 and det_size[0] > 320 and det_size[1] > 320:
log.debug("No face ed, trying again with smaller image")
det_size_half = (det_size[0] // 2, det_size[1] // 2)
return get_face_single(img_data, face_index=face_index, det_size=det_size_half)
return get_face_single(face_analyser,img_data, face_index=face_index, det_size=det_size_half)
try:
return sorted(face, key=lambda x: x.bbox[0])[face_index]
@@ -150,6 +181,7 @@ def get_face_single(img_data: np.ndarray, face_index=0, det_size=(640, 640)):
def swap_face(
face_analyser,
source_img: Union[Image.Image, List[Image.Image]],
target_img: Union[Image.Image, List[Image.Image]],
face_swapper_model,
@@ -161,14 +193,14 @@ def swap_face(
result_image = target_img
if face_swapper_model is not None:
source_img = cv2.cvtColor(np.array(source_img), cv2.COLOR_RGB2BGR)
target_img = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR)
source_face = get_face_single(source_img, face_index=0)
cv_source_img = cv2.cvtColor(np.array(source_img), cv2.COLOR_RGB2BGR)
cv_target_img = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR)
source_face = get_face_single(face_analyser,cv_source_img, face_index=0)
if source_face is not None:
result = target_img
result = cv_target_img
for face_num in faces_index:
target_face = get_face_single(target_img, face_index=face_num)
target_face = get_face_single(face_analyser,cv_target_img, face_index=face_num)
if target_face is not None:
sys.stdout = NullWriter()
result = face_swapper_model.get(result, target_face, source_face)
@@ -187,4 +219,4 @@ def swap_face(
# endregion face swap utils
__nodes__ = [FaceSwap, LoadFaceSwapModel]
__nodes__ = [FaceSwap, LoadFaceSwapModel, LoadFaceAnalysisModel]
+1 -1
View File
@@ -13,6 +13,6 @@
"reportMissingImports": true,
"reportMissingTypeStubs": false,
"pythonVersion": "3.10",
"pythonPlatform": "Windows",
"pythonPlatform": "All",
"reportOptionalMemberAccess": "none"
}
+4 -1
View File
@@ -8,7 +8,10 @@ tensorflow<2.11.0; platform_system == "Windows"
tb-nightly==2.12.0a20230126; platform_system == "Windows"
tensorflow; platform_system != "Windows"
# the old tf version on windows comes with a breaking protobuf version
protobuf==3.19.6
protobuf==3.20.2; platform_system == "Windows"
gdown @ git+https://github.com/melMass/gdown@main
mmdet==3.0.0
facexlib==0.3.0
insightface==0.7.3
mmcv==2.0.0
basicsr==1.4.2
-3
View File
@@ -1,3 +0,0 @@
insightface==0.7.3
mmcv==2.0.0
basicsr==1.4.2
+32
View File
@@ -0,0 +1,32 @@
## Core
These 3 scripts cannot be used independently and must all be present to work, they are mostly enhancing the frontend of python nodes
- `comfy_shared`: library of methods used in `mtb_widgets` and `debug`
**mtb_widgets** define ui callbacks, and various widgets like the `COLOR` type:
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/5dbcb714-e1e2-4be7-b0e2-68a6c38c83de" width=400/>
or the `BOOL` type:
<img src="https://github.com/melMass/comfy_mtb/assets/7041726/7601366d-601c-4f4d-b735-1a4b076770b0" width=400/>
There is also `Debug` which is a node that should be able to display any data input, it handle a few cases and fallback to the string representation of the
data otherwise:
![debug](https://github.com/melMass/comfy_mtb/assets/7041726/1f4393e4-1c3d-4807-9501-fe8888bfae25)
## Standalone
These scripts can be taken and placed independently of `comfy_mtb` or any other files, mimicking what pythongosss did for their
- **imageFeed**: a fork of @pythongosssss ' s [image feed](https://github.com/pythongosssss/ComfyUI-Custom-Scripts/tree/main/js), it adds support for: a lightbox to see images bigger, a way to load the current session history (in case of a web page reload), and different icons, most of the work come from the original script.
> **NOTE**
>
> The original imagefeed got updated since and offer more options, ideally I would clean my lightbox thing and PR it to pythongoss later but in the meantime the script will detect if you already use the original one and not load this fork
- ![imagefeed2-hd](https://github.com/melMass/comfy_mtb/assets/7041726/8539f46f-78e1-459a-a11c-fddd44e63ca9)
- **notify**: a basic toast notification system that I use in some places accross mtb, it can be used by simply calling `window.MTB.notify("Hello world!")`
![extract](https://github.com/melMass/comfy_mtb/assets/7041726/450c67fc-a7e9-4bea-ae49-b610d693098d)
+26 -6
View File
@@ -34,7 +34,7 @@ const styles = {
zIndex: 9999999,
fontSize: '30px',
cursor: 'pointer',
pointerEvents: 'bounding-box',
pointerEvents: 'auto',
...extra,
}),
img_list: {
@@ -53,10 +53,21 @@ let currentImageIndex = 0
const imageUrls = []
let image_menu = null
let activated = true
app.registerExtension({
name: 'mtb.ImageFeed',
setup: async () => {
init: async () => {
const pythongossFeed = app.extensions.find(
(e) => e.name == 'pysssss.ImageFeed'
)
if (pythongossFeed) {
console.warn(
"[mtb] - Aborting the loading of mtb's imageFeed in favor of pysssss.ImageFeed"
)
activated = false // just in case other methods are added later on
return
}
// - HTML & CSS
//- lightbox
const lightboxContainer = document.createElement('div')
@@ -209,11 +220,14 @@ app.registerExtension({
Object.assign(but.style, {
height: '120px',
width: '120px',
border: 'none',
padding: 0,
margin: 0,
})
Object.assign(img.style, {
width: '100%',
height: '100%',
objectFit: 'scale-down',
objectFit: 'cover',
})
img.src = `/view?filename=${encodeURIComponent(src.filename)}&type=${
@@ -224,6 +238,10 @@ app.registerExtension({
console.debug(img.src)
img.onload = () => {
but.style.width = `${120 * (img.naturalWidth / img.naturalHeight)}px`
}
but.onclick = () => {
lightboxContainer.style.display = 'flex'
// add the same image to the lightbox
@@ -270,9 +288,11 @@ app.registerExtension({
if (history.outputs) {
for (const key of Object.keys(history.outputs)) {
console.debug(key)
for (const im of history.outputs[key].images) {
console.debug(im)
createImageBtn(im)
if (history.outputs[key].images) {
for (const im of history.outputs[key].images) {
console.debug(im)
createImageBtn(im)
}
}
}
// for (const src of outputs.outputs.images) {
+47 -10
View File
@@ -688,7 +688,34 @@ const mtb_widgets = {
}
break
}
case 'Save Gif (mtb)': {
//TODO: remove this non sense
case 'Get Batch From History (mtb)': {
const onNodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated
? onNodeCreated.apply(this, arguments)
: undefined
const internal_count = this.widgets.find(
(w) => w.name === 'internal_count'
)
shared.hideWidgetForGood(this, internal_count)
internal_count.afterQueued = function () {
this.value++
}
return r
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted ? onExecuted.apply(this, message) : undefined
return r
}
break
}
case 'Save Gif (mtb)':
case 'Save Animated Image (mtb)': {
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
const prefix = 'anything_'
@@ -704,15 +731,25 @@ const mtb_widgets = {
}
let imgURLs = []
if (message && message.gif) {
imgURLs = imgURLs.concat(
message.gif.map((params) => {
return api.apiURL(
'/view?' + new URLSearchParams(params).toString()
)
})
)
if (message) {
if (message.gif) {
imgURLs = imgURLs.concat(
message.gif.map((params) => {
return api.apiURL(
'/view?' + new URLSearchParams(params).toString()
)
})
)
}
if (message.apng) {
imgURLs = imgURLs.concat(
message.apng.map((params) => {
return api.apiURL(
'/view?' + new URLSearchParams(params).toString()
)
})
)
}
let i = 0
for (const img of imgURLs) {
const w = this.addCustomWidget(