import os import subprocess import time import atexit import sys import re import argparse import shlex import io import select # --- ์„ค์ • ๋ณ€์ˆ˜ --- COMFYUI_PORT = 8188 # ๐Ÿšจ ์‚ฌ์šฉ์ž ์ง€์ •: ๋ชจ๋“  .py ํŒŒ์ผ์„ ์ €์žฅํ•  ์ ˆ๋Œ€ ๊ฒฝ๋กœ # ์ด ๊ฒฝ๋กœ์— gradio-tunnel.py๊ฐ€ ์ €์žฅ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (Colab ์…€ ์‹คํ–‰ ์ „์— /content/py ํด๋”๊ฐ€ ์กด์žฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.) PYTHON_SCRIPT_DIR = "/content/py" # ํ„ฐ๋„ ์Šคํฌ๋ฆฝํŠธ์˜ ์ตœ์ข… ์ ˆ๋Œ€ ๊ฒฝ๋กœ TUNNEL_SCRIPT_PATH = os.path.join(PYTHON_SCRIPT_DIR, "gradio-tunnel.py") # ์˜ˆ: "/content/py/gradio-tunnel.py" # Gradio Tunnel Script ๋‚ด์šฉ (ํ„ฐ๋„๋ง ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š” Python ์ฝ”๋“œ) GRADIO_TUNNEL_SCRIPT_CONTENT = r""" import atexit import os import platform import re import stat import subprocess import sys import time import secrets from pathlib import Path from typing import List, Optional import requests VERSION = "0.2" CURRENT_TUNNELS: List["Tunnel"] = [] machine = platform.machine() if machine == "x86_64": machine = "amd64" BINARY_REMOTE_NAME = f"frpc_{platform.system().lower()}_{machine.lower()}" EXTENSION = ".exe" if os.name == "nt" else "" BINARY_URL = f"https://cdn-media.huggingface.co/frpc-gradio-{VERSION}/{BINARY_REMOTE_NAME}{EXTENSION}" BINARY_FILENAME = f"{BINARY_REMOTE_NAME}_v{VERSION}" BINARY_FOLDER = Path(__file__).parent.absolute() BINARY_PATH = f"{BINARY_FOLDER / BINARY_FILENAME}" TUNNEL_TIMEOUT_SECONDS = 30 TUNNEL_ERROR_MESSAGE = ( "Could not create share URL. " "Please check the appended log from frpc for more information:" ) GRADIO_API_SERVER = "https://api.gradio.app/v2/tunnel-request" GRADIO_SHARE_SERVER_ADDRESS = None class Tunnel: def __init__(self, remote_host, remote_port, local_host, local_port, share_token): self.proc = None self.url = None self.remote_host = remote_host self.remote_port = remote_port self.local_host = local_host self.local_port = local_port self.share_token = share_token @staticmethod def download_binary(): if not Path(BINARY_PATH).exists(): resp = requests.get(BINARY_URL) if resp.status_code == 403: raise OSError( f"Cannot set up a share link as this platform is incompatible. Please " f"create a GitHub issue with information about your platform: {platform.uname()}" ) resp.raise_for_status() # Save file data to local copy with open(BINARY_PATH, "wb") as file: file.write(resp.content) st = os.stat(BINARY_PATH) os.chmod(BINARY_PATH, st.st_mode | stat.S_IEXEC) def start_tunnel(self) -> str: self.download_binary() self.url = self._start_tunnel(BINARY_PATH) return self.url def kill(self): if self.proc is not None: print(f"ํ„ฐ๋„ ์ข…๋ฃŒ ์ค‘: {self.local_host}:{self.local_port} <> {self.url}") self.proc.terminate() self.proc = None def _start_tunnel(self, binary: str) -> str: CURRENT_TUNNELS.append(self) command = [ binary, "http", "-n", self.share_token, "-l", str(self.local_port), "-i", self.local_host, "--uc", "--sd", "random", "--ue", "--server_addr", f"{self.remote_host}:{self.remote_port}", "--disable_log_color", ] self.proc = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) atexit.register(self.kill) return self._read_url_from_tunnel_stream() def _read_url_from_tunnel_stream(self) -> str: start_timestamp = time.time() log = [] url = "" def _raise_tunnel_error(): log_text = "\\n".join(log) print(log_text, file=sys.stderr) raise ValueError(f"{TUNNEL_ERROR_MESSAGE}\\n{log_text}") while url == "": if time.time() - start_timestamp >= TUNNEL_TIMEOUT_SECONDS: _raise_tunnel_error() assert self.proc is not None if self.proc.stdout is None: continue line = self.proc.stdout.readline() try: line = line.decode("utf-8") except UnicodeDecodeError: continue if line == "": time.sleep(0.01) continue log.append(line.strip()) if "start proxy success" in line: result = re.search("start proxy success: (.+)\\n", line) if result is None: _raise_tunnel_error() else: url = result.group(1) elif "login to server failed" in line: _raise_tunnel_error() if self.proc.poll() is not None and url == "": _raise_tunnel_error() return url def setup_tunnel( local_host: str, local_port: int, share_token: str, share_server_address: Optional[str], ) -> str: share_server_address = ( GRADIO_SHARE_SERVER_ADDRESS if share_server_address is None else share_server_address ) if share_server_address is None: response = requests.get(GRADIO_API_SERVER) if not (response and response.status_code == 200): raise RuntimeError("Could not get share link from Gradio API Server.") payload = response.json()[0] remote_host, remote_port = payload["host"], int(payload["port"]) else: remote_host, remote_port = share_server_address.split(":") remote_port = int(remote_port) try: tunnel = Tunnel(remote_host, remote_port, local_host, local_port, share_token) address = tunnel.start_tunnel() return address except Exception as e: raise RuntimeError(str(e)) from e # ์ด ์Šคํฌ๋ฆฝํŠธ๋Š” ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ Gradio ํ„ฐ๋„๋ง์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. if __name__ == "__main__": if len(sys.argv) < 2: print("์‚ฌ์šฉ๋ฒ•: python gradio-tunnel.py ", file=sys.stderr) sys.exit(1) try: port = int(sys.argv[1]) except ValueError: print("์˜ค๋ฅ˜: ํฌํŠธ ๋ฒˆํ˜ธ๋Š” ์ •์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.", file=sys.stderr) sys.exit(1) # requests ์ž„ํฌํŠธ ํ™•์ธ (Colab ํ™˜๊ฒฝ์—์„œ ํ•„์š”) try: import requests except ImportError: # ์ด ์ฝ”๋“œ๋Š” runcomfy.py์—์„œ ์‹คํ–‰ํ•˜๋Š” subprocess์ด๋ฏ€๋กœ, # runcomfy.py์—์„œ requests ์„ค์น˜๋ฅผ ๋ณด์žฅํ•ด์•ผ ํ•จ. pass try: address = setup_tunnel( "127.0.0.1", port, secrets.token_urlsafe(32), None, ) print(address, flush=True) # ํ„ฐ๋„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ข…๋ฃŒ๋˜์ง€ ์•Š๋„๋ก ๋Œ€๊ธฐ time.sleep(3600 * 24 * 3) except Exception as e: print(f"ํ„ฐ๋„ ์‹คํ–‰ ์ค‘ ์น˜๋ช…์ ์ธ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", file=sys.stderr) sys.exit(1) """ # --- 1. main ํ•จ์ˆ˜ --- def main(): # requests ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. try: import requests except ImportError: print("ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ 'requests'๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์„ค์น˜๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.") try: # ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜๋ฅผ ์‹œ๋„ subprocess.check_call([sys.executable, "-m", "pip", "install", "requests"]) except Exception as e: print(f"๊ฒฝ๊ณ : 'requests' ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ์‹คํŒจ. ํ„ฐ๋„๋ง ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋ฅ˜: {e}") # runcomfy.py์— ์ „๋‹ฌ๋œ ๋ชจ๋“  ์ธ์ž(์Šคํฌ๋ฆฝํŠธ ์ด๋ฆ„ ์ œ์™ธ)๋ฅผ ComfyUI์— ์ „๋‹ฌํ•  ์ธ์ž๋กœ ์‚ฌ์šฉ comfyui_args = sys.argv[1:] # --- ํ•„์ˆ˜ ๊ฒฝ๋กœ ์„ค์ • ๋ฐ ์ด๋™ --- # WS ๋ณ€์ˆ˜๋Š” ์ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์™ธ๋ถ€ Colab ์…€์—์„œ ๊ฒฐ์ •๋˜๊ฑฐ๋‚˜, # ํ˜น์€ ์ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ DRIVE ๋ชจ๋“œ(/content/drive/MyDrive/ONECLICK) ์ „์šฉ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. comfyui_dir = "/content/ComfyUI" print(f"ComfyUI ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™ ์ค‘: {comfyui_dir}") try: os.chdir(comfyui_dir) except FileNotFoundError: print(f"์˜ค๋ฅ˜: ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: {comfyui_dir}. ๊ฒฝ๋กœ๋ฅผ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.") sys.exit(1) # ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ์ข…๋ฃŒ # ๐Ÿšจ ์ˆ˜์ •: ํ„ฐ๋„ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์„ ์ง€์ •๋œ ๊ฒฝ๋กœ์— ์ €์žฅ (ํด๋” ์ƒ์„ฑ ์—†์Œ) print(f"ํ„ฐ๋„ ์Šคํฌ๋ฆฝํŠธ ์ €์žฅ ์ค‘: {TUNNEL_SCRIPT_PATH}") try: # TUNNEL_SCRIPT_PATH์— ์ €์žฅ (์ ˆ๋Œ€ ๊ฒฝ๋กœ ์‚ฌ์šฉ) with open(TUNNEL_SCRIPT_PATH, "w") as f: f.write(GRADIO_TUNNEL_SCRIPT_CONTENT) except Exception as e: print(f"์˜ค๋ฅ˜: ํ„ฐ๋„ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ์ €์žฅ ์‹คํŒจ. {PYTHON_SCRIPT_DIR} ๊ฒฝ๋กœ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•ด ์ฃผ์„ธ์š”. ์˜ค๋ฅ˜: {e}") sys.exit(1) # --- 2. ComfyUI ์„œ๋ฒ„ ์‹คํ–‰ (๋ฐฑ๊ทธ๋ผ์šด๋“œ) --- print("\nComfyUI ์„œ๋ฒ„๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค...") comfyui_base_args = [ "./main.py", "--listen", "127.0.0.1", "--port", str(COMFYUI_PORT), ] comfyui_command = ["python"] + comfyui_base_args + comfyui_args comfyui_proc = subprocess.Popen( comfyui_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True ) atexit.register(comfyui_proc.terminate) # --- 3. Gradio ํ„ฐ๋„๋ง ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ (๋ฐฑ๊ทธ๋ผ์šด๋“œ) --- print(f"์„œ๋ฒ„๊ฐ€ ํฌํŠธ {COMFYUI_PORT}๋ฅผ ์—ด ๋•Œ๊นŒ์ง€ 15์ดˆ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค...") time.sleep(15) print(f"\nGradio ํ„ฐ๋„ ํด๋ผ์ด์–ธํŠธ ์‹œ์ž‘ ์ค‘ (ํฌํŠธ {COMFYUI_PORT})...") # ๐Ÿšจ ์ˆ˜์ •: TUNNEL_SCRIPT_PATH ๋ณ€์ˆ˜(์ ˆ๋Œ€ ๊ฒฝ๋กœ) ์‚ฌ์šฉ tunnel_command = ["python", TUNNEL_SCRIPT_PATH, str(COMFYUI_PORT)] tunnel_proc = subprocess.Popen( tunnel_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True ) atexit.register(tunnel_proc.terminate) # --- 4. ํ„ฐ๋„ ์ฃผ์†Œ ์ถœ๋ ฅ ๋ฐ ํ”„๋กœ์„ธ์Šค ์œ ์ง€ --- share_url = None start_time = time.time() print("\n[ํ„ฐ๋„ ์ถœ๋ ฅ - Gradio ๋กœ๊ทธ]") # ํ„ฐ๋„ URL์„ ์ฐพ๋Š” ๋ฃจํ”„ while time.time() - start_time < 60 and tunnel_proc.poll() is None: line = tunnel_proc.stdout.readline() if line: line_stripped = line.strip() print(f"[TUNNEL] {line_stripped}") # URL ํŒจํ„ด ์ฐพ๊ธฐ if line_stripped.startswith(("https://", "http://")): share_url = line_stripped break time.sleep(0.01) if share_url: print("\n" + "="*50) print(f"๐ŸŽ‰ **ComfyUI ๊ณต์œ  URL์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค:** {share_url}") print("์ด ์ฃผ์†Œ๋กœ ์ ‘์†ํ•˜์„ธ์š”.") print("="*50) else: # --- ํ„ฐ๋„๋ง ์‹คํŒจ ์‹œ ์˜ค๋ฅ˜ ์ง„๋‹จ ๋กœ์ง --- print("\n" + "="*50) print("๐Ÿšจ **ํ„ฐ๋„๋ง ํด๋ผ์ด์–ธํŠธ ์‹คํ–‰์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค!**") print("์•„๋ž˜ ๋กœ๊ทธ์—์„œ ์˜ค๋ฅ˜ ์›์ธ์„ ํ™•์ธํ•˜์„ธ์š”.") print("="*50) # 1. ํ„ฐ๋„ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹œ ๋‚จ์€ ๋กœ๊ทธ ํ™•์ธ ๋ฐ ์ถœ๋ ฅ print("\n--- ํ„ฐ๋„ ํด๋ผ์ด์–ธํŠธ ์ตœ์ข… ๋กœ๊ทธ ---") # ํ„ฐ๋„ stdout์˜ ์ฒ˜์Œ์œผ๋กœ ์ด๋™ (์ด๋ฏธ ์ฝ์€ ๋‚ด์šฉ ํฌํ•จ) try: tunnel_logs = tunnel_proc.stdout.read() if tunnel_logs: print(tunnel_logs.strip()) else: print("ํ„ฐ๋„ ๋กœ๊ทธ๊ฐ€ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ฆ‰์‹œ ์ข…๋ฃŒ๋œ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.") except Exception as e: print(f"ํ„ฐ๋„ ๋กœ๊ทธ๋ฅผ ์ฝ๋Š” ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}") # 2. ComfyUI ํ”„๋กœ์„ธ์Šค ๋กœ๊ทธ ํ™•์ธ print("\n--- ComfyUI ์ดˆ๊ธฐ ๋กœ๊ทธ ํ™•์ธ (ComfyUI๊ฐ€ ํฌํŠธ๋ฅผ ์—ด์—ˆ๋Š”์ง€ ํ™•์ธ) ---") try: if comfyui_proc.stdout is not None: # select๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„์ฐจ๋‹จ ๋ฐฉ์‹์œผ๋กœ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ์‹œ๋„ ready_to_read, _, _ = select.select([comfyui_proc.stdout], [], [], 0.5) if ready_to_read: comfy_logs = comfyui_proc.stdout.read() print(comfy_logs.strip() if comfy_logs else "ComfyUI ๋กœ๊ทธ ๋ฒ„ํผ์— ๋‚ด์šฉ ์—†์Œ.") else: print("ComfyUI ๋กœ๊ทธ ๋ฒ„ํผ์— ๋‚ด์šฉ ์—†์Œ (500ms ๋Œ€๊ธฐ ํ›„).") else: print("ComfyUI stdout์ด None์ž…๋‹ˆ๋‹ค.") except Exception as e: print(f"ComfyUI ๋กœ๊ทธ ์ฝ๊ธฐ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}") # ๋ชจ๋“  ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ํ›„ ์˜ค๋ฅ˜ ๋ฐœ์ƒ if comfyui_proc.poll() is None: comfyui_proc.terminate() if tunnel_proc.poll() is None: tunnel_proc.terminate() raise RuntimeError("Gradio ํ„ฐ๋„๋ง์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์ƒ์„ธ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ด ์ฃผ์‹ญ์‹œ์˜ค.") # ํ”„๋กœ์„ธ์Šค ์œ ์ง€๋ฅผ ์œ„ํ•œ ๋ฉ”์ธ ๋ฃจํ”„ (์ถฉ๋Œ ๋กœ๊ทธ ์ถœ๋ ฅ ๊ฐ•ํ™”) try: print("\n**ํ”„๋กœ์„ธ์Šค๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. Colab ์„ธ์…˜์ด ์—ฐ๊ฒฐ๋œ ๋™์•ˆ ํ„ฐ๋„์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.**") print("์ค‘์ง€ํ•˜๋ ค๋ฉด ์ด ์…€์˜ ์‹คํ–‰์„ ๋ฉˆ์ถ”์„ธ์š”.") while True: # ComfyUI ๋กœ๊ทธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‘ ์ถœ๋ ฅ comfy_line = comfyui_proc.stdout.readline() if comfy_line: print(comfy_line.strip()) if comfyui_proc.poll() is not None: print("\n[์˜ค๋ฅ˜] ComfyUI ํ”„๋กœ์„ธ์Šค๊ฐ€ ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ ์ค‘์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์„ธ์…˜์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.") # ์ข…๋ฃŒ ์‹œ ๋‚จ์•„์žˆ๋Š” ๋ชจ๋“  ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜์—ฌ ์›์ธ ํŒŒ์•… print("\n--- ComfyUI ์ถฉ๋Œ ๋กœ๊ทธ (์›์ธ ๋ถ„์„์šฉ) ---") print(comfyui_proc.stdout.read()) break if tunnel_proc.poll() is not None: print("\n[์˜ค๋ฅ˜] ํ„ฐ๋„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ ์ค‘์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์„ธ์…˜์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.") # ํ„ฐ๋„ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹œ ๋‚จ์€ ๋กœ๊ทธ ํ™•์ธ ๋ฐ ์ถœ๋ ฅ print("\n--- ํ„ฐ๋„ ์ถฉ๋Œ ๋กœ๊ทธ (์›์ธ ๋ถ„์„์šฉ) ---") print(tunnel_proc.stdout.read()) break time.sleep(0.01) except KeyboardInterrupt: print("\n์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ค‘์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค. (KeyboardInterrupt)") finally: print("\n๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค...") if comfyui_proc.poll() is None: comfyui_proc.terminate() if tunnel_proc.poll() is None: tunnel_proc.terminate() print("๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.") if __name__ == '__main__': main()