data / runcomfy.py
arcacolab's picture
Update runcomfy.py
b17fd97 verified
Raw
History Blame Contribute Delete
14.9 kB
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 <port_number>", 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()