⚡
FLUXをComfyUIのAPIからPythonで使用する方法
はじめに
ComfyUIは強力な画像生成ツールであり、FLUXモデルはその中でも特に注目される新しいモデルです。この記事では、Pythonスクリプトを使用してComfyUI FLUXモデルをAPIで呼び出し、画像を生成する方法を解説します。
必要なライブラリのインストール
まず、必要なライブラリをインストールします。以下のコマンドを実行してください:
pip install rich pyfiglet
Pythonスクリプトの作成
以下のPythonスクリプトを作成します。このスクリプトは、ComfyUIのFLUXモデルを使用して画像を生成します。
import json
from urllib import request
import random
import os
from typing import Dict, Any, Optional
from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.syntax import Syntax
from rich import print as rprint
from art import text2art
from pyfiglet import Figlet
from rich.text import Text
class ComfyUIPromptGeneratorFlux:
"""ComfyUIのプロンプトを生成し、サーバーにキューイングするクラス"""
def __init__(self, server_address: str = "127.0.0.1:8188", workflow_path: Optional[str] = None):
"""
コンストラクタ: クラスの初期化を行います
:param server_address: ComfyUIサーバーのアドレス(デフォルトは localhost の 8188 ポート)
:param workflow_path: ワークフローJSONファイルへのパス(デフォルトはNone)
"""
self.console = Console(width=120)
self.server_address = server_address
if workflow_path is None:
self.workflow_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'workflow_api.json')
else:
self.workflow_path = workflow_path
self.prompt = self._load_workflow()
def _load_workflow(self) -> Dict[str, Any]:
"""ワークフローJSONファイルを読み込むプライベートメソッド"""
try:
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=self.console
) as progress:
task = progress.add_task("[cyan]Loading workflow...", total=None)
with open(self.workflow_path, 'r') as file:
workflow = json.load(file)
progress.update(task, completed=True)
self.console.print(Panel(f"[green]Workflow loaded successfully from {self.workflow_path}[/green]"))
return workflow
except FileNotFoundError:
self.console.print(Panel(f"[red]Workflow file not found: {self.workflow_path}[/red]", title="Error"))
raise
except json.JSONDecodeError:
self.console.print(Panel(f"[red]Invalid JSON in workflow file: {self.workflow_path}[/red]", title="Error"))
raise ValueError(f"Invalid JSON in workflow file: {self.workflow_path}")
def set_clip_text(self, text: str, node_id: str = "283") -> None:
"""CLIPTextEncodeノードのテキストを設定するメソッド"""
self.prompt[node_id]["inputs"]["text"] = text
self.console.print(f"[yellow]CLIP text set to:[/yellow] {text}")
def set_random_seed(self, node_id: str = "271") -> None:
"""KSamplerノードのシードをランダムに設定するメソッド"""
seed = random.randint(1, 1_000_000)
self.prompt[node_id]["inputs"]["noise_seed"] = seed
self.console.print(f"[yellow]Random seed set to:[/yellow] {seed}")
def queue_prompt(self) -> None:
"""プロンプトをComfyUIサーバーにキューイングするメソッド"""
data = json.dumps({"prompt": self.prompt}).encode('utf-8')
req = request.Request(f"http://{self.server_address}/prompt", data=data, headers={'Content-Type': 'application/json'})
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=self.console
) as progress:
task = progress.add_task("[cyan]Queueing prompt...", total=None)
try:
request.urlopen(req)
progress.update(task, completed=True)
self.console.print(Panel("[green]Prompt queued successfully[/green]"))
except Exception as e:
progress.update(task, completed=True)
self.console.print(Panel(f"[red]Failed to queue prompt: {str(e)}[/red]", title="Error"))
raise ConnectionError(f"Failed to queue prompt: {str(e)}")
def generate_and_queue(self, clip_text: str) -> None:
"""プロンプトを生成し、キューイングするメソッド"""
self.console.rule("[bold blue]Generating and Queueing Prompt")
self.set_clip_text(text=clip_text, node_id="6")
self.set_random_seed(node_id="25")
self.queue_prompt()
self.console.rule("[bold blue]Process Completed")
def display_prompt(self) -> None:
"""現在のプロンプトを表示するメソッド"""
prompt_json = json.dumps(self.prompt, indent=2)
syntax = Syntax(prompt_json, "json", theme="monokai", line_numbers=True)
self.console.print(Panel(syntax, title="Current Prompt", expand=True))
if __name__ == "__main__":
console = Console(width=220)
# Figletを使用してASCIIアートを生成
f = Figlet(font='slant', width=200)
title = f.renderText('ComfyUI Prompt Generator')
# ASCIIアートをRichのTextオブジェクトに変換し、色を付ける
title_text = Text.from_ansi(title)
title_text.stylize("bold magenta")
# パネル内にASCIIアートを表示
console.print(Panel(title_text, expand=False, border_style="bold blue"))
console.print(Panel("[bold cyan]ComfyUI Prompt Generator Demo[/bold cyan]"))
console.rule("[bold green]Using Custom Workflow")
# カスタムワークフローパスを指定
custom_workflow_path = r"workflow\workflow_api_flux.json"
custom_generator = ComfyUIPromptGeneratorFlux(workflow_path=custom_workflow_path)
custom_generator.generate_and_queue("Photorealistic landscape")
custom_generator.display_prompt()
スクリプトの実行
スクリプトを実行すると、以下のような出力が得られます:
(pygico-imagegen-py3.12) C:\Prj\PyGiCo>python example\python_usage_flux_simple.py
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ______ ____ __ ______ ____ __ ______ __ │
│ / ____/___ ____ ___ / __/_ __/ / / / _/ / __ \_________ ____ ___ ____ / /_ / ____/__ ____ ___ _________ _/ /_____ _____ │
│ / / / __ \/ __ `__ \/ /_/ / / / / / // / / /_/ / ___/ __ \/ __ `__ \/ __ \/ __/ / / __/ _ \/ __ \/ _ \/ ___/ __ `/ __/ __ \/ ___/ │
│ / /___/ /_/ / / / / / / __/ /_/ / /_/ // / / ____/ / / /_/ / / / / / / /_/ / /_ / /_/ / __/ / / / __/ / / /_/ / /_/ /_/ / / │
│ \____/\____/_/ /_/ /_/_/ \__, /\____/___/ /_/ /_/ \____/_/ /_/ /_/ .___/\__/ \____/\___/_/ /_/\___/_/ \__,_/\__/\____/_/ │
│ /____/ /_/ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ComfyUI Prompt Generator Demo
│
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
────────────────────────────────────────────────────────────────────────────────────────────────── Using Custom Workflow ───────────────────────────────────────────────────────────────────────────────────────────────────
⠋ Loading workflow...
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Workflow loaded successfully from workflow\workflow_api_flux.json │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
──────────────────────────────────────────── Generating and Queueing Prompt ────────────────────────────────────────────
CLIP text set to: Photorealistic landscape
Random seed set to: 737586
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Prompt queued successfully │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
⠋ Queueing prompt...
────────────────────────────────────────────────── Process Completed ───────────────────────────────────────────────────
╭─────────────────────────────────────────────────── Current Prompt ───────────────────────────────────────────────────╮
│ 1 { │
│ 2 "5": { │
│ 3 "inputs": { │
│ 4 "width": 1920, │
│ 5 "height": 1024, │
│ 6 "batch_size": 1 │
│ 7 }, │
│ 8 "class_type": "EmptyLatentImage", │
│ 9 "_meta": { │
│ 10 "title": "Empty Latent Image" │
│ 11 } │
│ 12 }, │
│ 13 "6": { │
│ 14 "inputs": { │
│ 15 "text": "Photorealistic landscape", │
│ 16 "clip": [ │
│ 17 "11", │
│ 18 0 │
│ 19 ] │
│ 20 }, │
│ 21 "class_type": "CLIPTextEncode", │
│ 22 "_meta": { │
│ 23 "title": "CLIP Text Encode (Prompt)" │
│ 24 } │
│ 25 }, │
│ 26 "8": { │
│ 27 "inputs": { │
│ 28 "samples": [ │
│ 29 "13", │
│ 30 0 │
│ 31 ], │
│ 32 "vae": [ │
│ 33 "10", │
│ 34 0 │
│ 35 ] │
│ 36 }, │
│ 37 "class_type": "VAEDecode", │
│ 38 "_meta": { │
│ 39 "title": "VAE Decode" │
│ 40 } │
│ 41 }, │
│ 42 "9": { │
│ 43 "inputs": { │
│ 44 "filename_prefix": "ComfyUI", │
│ 45 "images": [ │
│ 46 "8", │
│ 47 0 │
│ 48 ] │
│ 49 }, │
│ 50 "class_type": "SaveImage", │
│ 51 "_meta": { │
│ 52 "title": "Save Image" │
│ 53 } │
│ 54 }, │
│ 55 "10": { │
│ 56 "inputs": { │
│ 57 "vae_name": "ae.sft" │
│ 58 }, │
│ 59 "class_type": "VAELoader", │
│ 60 "_meta": { │
│ 61 "title": "Load VAE" │
│ 62 } │
│ 63 }, │
│ 64 "11": { │
│ 65 "inputs": { │
│ 66 "clip_name1": "t5xxl_fp16.safetensors", │
│ 67 "clip_name2": "clip_l_flux.safetensors", │
│ 68 "type": "flux" │
│ 69 }, │
│ 70 "class_type": "DualCLIPLoader", │
│ 71 "_meta": { │
│ 72 "title": "DualCLIPLoader" │
│ 73 } │
│ 74 }, │
│ 75 "12": { │
│ 76 "inputs": { │
│ 77 "unet_name": "flux1-dev.sft", │
│ 78 "weight_dtype": "default" │
│ 79 }, │
│ 80 "class_type": "UNETLoader", │
│ 81 "_meta": { │
│ 82 "title": "Load Diffusion Model" │
│ 83 } │
│ 84 }, │
│ 85 "13": { │
│ 86 "inputs": { │
│ 87 "noise": [ │
│ 88 "25", │
│ 89 0 │
│ 90 ], │
│ 91 "guider": [ │
│ 92 "22", │
│ 93 0 │
│ 94 ], │
│ 95 "sampler": [ │
│ 96 "16", │
│ 97 0 │
│ 98 ], │
│ 99 "sigmas": [ │
│ 100 "17", │
│ 101 0 │
│ 102 ], │
│ 103 "latent_image": [ │
│ 104 "5", │
│ 105 0 │
│ 106 ] │
│ 107 }, │
│ 108 "class_type": "SamplerCustomAdvanced", │
│ 109 "_meta": { │
│ 110 "title": "SamplerCustomAdvanced" │
│ 111 } │
│ 112 }, │
│ 113 "16": { │
│ 114 "inputs": { │
│ 115 "sampler_name": "euler" │
│ 116 }, │
│ 117 "class_type": "KSamplerSelect", │
│ 118 "_meta": { │
│ 119 "title": "KSamplerSelect" │
│ 120 } │
│ 121 }, │
│ 122 "17": { │
│ 123 "inputs": { │
│ 124 "scheduler": "simple", │
│ 125 "steps": 30, │
│ 126 "denoise": 1, │
│ 127 "model": [ │
│ 128 "12", │
│ 124 "scheduler": "simple", │
│ 125 "steps": 30, │
│ 126 "denoise": 1, │
│ 127 "model": [ │
│ 128 "12", │
│ 125 "steps": 30, │
│ 126 "denoise": 1, │
│ 127 "model": [ │
│ 128 "12", │
│ 126 "denoise": 1, │
│ 127 "model": [ │
│ 128 "12", │
│ 129 0 │
│ 127 "model": [ │
│ 128 "12", │
│ 129 0 │
│ 129 0 │
│ 130 ] │
│ 131 }, │
│ 130 ] │
│ 131 }, │
│ 131 }, │
│ 132 "class_type": "BasicScheduler", │
│ 132 "class_type": "BasicScheduler", │
│ 133 "_meta": { │
│ 133 "_meta": { │
│ 134 "title": "BasicScheduler" │
│ 135 } │
│ 134 "title": "BasicScheduler" │
│ 135 } │
│ 135 } │
│ 136 }, │
│ 137 "22": { │
│ 137 "22": { │
│ 138 "inputs": { │
│ 139 "model": [ │
│ 140 "12", │
│ 139 "model": [ │
│ 140 "12", │
│ 141 0 │
│ 142 ], │
│ 140 "12", │
│ 141 0 │
│ 142 ], │
│ 141 0 │
│ 142 ], │
│ 142 ], │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 149 "_meta": { │
│ 150 "title": "BasicGuider" │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 149 "_meta": { │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 143 "conditioning": [ │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 143 "conditioning": [ │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 143 "conditioning": [ │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 149 "_meta": { │
│ 150 "title": "BasicGuider" │
│ 151 } │
│ 152 }, │
│ 153 "25": { │
│ 154 "inputs": { │
│ 155 "noise_seed": 1112723341199311, │
│ 156 "seed": 737586 │
│ 157 }, │
│ 158 "class_type": "RandomNoise", │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 149 "_meta": { │
│ 150 "title": "BasicGuider" │
│ 151 } │
│ 152 }, │
│ 153 "25": { │
│ 154 "inputs": { │
│ 155 "noise_seed": 1112723341199311, │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 149 "_meta": { │
│ 150 "title": "BasicGuider" │
│ 151 } │
│ 152 }, │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 149 "_meta": { │
│ 150 "title": "BasicGuider" │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 143 "conditioning": [ │
│ 143 "conditioning": [ │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 143 "conditioning": [ │
│ 143 "conditioning": [ │
│ 143 "conditioning": [ │
│ 144 "6", │
│ 145 0 │
│ 146 ] │
│ 147 }, │
│ 148 "class_type": "BasicGuider", │
│ 149 "_meta": { │
│ 150 "title": "BasicGuider" │
│ 151 } │
│ 152 }, │
│ 153 "25": { │
│ 154 "inputs": { │
│ 155 "noise_seed": 1112723341199311, │
│ 156 "seed": 737586 │
│ 157 }, │
│ 158 "class_type": "RandomNoise", │
│ 159 "_meta": { │
│ 160 "title": "RandomNoise" │
│ 161 } │
│ 162 } │
│ 163 } │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
(pygico-imagegen-py3.12) C:\Prj\PyGiCo>
結果の解説
このスクリプトは以下の操作を行います:
- カスタムワークフローを読み込みます(
workflow\workflow_api_flux.json
)。 - "Photorealistic landscape"というプロンプトを設定します。
- ランダムなシード値を生成します(この例では737586)。
- プロンプトをComfyUIサーバーにキューイングします。
注意点
- このスクリプトを実行する前に、ComfyUIサーバーが起動していることを確認してください。
-
workflow_api_flux.json
ファイルが正しいパスに配置されていることを確認してください。 - FLUXモデルが正しくComfyUIにインストールされていることを確認してください。
まとめ
このPythonスクリプトを使用することで、ComfyUIのFLUXモデルをプログラマティックに制御し、高品質な画像を生成することができます。プロンプトやシード値を変更することで、さまざまな画像を生成することが可能です。
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
リポジトリ
Discussion