ここまでの記事では、ComfyUIとAPIを利用してACE-StepをPythonから制御する方法を段階的に紹介してきました。
本記事では、その内容を一度整理し、全体像としてPythonコードをまとめます。
ComfyUIでACE-StepをAPI用JSONをエクスポート
ComfyUIは、ワークフローをHTTP API経由で実行できるノードベースの生成AIツールです。
ACE-Stepはテキストから音楽を生成するオープンソースモデルで、ComfyUI公式ドキュメントでも音楽生成チュートリアルが提供されています。
API利用時は、UIで作成したワークフローを「API形式JSON」としてエクスポートし、それをそのままサーバーに送信します。
音楽生成ジョブをAPIで投入する
API操作の起点は POST /prompt です。Pythonなど外部プログラムから、ACE-StepワークフローJSONを送信すると、ComfyUIは生成ジョブをキューに登録し、prompt_id を返します。
このIDが、以降の進捗確認や結果取得の鍵になります。
WebSocketで進捗を確認する
ACE-Stepでの音楽生成は数十秒から数分かかるため、進捗確認が重要です。
ComfyUIは WebSocket(/ws) を提供しており、生成中の進捗や実行状態をリアルタイムで受信できます。
PythonではWebSocketクライアントとTQDMを組み合わせることで、CLI上に進捗バーを表示できます。
生成された音楽ファイルを取得する
生成が完了したら GET /history/{prompt_id} を呼び出し、どのファイルが出力されたかを確認します。
ACE-Stepの音楽出力は audio として履歴に記録され、ファイル名や保存先サブフォルダを取得できます。その情報をもとに GET /view を使って実ファイルをダウンロードします。
サンプルコードまとめ
これまでの紹介記事をまとめ、実行可能な一つのコードとしてまとめたものを示します。
コードはChatGPTに生成してもらい、ComfyUI v0.9.1 + ACE-Step v1.2 + Python 3.10系で動作を確認していますが、将来のバージョンでは動作しない可能性があります。動作しない場合はAIに見せて、最新バージョンや自分の環境に合わせてもらうのも有効な手段です。
事前に必要パッケージをいれておきます。
pip install requests websocket-client tqdm
実行時には以下の点に注意してください。
- 同じフォルダに、ACE-StepワークフローをAPI向けにエクスポートしたJSONファイルを
audio-ace-step.jsonとして配置しておきます。 - ComfyUIサーバーはローカルで起動しておきます。ポート番号はデフォルトの8188を指定しています。
- URLを適宜書き換えればリモートでも動きます。リモートの場合は、ComfyUI起動時に –listen オプションを指定し外部アクセスを許可しておきます。
- 実行後は、
sample_generated_audio.wavに生成音声が保存されます。
import json
import time
import uuid
import requests
import websocket
from tqdm import tqdm
import os
COMFY_BASE = "http://127.0.0.1:8188"
COMFY_PROMPT = f"{COMFY_BASE}/prompt"
COMFY_HISTORY = f"{COMFY_BASE}/history"
COMFY_VIEW = f"{COMFY_BASE}/view"
WS_URL = "ws://127.0.0.1:8188/ws"
WORKFLOW_JSON = "audio_ace_step.json"
OUTPUT_FILE = "sample_generated_audio.wav"
def load_and_patch(json_path: str, prompt_text: str, duration_s: int = 60):
wf = json.load(open(json_path, "r", encoding="utf-8"))
for node in wf.values():
if not isinstance(node, dict):
continue
cls = node.get("class_type", "")
inputs = node.get("inputs", {}) or {}
if cls == "TextEncodeAceStepAudio":
if "tags" in inputs:
inputs["tags"] = prompt_text
inputs["lyrics"] = "[inst]"
if cls == "EmptyAceStepLatentAudio":
if "seconds" in inputs:
inputs["seconds"] = duration_s
if cls == "KSampler" and "seed" in inputs:
inputs["seed"] = int(uuid.uuid4().int & (2**32 - 1))
return wf
def submit_workflow(wf: dict, client_id: str) -> str:
r = requests.post(COMFY_PROMPT, json={"prompt": wf, "client_id": client_id}, timeout=30)
r.raise_for_status()
j = r.json()
pid = j.get("prompt_id")
if not pid:
raise RuntimeError("no prompt_id returned")
return pid
def wait_and_show_progress(prompt_id: str, client_id: str, timeout: int = 1800):
ws_url = f"{WS_URL}?clientId={client_id}"
ws = websocket.create_connection(ws_url)
start = time.time()
bar = tqdm(total=100, desc="Comfy", unit="%")
last = -1
try:
while True:
if time.time() - start > timeout:
raise TimeoutError("WS timeout")
raw = ws.recv()
try:
data = json.loads(raw)
except Exception:
continue
mtype = data.get("type")
pdata = data.get("data") or {}
pid = pdata.get("prompt_id")
if pid != prompt_id:
continue
if mtype == "progress":
v = pdata.get("value")
maxv = pdata.get("max", 1)
try:
pct = int(v / maxv * 100)
except Exception:
pct = None
if pct is not None and pct != last:
delta = max(0, min(100, pct)) - bar.n
if delta > 0:
bar.update(delta)
last = pct
if mtype == "execution_error":
raise RuntimeError("execution_error from ComfyUI")
if (mtype == "execution_success") or (mtype == "executing" and pdata.get("node") is None):
if bar.n < 100:
bar.update(100 - bar.n)
bar.close()
return
finally:
try:
ws.close()
except Exception:
pass
def download_first_audio(prompt_id: str, out_path: str):
r = requests.get(f"{COMFY_HISTORY}/{prompt_id}", timeout=30)
r.raise_for_status()
hist = r.json()
entry = hist.get(prompt_id, {})
outputs = entry.get("outputs") or {}
type = "audio"
for out in outputs.values():
if not isinstance(out, dict):
continue
# ACEStep の出力は 'audio' に入る
if type in out and isinstance(out[type], list) and out[type]:
item = out[type][0]
fn = item.get("filename")
sub = item.get("subfolder", "")
if not fn:
continue
# type は履歴のエントリに従う(例: 'output')。デフォルトは 'output'
t = item.get("type", "output")
params = {"filename": fn, "type": t}
if sub:
params["subfolder"] = sub
vr = requests.get(COMFY_VIEW, params=params, timeout=60)
vr.raise_for_status()
with open(out_path, "wb") as f:
f.write(vr.content)
return out_path
raise RuntimeError("no audio found in history outputs")
PROMPT_TEXT = "instrumental, medieval fantasy, calm background" # サンプル
CLIENT_ID = str(uuid.uuid4())
def main():
wf = load_and_patch(WORKFLOW_JSON, PROMPT_TEXT, duration_s=60)
pid = submit_workflow(wf, CLIENT_ID)
print("submitted prompt_id:", pid)
wait_and_show_progress(pid, CLIENT_ID)
saved = download_first_audio(pid, OUTPUT_FILE)
print("saved to:", saved)
if __name__ == "__main__":
main()
まとめ
ComfyUI APIを使えば、ACE-Stepによる音楽生成をPythonから一貫して自動化できます。
/prompt で生成を開始し、WebSocketで進捗を確認し、**/history** と /view で結果を取得する、という流れを押さえることが重要です。
この構成を理解しておけば、バッチ生成や定期実行など、より実践的な自動音楽生成にも無理なく発展させられます。
また、ComfyUIが対応しているモデルであれば、同様のアプローチで動画や画像もAPIでアクセスできます。
今後はこのベースを使って、より高度な自動化やバッチ処理を試していきましょう。






