Add ControlNet feature and update workflows and examples for enhanced functionality.
This commit is contained in:
+88
-64
@@ -1,16 +1,18 @@
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import torch
|
||||
from huggingface_hub import snapshot_download
|
||||
from folder_paths import get_filename_list, get_output_directory, models_dir
|
||||
|
||||
from mflux.config.model_config import ModelConfig
|
||||
from mflux.config.config import Config
|
||||
from mflux.config.config import Config, ConfigControlnet
|
||||
from mflux.flux.flux import Flux1
|
||||
from mflux.controlnet.flux_controlnet import Flux1Controlnet
|
||||
from pathlib import Path
|
||||
from folder_paths import get_filename_list, get_output_directory, models_dir
|
||||
from huggingface_hub import snapshot_download
|
||||
import torch
|
||||
import json
|
||||
from .MfluxPro import MfluxControlNetPipeline
|
||||
|
||||
flux_cache = {}
|
||||
|
||||
@@ -37,43 +39,10 @@ def load_or_create_flux(model, path, lora_paths, lora_scales, is_controlnet=Fals
|
||||
lora_paths=lora_paths,
|
||||
lora_scales=lora_scales,
|
||||
)
|
||||
# This code is licensed under the Apache 2.0 License.
|
||||
# This code is licensed under the Apache 2.0 License.
|
||||
# Portions of this code are derived from the work of CharafChnioune at https://github.com/CharafChnioune/MFLUX-WEBUI
|
||||
return flux_cache[key]
|
||||
|
||||
|
||||
def generate_image(prompt, model, seed, height, width, steps, guidance, path, lora_files, lora_scales_str):
|
||||
lora_paths = lora_files if lora_files else []
|
||||
lora_scales = [float(s.strip()) for s in lora_scales_str.split(",")] if lora_scales_str else []
|
||||
|
||||
seed = random.randint(0, 0xffffffffffffffff) if seed == -1 else int(seed)
|
||||
print(f"Using seed: {seed}")
|
||||
|
||||
flux = load_or_create_flux(model, path if path else None, lora_paths, lora_scales)
|
||||
|
||||
image = flux.generate_image(
|
||||
seed=seed,
|
||||
prompt=prompt,
|
||||
config=Config(
|
||||
num_inference_steps=steps,
|
||||
height=height,
|
||||
width=width,
|
||||
guidance=guidance,
|
||||
),
|
||||
)
|
||||
|
||||
inner_image = image.image
|
||||
|
||||
inner_image_numpy = np.array(inner_image).astype(np.float32)
|
||||
|
||||
normalized = inner_image_numpy / 255.0
|
||||
|
||||
tensor_image = torch.from_numpy(normalized).permute(2, 0, 1)
|
||||
tensor_image = tensor_image.unsqueeze(0)
|
||||
tensor_image = tensor_image.permute(0, 2, 3, 1)
|
||||
|
||||
return inner_image, tensor_image
|
||||
|
||||
class QuickMfluxNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -83,24 +52,38 @@ class QuickMfluxNode:
|
||||
"model": (["dev", "schnell"], {"default": "schnell"}),
|
||||
"path": (["flux.1-schnell-mflux-4bit", "flux.1-dev-mflux-4bit"], {"default": "flux.1-schnell-mflux-4bit"}),
|
||||
"seed": ("INT", {"default": -1, "min": -1, "max": 0xffffffffffffffff}),
|
||||
"height": ("INT", {"default": 1024, "min": 256}),
|
||||
"width": ("INT", {"default": 1024, "min": 256}),
|
||||
"width": ("INT", {"default": 512}),
|
||||
"height": ("INT", {"default": 512}),
|
||||
"steps": ("INT", {"default": 2, "min": 1}),
|
||||
"guidance": ("FLOAT", {"default": 3.5, "min": 0.0}),
|
||||
"cfg": ("FLOAT", {"default": 3.5, "min": 0.0}),
|
||||
"metadata": (["true", "false"], {"default": "false"}),
|
||||
},
|
||||
"optional": {
|
||||
"lora_files": ("LIST", {"default": []}),
|
||||
"lora_scales": ("STRING", {"default": ""}),
|
||||
"ControlNet": ("MfluxControlNetPipeline",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "Mflux/Quick"
|
||||
CATEGORY = "Mflux/Air"
|
||||
FUNCTION = "generate"
|
||||
|
||||
def generate(self, prompt, model, path, seed, height, width, steps, guidance, metadata="false", lora_files=[], lora_scales=""):
|
||||
model_id = f"madroid/{path}"
|
||||
def generate(self, prompt, model, path, seed, width, height, steps, cfg, metadata="false", lora_files=[], lora_scales="", ControlNet=None):
|
||||
image_path, save_canny = None, None
|
||||
strength = None
|
||||
|
||||
if ControlNet:
|
||||
if isinstance(ControlNet, MfluxControlNetPipeline):
|
||||
image_path = ControlNet.image_path
|
||||
save_canny = ControlNet.save_canny
|
||||
strength = ControlNet.strength
|
||||
else:
|
||||
print("Unexpected format for ControlNet.")
|
||||
else:
|
||||
print("Non-ControlNet")
|
||||
|
||||
model_id = f"madroid/{path}"
|
||||
full_model_path = download_hg_model(model_id, path)
|
||||
|
||||
if model == "dev":
|
||||
@@ -108,16 +91,65 @@ class QuickMfluxNode:
|
||||
elif model == "schnell":
|
||||
steps = min(max(steps, 2), 4)
|
||||
|
||||
inner_image, tensor_image = generate_image(prompt, model, seed, height, width, steps, guidance, full_model_path, lora_files, lora_scales)
|
||||
|
||||
lora_paths = lora_files if lora_files else []
|
||||
lora_scales = [float(s.strip()) for s in lora_scales.split(",")] if lora_scales else []
|
||||
|
||||
is_controlnet = ControlNet is not None
|
||||
flux = load_or_create_flux(model, full_model_path, lora_paths, lora_scales, is_controlnet)
|
||||
|
||||
seed = random.randint(0, 0xffffffffffffffff) if seed == -1 else int(seed)
|
||||
print(f"Using seed: {seed}")
|
||||
|
||||
output_image_path = None
|
||||
if is_controlnet and save_canny == "true":
|
||||
output_directory = get_output_directory()
|
||||
output_image_path = f"{output_directory}/generated_image.png"
|
||||
|
||||
if is_controlnet:
|
||||
config = ConfigControlnet(
|
||||
num_inference_steps=steps,
|
||||
height=height,
|
||||
width=width,
|
||||
guidance=cfg,
|
||||
controlnet_strength=strength
|
||||
)
|
||||
else:
|
||||
config = Config(
|
||||
num_inference_steps=steps,
|
||||
height=height,
|
||||
width=width,
|
||||
guidance=cfg
|
||||
)
|
||||
|
||||
generate_args = {
|
||||
"seed": seed,
|
||||
"prompt": prompt,
|
||||
"config": config
|
||||
}
|
||||
|
||||
if is_controlnet:
|
||||
generate_args["controlnet_image_path"] = image_path
|
||||
generate_args["controlnet_save_canny"] = save_canny
|
||||
if save_canny == "true":
|
||||
generate_args["output"] = output_image_path
|
||||
else:
|
||||
generate_args["output"] = ""
|
||||
|
||||
image = flux.generate_image(**generate_args)
|
||||
|
||||
inner_image = image.image
|
||||
tensor_image = torch.from_numpy(np.array(inner_image).astype(np.float32) / 255.0)
|
||||
|
||||
if tensor_image.dim() == 3:
|
||||
tensor_image = tensor_image.unsqueeze(0)
|
||||
|
||||
if metadata == "true":
|
||||
self.save_images([inner_image], prompt, model, path, seed, height, width, steps, guidance)
|
||||
self.save_images([inner_image], prompt, model, path, seed, height, width, steps, cfg)
|
||||
|
||||
print(f"Generated tensor image with shape: {tensor_image.shape}, dtype: {tensor_image.dtype}")
|
||||
|
||||
return (tensor_image,)
|
||||
|
||||
def save_images(self, images, prompt, model, path, seed, height, width, steps, guidance, filename_prefix="Mflux"):
|
||||
def save_images(self, images, prompt, model, path, seed, height, width, steps, cfg, filename_prefix="Mflux"):
|
||||
output_dir = get_output_directory()
|
||||
mflux_dir = os.path.join(output_dir, "Mflux")
|
||||
os.makedirs(mflux_dir, exist_ok=True)
|
||||
@@ -125,7 +157,7 @@ class QuickMfluxNode:
|
||||
results = []
|
||||
existing_files = os.listdir(mflux_dir)
|
||||
existing_count = len([f for f in existing_files if f.startswith(filename_prefix)])
|
||||
|
||||
|
||||
for (batch_number, img) in enumerate(images):
|
||||
filename = f"{filename_prefix}_{existing_count + batch_number:05}.png"
|
||||
file_path = os.path.join(mflux_dir, filename)
|
||||
@@ -139,9 +171,9 @@ class QuickMfluxNode:
|
||||
"height": height,
|
||||
"width": width,
|
||||
"steps": steps,
|
||||
"guidance": guidance,
|
||||
"guidance": cfg,
|
||||
}
|
||||
|
||||
|
||||
metadata_filename = os.path.join(mflux_dir, f"{filename_prefix}_{existing_count + batch_number:05}.json")
|
||||
with open(metadata_filename, 'w') as metadata_file:
|
||||
json.dump(metadata, metadata_file)
|
||||
@@ -151,12 +183,4 @@ class QuickMfluxNode:
|
||||
"path": file_path,
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"QuickMfluxNode": QuickMfluxNode,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"QuickMfluxNode": "Quick MFlux Generation"
|
||||
}
|
||||
return results
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import os
|
||||
from PIL import Image
|
||||
import folder_paths
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from mflux.controlnet.controlnet_util import ControlnetUtil
|
||||
|
||||
class MfluxControlNetPipeline:
|
||||
def __init__(self, image_path, model_selection, strength, save_canny=False):
|
||||
self.image_path = image_path
|
||||
self.model_selection = model_selection
|
||||
self.strength = strength
|
||||
self.save_canny = save_canny
|
||||
|
||||
def clear_cache(self):
|
||||
self.image_path = None
|
||||
self.model_selection = None
|
||||
self.strength = None
|
||||
self.save_canny = False
|
||||
|
||||
class MfluxControlNetLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
||||
|
||||
controlnet_models = [
|
||||
"InstantX/FLUX.1-dev-Controlnet-Canny",
|
||||
]
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"image": (sorted(files), {"image_upload": True}),
|
||||
"model_selection": (controlnet_models, {"default": "InstantX/FLUX.1-dev-Controlnet-Canny"}),
|
||||
"strength": ("FLOAT", {"default": 0.4, "min": 0.0, "max": 1.0}),
|
||||
"save_canny": (["true", "false"], {"default": "false"}),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "Mflux/Pro"
|
||||
RETURN_TYPES = ("MfluxControlNetPipeline", "INT", "INT", "IMAGE")
|
||||
RETURN_NAMES = ("ControlNet", "width", "height", "canny_image")
|
||||
FUNCTION = "load_and_select"
|
||||
|
||||
def load_and_select(self, image, model_selection, strength, save_canny):
|
||||
save_canny_bool = save_canny == "true"
|
||||
image_path = folder_paths.get_annotated_filepath(image)
|
||||
print(f"Loading image from path: {image_path}")
|
||||
|
||||
with Image.open(image_path) as img:
|
||||
width, height = img.size
|
||||
canny_image = ControlnetUtil.preprocess_canny(img)
|
||||
canny_image_np = np.array(canny_image).astype(np.float32) / 255.0
|
||||
canny_tensor = torch.from_numpy(canny_image_np)
|
||||
|
||||
if canny_tensor.dim() == 3:
|
||||
canny_tensor = canny_tensor.unsqueeze(0)
|
||||
|
||||
print(f"Model selection: {model_selection}")
|
||||
print(f"Strength: {strength}")
|
||||
print(f"Save Canny: {save_canny_bool}")
|
||||
|
||||
return MfluxControlNetPipeline(image_path, model_selection, strength, save_canny_bool), width, height, canny_tensor
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, image, model_selection, strength, save_canny):
|
||||
return str(image) + str(model_selection) + str(strength) + str(save_canny)
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, image, model_selection, strength, save_canny):
|
||||
if not folder_paths.exists_annotated_filepath(image):
|
||||
return "Invalid image file: {}".format(image)
|
||||
|
||||
available_models = [
|
||||
"InstantX/FLUX.1-dev-Controlnet-Canny",
|
||||
]
|
||||
if model_selection not in available_models:
|
||||
return f"Invalid model selection: {model_selection}"
|
||||
|
||||
if not isinstance(strength, (int, float)):
|
||||
return "Strength must be a number"
|
||||
|
||||
if save_canny not in ["true", "false"]:
|
||||
return "save_canny must be 'true' or 'false'"
|
||||
|
||||
return True
|
||||
@@ -4,30 +4,83 @@
|
||||
<br> <font size=5>English | <a href="README_zh.md">中文</a></font>
|
||||
</p>
|
||||
|
||||
# Quick Mflux on Comfyui
|
||||
|
||||
## Introduction
|
||||
Simple use of Mflux in ComfyUI, suitable for users who are not familiar with terminal usage.
|
||||
Simple use of Mflux in ComfyUI, suitable for users who are not familiar with terminal usage, specifically for MacOS.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Special thanks to the developers of the [**mflux**](https://github.com/filipstrand/mflux) project, especially the initiator **@filipstrand** and active contributor **@anthonywu**, for making it easier and more efficient for Mac users to generate flux model images. Their contributions are truly delightful; appreciate them!
|
||||
Thanks to the developers of the [**mflux**](https://github.com/filipstrand/mflux) project, especially the initiator **@filipstrand** and active contributor **@anthonywu**, for making it easier and more efficient for Mac users to generate flux model images. These contributions are truly delightful—thank you!
|
||||
|
||||
I also want to thank **@CharafChnioune**, the author of [**MFLUX-WEBUI**](https://github.com/CharafChnioune/MFLUX-WEBUI). I have partially referenced his code, and in accordance with the Apache 2.0 license used in his project, I have added license comments in the referenced sections of my code.
|
||||
mflux:
|
||||
https://github.com/filipstrand/mflux
|
||||
|
||||
Thanks also to **@CharafChnioune**, the author of [**MFLUX-WEBUI**](https://github.com/CharafChnioune/MFLUX-WEBUI), from whom I partially referenced some code. In compliance with the Apache 2.0 license used in his project, I have added license comments in the referenced sections of my code.
|
||||
|
||||
## Installation Guide
|
||||
1. cd /path/to/your_ComfyUI
|
||||
2. Activate your virtual environment
|
||||
3. cd custom_nodes
|
||||
1. `cd /path/to/your_ComfyUI`
|
||||
2. Activate the virtual environment
|
||||
3. `cd custom_nodes`
|
||||
4. `git clone https://github.com/raysers/Mflux-ComfyUI.git`
|
||||
5. pip install mflux==0.3.0
|
||||
5. `pip install mflux==0.3.0`
|
||||
6. Restart ComfyUI
|
||||
|
||||
## Usage Instructions
|
||||
Currently, the functionality is relatively simple and only implements text-to-image generation. To use it, simply create a node named **Quick MFlux Generation** (found under **Mflux/Quick**), and then connect it to the built-in preview or save image nodes in ComfyUI. If the Quick MFlux Generation node is connected to a preview node and you are satisfied with the generated image, you will need to right-click to manually save the final result. If it is connected to a save image node, any generated images will be automatically saved to **ComfyUI/output** by the ComfyUI mechanism. Note that in the Quick MFlux Generation node, the metadata option should be set to true (default is false); if true, the generated image will be saved to **ComfyUI/output/Mflux** along with a JSON file of the same name, containing almost all the generation parameters of that image.
|
||||
Alternatively, you can search for "Mflux-ComfyUI" in ComfyUI-Manager for a quick installation.
|
||||
|
||||
PS: Alternatively, you can directly drag the JSON or PNG workflows from this project into ComfyUI, and they will load automatically.
|
||||
## Usage Instructions
|
||||
|
||||
When creating a new node via right-click:
|
||||
|
||||
**Quick MFlux Generation** is located under **Mflux Air**
|
||||
**Mflux ControlNet Loader** is located under **Mflux Pro**
|
||||
|
||||
Or, double-click on the blank canvas to open the node search box and search directly by node name, using the keyword “Mflux.”
|
||||
|
||||
### Workflows
|
||||
|
||||
**Mflux Air:**
|
||||
|
||||

|
||||
|
||||
**Mflux Pro:**
|
||||
|
||||

|
||||
Mflux's ControlNet currently supports only Canny.
|
||||
|
||||
**Mflux Plus:**
|
||||
|
||||

|
||||
Essential for non-native English speakers.
|
||||
|
||||

|
||||
Reverse image generation using MiaoshouAI/Florence-2-large-PromptGen-v1.5 vision model.
|
||||
|
||||
All these workflows can be directly dragged from the `workflows` folder into ComfyUI.
|
||||
|
||||
!!! If nodes are marked in red, use ComfyUI-Manager's "One-Click Install Missing Nodes" feature.
|
||||
!!! Please note that all workflows end with preview nodes. These do not automatically save images—you need to manually save the generated image you like, or replace the preview node with a save node.
|
||||
|
||||
In the **Quick MFlux Generation** node, if the metadata option is set to true (default is false), the generated images will be saved to **ComfyUI/output/Mflux** along with a JSON file of the same name, which contains almost all the generation parameters for the image.
|
||||
|
||||
### Possible Explorations:
|
||||
|
||||
**Mflux MAX:**
|
||||
|
||||
......
|
||||
|
||||
**Mflux Ultra:**
|
||||
|
||||
......
|
||||
|
||||
I hope experts can share more workflows and contribute to the open-sharing spirit of the internet. Paid knowledge? No, I advocate for open sharing.
|
||||
|
||||
## Roadmap
|
||||
|
||||
The next goal is to implement Lora, if possible.
|
||||
|
||||
## Contributions
|
||||
I am a novice coder, and this is my first GitHub project. I initially had grand plans, such as implementing mflux's Lora and ControlNet functionalities, and realizing the essence of ComfyUI—splitting nodes to allow users to delve into the underlying logic of flux. However, my limited coding skills have constrained my vision. If anyone with expertise could help implement these, I would be very grateful. Greetings to all!
|
||||
I am a novice coder, and this is my first GitHub project. I initially had grand plans, such as implementing mflux’s Lora and ControlNet functionalities (currently, Mflux ControlNet with Canny is implemented), and realizing ComfyUI’s core concept—splitting nodes to help users dive deep into the underlying logic of flux. However, my limited coding skills have constrained my vision. If anyone with expertise could help implement these, I would be very grateful. Greetings to all!
|
||||
|
||||
## License
|
||||
I would like to adopt the MIT license, consistent with the mflux project, as a contribution to open source.
|
||||
I would like to adopt the MIT license, consistent with the mflux project, as a contribution to open source.
|
||||
|
||||
+56
-5
@@ -8,12 +8,15 @@
|
||||
# Quick Mflux on Comfyui
|
||||
|
||||
## 简介
|
||||
Mflux在ComfyUI中的简单使用,适合不太懂得使用终端的用户。
|
||||
Mflux在ComfyUI中的简单使用,适合不太懂得使用终端的用户,仅MacOS。
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
感谢[**mflux**](https://github.com/filipstrand/mflux)项目的开发者们,特别是项目发起人 **@filipstrand** 和活跃贡献者 **@anthonywu**,是他们让Mac用户实现了更方便高效的flux模型生图,这些贡献着实令人轻松愉悦,谢谢他们!
|
||||
|
||||
mflux:
|
||||
https://github.com/filipstrand/mflux
|
||||
|
||||
同时感谢[**MFLUX-WEBUI**](https://github.com/CharafChnioune/MFLUX-WEBUI)的作者 **@CharafChnioune**,我部分参考了他的代码,基于他的项目所使用的Apache 2.0许可证的要求,我在代码中的引用段落添加了许可证注释。
|
||||
|
||||
## 安装指南
|
||||
@@ -24,13 +27,61 @@ Mflux在ComfyUI中的简单使用,适合不太懂得使用终端的用户。
|
||||
5. pip install mflux==0.3.0
|
||||
6. 重启ComfyUI
|
||||
|
||||
## 使用说明
|
||||
由于目前的功能相对简单,仅仅实现了文生图,因此只需要新建节点 **Quick MFlux Generation**(在 **Mflux/Quick** 下),再连接ComfyUI自带的预览图像或保存图像节点即可。如果 **Quick MFlux Generation** 连接的是预览图像节点,最终生成结果需要右键手动保存;如果是保存图像节点,任何生成图片都将被ComfyUI的机制自动保存到 **ComfyUI/output** 下。需要注意的是 **Quick MFlux Generation** 节点中的metadata选项,如果它是true值(默认false),那么生成的图像将被保存到 **ComfyUI/output/Mflux** 中,同时带有图像同名的json文件,里面包含了该图像的几乎一切生成参数。
|
||||
或者,从ComfyUI-Manager中直接搜索“Mflux-ComfyUI”来快速安装。
|
||||
|
||||
或者,您可以直接将本项目中的JSON或PNG工作流拖入ComfyUI,它们将自动加载。
|
||||
## 使用说明
|
||||
|
||||
节点,当右键新建时:
|
||||
|
||||
**Quick MFlux Generation** 在**Mflux/Air**下
|
||||
**Mflux ControlNet Loader** 在**Mflux/Pro**下
|
||||
|
||||
或者双击画板空白处调出节点搜索框,直接搜索节点名称,搜索关键字“Mflux”
|
||||
|
||||
### 流程
|
||||
|
||||
**Mflux Air:**
|
||||
|
||||

|
||||
|
||||
**Mflux Pro:**
|
||||
|
||||

|
||||
Mflux的ControlNet,目前仅支持Canny
|
||||
|
||||
**Mflux Plus:**
|
||||
|
||||

|
||||
英文小白们必备
|
||||
|
||||

|
||||
图像反推再生成,这里使用MiaoshouAI/Florence-2-large-PromptGen-v1.5视觉模型
|
||||
|
||||
以上流程均可从workflows文件夹中直接拖入ComfyUI.
|
||||
|
||||
!!!节点报红色的话使用ComfyUI-Manager的“一键安装缺失节点”。
|
||||
!!!请注意流程末端全部使用的是预览节点,不会自动保存,需要自己挑选满意的生成图片手动保存,或者干脆把预览节点换成保存节点。
|
||||
|
||||
**Quick MFlux Generation** 节点中的metadata选项,如果它是true值(默认false),那么生成的图像将被保存到 **ComfyUI/output/Mflux** 中,同时带有图像同名的json文件,里面包含了该图像的几乎一切生成参数。
|
||||
|
||||
### 可能的探索
|
||||
|
||||
**Mflux MAX:**
|
||||
|
||||
......
|
||||
|
||||
**Mflux Ultra:**
|
||||
|
||||
......
|
||||
|
||||
这里还是希望大佬们多分享工作流程,充分发扬互联网的共享精神,知识付费?不,我要拿来主义。
|
||||
|
||||
## 规划
|
||||
|
||||
下一步的目标是Lora,尽量吧。
|
||||
|
||||
## 贡献
|
||||
我是编码新手,这是我的第一个GitHub项目。我原本的规划十分宏大,比如实现mflux的Lora以及ControlNet功能,以及实现ComfyUI的精髓理念——即拆分节点以让用户能够深入了解flux的底层逻辑……但我粗浅的代码能力限制了我的蓝图。如果有高手能够帮忙实现这些,我十分感谢。在此问好。
|
||||
我是编码新手,这是我的第一个GitHub项目。我原本的规划十分宏大,比如实现mflux的Lora以及ControlNet功能(目前已实现Mflux ControNet仅Canny),以及实现ComfyUI的精髓理念——即拆分节点以让用户能够深入了解flux的底层逻辑……但我粗浅的代码能力限制了我的蓝图。如果有高手能够帮忙实现这些,我十分感谢。在此问好。
|
||||
|
||||
## 许可证
|
||||
我想采用和mflux项目一致的MIT麻省理工许可,也算为开源贡献一份心力吧。
|
||||
+13
-2
@@ -1,3 +1,14 @@
|
||||
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
from .MfluxAir import QuickMfluxNode
|
||||
from .MfluxPro import MfluxControlNetLoader
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
# 节点类映射
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"QuickMfluxNode": QuickMfluxNode,
|
||||
"MfluxControlNetLoader": MfluxControlNetLoader, # 映射新节点
|
||||
}
|
||||
|
||||
# 节点显示名称映射
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"QuickMfluxNode": "Quick MFlux Generation",
|
||||
"MfluxControlNetLoader": "Mflux ControlNet Loader", # 添加显示名称
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,309 @@
|
||||
{
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 4,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "LoadImage",
|
||||
"pos": {
|
||||
"0": 22,
|
||||
"1": 53
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 314
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"shape": 3,
|
||||
"label": "图像"
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3,
|
||||
"label": "遮罩"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"IMG_1281.jpeg",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "DownloadAndLoadFlorence2Model",
|
||||
"pos": {
|
||||
"0": 25,
|
||||
"1": 422
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "lora",
|
||||
"type": "PEFTLORA",
|
||||
"link": null,
|
||||
"label": "LoRA"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "florence2_model",
|
||||
"type": "FL2MODEL",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3,
|
||||
"label": "Florence2"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DownloadAndLoadFlorence2Model"
|
||||
},
|
||||
"widgets_values": [
|
||||
"MiaoshouAI/Florence-2-large-PromptGen-v1.5",
|
||||
"fp16",
|
||||
"sdpa"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Florence2Run",
|
||||
"pos": {
|
||||
"0": 365,
|
||||
"1": 58
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 352
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 2,
|
||||
"label": "图像"
|
||||
},
|
||||
{
|
||||
"name": "florence2_model",
|
||||
"type": "FL2MODEL",
|
||||
"link": 1,
|
||||
"label": "Florence2"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3,
|
||||
"label": "图像"
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3,
|
||||
"label": "遮罩"
|
||||
},
|
||||
{
|
||||
"name": "caption",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 2,
|
||||
"shape": 3,
|
||||
"label": "caption"
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"type": "JSON",
|
||||
"links": null,
|
||||
"shape": 3,
|
||||
"label": "json数据"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Florence2Run"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"more_detailed_caption",
|
||||
true,
|
||||
false,
|
||||
1024,
|
||||
3,
|
||||
true,
|
||||
"",
|
||||
945771744623101,
|
||||
"randomize",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "QuickMfluxNode",
|
||||
"pos": {
|
||||
"0": 791,
|
||||
"1": 60
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 336
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "lora_files",
|
||||
"type": "LIST",
|
||||
"link": null,
|
||||
"label": "lora_files"
|
||||
},
|
||||
{
|
||||
"name": "ControlNet",
|
||||
"type": "MfluxControlNetPipeline",
|
||||
"link": null,
|
||||
"label": "ControlNet"
|
||||
},
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 3,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
},
|
||||
"label": "prompt"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3,
|
||||
"label": "IMAGE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "QuickMfluxNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Luxury food photograph",
|
||||
"schnell",
|
||||
"flux.1-schnell-mflux-4bit",
|
||||
682107544996648,
|
||||
"randomize",
|
||||
512,
|
||||
512,
|
||||
2,
|
||||
3.5,
|
||||
"false",
|
||||
"",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 1220,
|
||||
"1": 61
|
||||
},
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 4,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
"FL2MODEL"
|
||||
],
|
||||
[
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
3,
|
||||
2,
|
||||
2,
|
||||
4,
|
||||
2,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.1167815779424797,
|
||||
"offset": [
|
||||
14.59059871397305,
|
||||
25.485045382690416
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"last_node_id": 12,
|
||||
"last_link_id": 15,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 9,
|
||||
"type": "MfluxControlNetLoader",
|
||||
"pos": {
|
||||
"0": 26,
|
||||
"1": 52
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 426
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "ControlNet",
|
||||
"type": "MfluxControlNetPipeline",
|
||||
"links": [
|
||||
11
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3,
|
||||
"label": "ControlNet"
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
12
|
||||
],
|
||||
"slot_index": 1,
|
||||
"shape": 3,
|
||||
"label": "width"
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"slot_index": 2,
|
||||
"shape": 3,
|
||||
"label": "height"
|
||||
},
|
||||
{
|
||||
"name": "canny_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"slot_index": 3,
|
||||
"shape": 3,
|
||||
"label": "canny_image"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "MfluxControlNetLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"图像.jpeg",
|
||||
"InstantX/FLUX.1-dev-Controlnet-Canny",
|
||||
0.4,
|
||||
"false",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 127,
|
||||
"1": 535
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 14,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "QuickMfluxNode",
|
||||
"pos": {
|
||||
"0": 370,
|
||||
"1": 56
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 337
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "lora_files",
|
||||
"type": "LIST",
|
||||
"link": null,
|
||||
"label": "lora_files"
|
||||
},
|
||||
{
|
||||
"name": "ControlNet",
|
||||
"type": "MfluxControlNetPipeline",
|
||||
"link": 11,
|
||||
"label": "ControlNet"
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"link": 12,
|
||||
"widget": {
|
||||
"name": "width"
|
||||
},
|
||||
"label": "width"
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"link": 13,
|
||||
"widget": {
|
||||
"name": "height"
|
||||
},
|
||||
"label": "height"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3,
|
||||
"label": "IMAGE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "QuickMfluxNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"1girl, ",
|
||||
"dev",
|
||||
"flux.1-dev-mflux-4bit",
|
||||
1031674300637981,
|
||||
"randomize",
|
||||
512,
|
||||
512,
|
||||
20,
|
||||
3.5,
|
||||
"false",
|
||||
"",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 803,
|
||||
"1": 56
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 15,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
11,
|
||||
9,
|
||||
0,
|
||||
10,
|
||||
1,
|
||||
"MfluxControlNetPipeline"
|
||||
],
|
||||
[
|
||||
12,
|
||||
9,
|
||||
1,
|
||||
10,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
13,
|
||||
9,
|
||||
2,
|
||||
10,
|
||||
3,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
14,
|
||||
9,
|
||||
3,
|
||||
11,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
15,
|
||||
10,
|
||||
0,
|
||||
12,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.9229599817706443,
|
||||
"offset": [
|
||||
47.86433519924988,
|
||||
59.566368698020256
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"last_node_id": 33,
|
||||
"last_link_id": 22,
|
||||
"last_node_id": 2,
|
||||
"last_link_id": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 32,
|
||||
"id": 1,
|
||||
"type": "QuickMfluxNode",
|
||||
"pos": {
|
||||
"0": 37,
|
||||
"1": 62
|
||||
"0": 24,
|
||||
"1": 56
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 316
|
||||
"1": 336
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
@@ -22,6 +22,12 @@
|
||||
"type": "LIST",
|
||||
"link": null,
|
||||
"label": "lora_files"
|
||||
},
|
||||
{
|
||||
"name": "ControlNet",
|
||||
"type": "MfluxControlNetPipeline",
|
||||
"link": null,
|
||||
"label": "ControlNet"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -29,7 +35,7 @@
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
22
|
||||
1
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3,
|
||||
@@ -43,10 +49,10 @@
|
||||
"Luxury food photograph",
|
||||
"schnell",
|
||||
"flux.1-schnell-mflux-4bit",
|
||||
732227569682063,
|
||||
161812310247565,
|
||||
"randomize",
|
||||
1024,
|
||||
1024,
|
||||
512,
|
||||
512,
|
||||
2,
|
||||
3.5,
|
||||
"false",
|
||||
@@ -55,11 +61,11 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"id": 2,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 471,
|
||||
"1": 61
|
||||
"0": 449,
|
||||
"1": 60
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
@@ -72,7 +78,7 @@
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 22,
|
||||
"link": 1,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
@@ -85,10 +91,10 @@
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
22,
|
||||
32,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
33,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
@@ -97,10 +103,10 @@
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"scale": 1.2284597357367277,
|
||||
"offset": [
|
||||
63,
|
||||
39
|
||||
25.90558087874367,
|
||||
28.985363606324412
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"last_node_id": 3,
|
||||
"last_link_id": 2,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "QuickMfluxNode",
|
||||
"pos": {
|
||||
"0": 463,
|
||||
"1": 61
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 336
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "lora_files",
|
||||
"type": "LIST",
|
||||
"link": null,
|
||||
"label": "lora_files"
|
||||
},
|
||||
{
|
||||
"name": "ControlNet",
|
||||
"type": "MfluxControlNetPipeline",
|
||||
"link": null,
|
||||
"label": "ControlNet"
|
||||
},
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 1,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
},
|
||||
"label": "prompt"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3,
|
||||
"label": "IMAGE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "QuickMfluxNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Luxury food photograph",
|
||||
"schnell",
|
||||
"flux.1-schnell-mflux-4bit",
|
||||
656585466532514,
|
||||
"randomize",
|
||||
512,
|
||||
512,
|
||||
2,
|
||||
3.5,
|
||||
"false",
|
||||
"",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 901,
|
||||
"1": 60
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 2,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "ArgosTranslateTextNode",
|
||||
"pos": {
|
||||
"0": 27,
|
||||
"1": 57
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3,
|
||||
"label": "文本"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ArgosTranslateTextNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"chinese",
|
||||
"english",
|
||||
"明月松间照,清泉石上流。",
|
||||
true
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.2284597357367277,
|
||||
"offset": [
|
||||
25.90558087874367,
|
||||
28.985363606324412
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
Reference in New Issue
Block a user