Files

1 line
21 KiB
JSON

{"repo": "chaonenglin/ocrfanyi", "files": {"demo.py": {"d5847986f49382e9dae5e4960f6bea10d57fbf9f": {"t": 1785745406.4189174, "n": 6944, "h": "26ec09b4777b837d", "c": "\"\"\"Demo: One-shot region select → OCR → translate → display all at once.\n\nCtrl+Shift+R : Select a region. OCRs the whole thing, translates all\n detected texts in one API call, displays all results together.\nCtrl+Shift+T : Hide overlay.\nCtrl+C : Quit.\n\"\"\"\n\nimport os\nimport sys\nimport time\nimport asyncio\nimport ctypes\nimport ctypes.wintypes\nimport numpy as np\nfrom PIL import Image, ImageDraw, ImageFont\n\nfrom config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL, OCR_CONFIDENCE_THRESHOLD, BG_ALPHA, LANGUAGES, DEFAULT_SOURCE_LANG\nfrom src.capture.screen_capture import ScreenCapture\nfrom src.ocr.paddle_ocr import EasyOCREngine\nfrom src.overlay.selection_window import SelectionWindow\nfrom src.overlay.overlay_window import OverlayWindow\nfrom src.overlay.text_renderer import TextRenderer\n\nMOD_CONTROL = 0x0002\nMOD_SHIFT = 0x0004\nMOD_NOREPEAT = 0x4000\nVK_T = 0x54\nVK_R = 0x52\nWM_HOTKEY = 0x0312\nHOTKEY_ID = 1\nHOTKEY_ID_REGION = 2\n\nuser32 = ctypes.windll.user32\n\n\nclass MSG(ctypes.Structure):\n _fields_ = [\n (\"hwnd\", ctypes.c_void_p),\n (\"message\", ctypes.c_uint),\n (\"wParam\", ctypes.wintypes.WPARAM),\n (\"lParam\", ctypes.wintypes.LPARAM),\n (\"time\", ctypes.c_uint32),\n (\"pt_x\", ctypes.c_int32),\n (\"pt_y\", ctypes.c_int32),\n ]\n\n\nSOURCE_LANG = os.environ.get(\"OCR_SOURCE_LANG\", DEFAULT_SOURCE_LANG)\nLANG_INFO = LANGUAGES.get(SOURCE_LANG, LANGUAGES[DEFAULT_SOURCE_LANG])\n\nSYSTEM_PROMPT = f\"\"\"You are a {LANG_INFO['prompt_lang']}-to-Chinese translator. Translate each line to natural, fluent Simplified Chinese.\n- Input lines are separated by \"|||\". Output translations separated by \"|||\" in the exact same order.\n- Lines are in reading order (top-to-bottom, left-to-right). Neighboring lines may form a paragraph — translate them coherently, preserving paragraph flow.\n- Return ONLY the translations. No explanations, notes, or extra text.\n- Keep non-{LANG_INFO['prompt_lang']} text unchanged.\"\"\"\n\n\nasync def translate_batch(session, texts: list[str]) -> list[str]:\n if not texts:\n return []\n joined = \"|||\".join(texts)\n url = f\"{DEEPSEEK_BASE_URL.rstrip('/')}/chat/completions\"\n headers = {\n \"Authorization\": f\"Bearer {DEEPSEEK_API_KEY}\",\n \"Content-Type\": \"application/json\",\n }\n payload = {\n \"model\": DEEPSEEK_MODEL,\n \"messages\": [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": joined},\n ],\n \"temperature\": 0.0,\n \"max_tokens\": 2000,\n }\n async with session.post(url, json=payload, headers=headers) as resp:\n data = await resp.json()\n if \"choices\" not in data:\n raise RuntimeError(f\"DeepSeek API error: {data}\")\n content = data[\"choices\"][0][\"message\"][\"content\"].strip()\n translations = [t.strip() for t in content.split(\"|||\")]\n while len(translations) < len(texts):\n translations.append(texts[len(translations)])\n return translations[:len(texts)]\n\n\nasync def do_selection_and_translate(capture, ocr_engine, session, renderer, overlay):\n \"\"\"One selection cycle: select → OCR → translate → display.\"\"\"\n print(\"\\n[Demo] Select a region with your mouse...\")\n\n win = SelectionWindow(capture.width, capture.height)\n rect = win.run()\n win.destroy()\n\n if rect is None or rect[2] < 10 or rect[3] < 10:\n print(\"[Demo] Cancelled or too small\")\n return\n\n x, y, w, h = rect\n print(f\"[Demo] Region: ({x},{y}) {w}x{h}\")\n\n # Step 1: Capture region\n img = capture.capture_region(x, y, w, h)\n print(f\"[Demo] Captured {img.shape[1]}x{img.shape[0]}\")\n\n # Step 2: OCR the whole region at once\n t0 = time.time()\n results = ocr_engine.detect_and_recognize(img)\n ocr_ms = (time.time() - t0) * 1000\n print(f\"[Demo] OCR found {len(results)} texts in {ocr_ms:.0f}ms\")\n\n if not results:\n print(\"[Demo] No text found\")\n return\n\n # Step 3: Collect all texts\n entries = []\n texts_to_translate = []\n for (bx, by, bw, bh), text, conf in results:\n abs_bbox = (x + bx, y + by, bw, bh)\n entries.append((abs_bbox, text))\n texts_to_translate.append(text)\n print(f\" [{conf:.2f}] \\\"{text}\\\"\")\n\n # Step 4: One API call for ALL texts\n t0 = time.time()\n translations = await translate_batch(session, texts_to_translate)\n api_ms = (time.time() - t0) * 1000\n print(f\"[Demo] API returned {len(translations)} translations in {api_ms:.0f}ms\")\n\n # Step 5: Display all at once\n entry_list = []\n for (bbox, _), trans in zip(entries, translations):\n entry_list.append((bbox, trans))\n print(f\" → {trans}\")\n\n bitmap = renderer.render(entry_list)\n overlay.update(bitmap)\n overlay.show()\n print(f\"[Demo] Done — all {len(entry_list)} texts displayed together ({ocr_ms:.0f}ms OCR + {api_ms:.0f}ms API)\")\n\n\nasync def main_async():\n if not DEEPSEEK_API_KEY:\n print(\"ERROR: DEEPSEEK_API_KEY environment variable not set.\")\n sys.exit(1)\n\n print(\"=\" * 50)\n print(\" OCRfanyi Demo — One-shot region translate\")\n print(\" Ctrl+Shift+R : Select region & translate\")\n print(\" Ctrl+Shift+T : Hide overlay\")\n print(\" Ctrl+C : Quit\")\n print(\"=\" * 50)\n\n capture = ScreenCapture()\n ocr_engine = EasyOCREngine(lang_list=LANG_INFO[\"ocr_list\"])\n renderer = TextRenderer(capture.width, capture.height)\n overlay = OverlayWindow(capture.width, capture.height)\n\n import aiohttp\n async with aiohttp.ClientSession() as session:\n # Register hotkeys\n if user32.RegisterHotKey(None, HOTKEY_ID, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, VK_T):\n print(\"Hotkey registered: Ctrl+Shift+T (hide)\")\n if user32.RegisterHotKey(None, HOTKEY_ID_REGION, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, VK_R):\n print(\"Hotkey registered: Ctrl+Shift+R (select & translate)\")\n\n print(\"[Demo] Ready.\\n\")\n\n running = True\n msg = MSG()\n\n while running:\n result = user32.PeekMessageW(ctypes.byref(msg), None, 0, 0, 1)\n if result:\n if msg.message == WM_HOTKEY:\n if msg.wParam == HOTKEY_ID_REGION:\n await do_selection_and_translate(capture, ocr_engine, session, renderer, overlay)\n elif msg.wParam == HOTKEY_ID:\n overlay.hide()\n print(\"[Demo] Overlay hidden\")\n user32.TranslateMessage(ctypes.byref(msg))\n user32.DispatchMessageW(ctypes.byref(msg))\n else:\n await asyncio.sleep(0.03)\n\n user32.UnregisterHotKey(None, HOTKEY_ID)\n user32.UnregisterHotKey(None, HOTKEY_ID_REGION)\n overlay.destroy()\n print(\"Goodbye.\")\n\n\ndef main():\n try:\n asyncio.run(main_async())\n except KeyboardInterrupt:\n print(\"\\nShutting down...\")\n\n\nif __name__ == \"__main__\":\n main()\n"}, "73a8095a99576cfea957030c58d784d8c85fe0b4": {"t": 1785752877.2292233, "n": 6944, "h": "26ec09b4777b837d", "c": "\"\"\"Demo: One-shot region select → OCR → translate → display all at once.\n\nCtrl+Shift+R : Select a region. OCRs the whole thing, translates all\n detected texts in one API call, displays all results together.\nCtrl+Shift+T : Hide overlay.\nCtrl+C : Quit.\n\"\"\"\n\nimport os\nimport sys\nimport time\nimport asyncio\nimport ctypes\nimport ctypes.wintypes\nimport numpy as np\nfrom PIL import Image, ImageDraw, ImageFont\n\nfrom config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL, OCR_CONFIDENCE_THRESHOLD, BG_ALPHA, LANGUAGES, DEFAULT_SOURCE_LANG\nfrom src.capture.screen_capture import ScreenCapture\nfrom src.ocr.paddle_ocr import EasyOCREngine\nfrom src.overlay.selection_window import SelectionWindow\nfrom src.overlay.overlay_window import OverlayWindow\nfrom src.overlay.text_renderer import TextRenderer\n\nMOD_CONTROL = 0x0002\nMOD_SHIFT = 0x0004\nMOD_NOREPEAT = 0x4000\nVK_T = 0x54\nVK_R = 0x52\nWM_HOTKEY = 0x0312\nHOTKEY_ID = 1\nHOTKEY_ID_REGION = 2\n\nuser32 = ctypes.windll.user32\n\n\nclass MSG(ctypes.Structure):\n _fields_ = [\n (\"hwnd\", ctypes.c_void_p),\n (\"message\", ctypes.c_uint),\n (\"wParam\", ctypes.wintypes.WPARAM),\n (\"lParam\", ctypes.wintypes.LPARAM),\n (\"time\", ctypes.c_uint32),\n (\"pt_x\", ctypes.c_int32),\n (\"pt_y\", ctypes.c_int32),\n ]\n\n\nSOURCE_LANG = os.environ.get(\"OCR_SOURCE_LANG\", DEFAULT_SOURCE_LANG)\nLANG_INFO = LANGUAGES.get(SOURCE_LANG, LANGUAGES[DEFAULT_SOURCE_LANG])\n\nSYSTEM_PROMPT = f\"\"\"You are a {LANG_INFO['prompt_lang']}-to-Chinese translator. Translate each line to natural, fluent Simplified Chinese.\n- Input lines are separated by \"|||\". Output translations separated by \"|||\" in the exact same order.\n- Lines are in reading order (top-to-bottom, left-to-right). Neighboring lines may form a paragraph — translate them coherently, preserving paragraph flow.\n- Return ONLY the translations. No explanations, notes, or extra text.\n- Keep non-{LANG_INFO['prompt_lang']} text unchanged.\"\"\"\n\n\nasync def translate_batch(session, texts: list[str]) -> list[str]:\n if not texts:\n return []\n joined = \"|||\".join(texts)\n url = f\"{DEEPSEEK_BASE_URL.rstrip('/')}/chat/completions\"\n headers = {\n \"Authorization\": f\"Bearer {DEEPSEEK_API_KEY}\",\n \"Content-Type\": \"application/json\",\n }\n payload = {\n \"model\": DEEPSEEK_MODEL,\n \"messages\": [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": joined},\n ],\n \"temperature\": 0.0,\n \"max_tokens\": 2000,\n }\n async with session.post(url, json=payload, headers=headers) as resp:\n data = await resp.json()\n if \"choices\" not in data:\n raise RuntimeError(f\"DeepSeek API error: {data}\")\n content = data[\"choices\"][0][\"message\"][\"content\"].strip()\n translations = [t.strip() for t in content.split(\"|||\")]\n while len(translations) < len(texts):\n translations.append(texts[len(translations)])\n return translations[:len(texts)]\n\n\nasync def do_selection_and_translate(capture, ocr_engine, session, renderer, overlay):\n \"\"\"One selection cycle: select → OCR → translate → display.\"\"\"\n print(\"\\n[Demo] Select a region with your mouse...\")\n\n win = SelectionWindow(capture.width, capture.height)\n rect = win.run()\n win.destroy()\n\n if rect is None or rect[2] < 10 or rect[3] < 10:\n print(\"[Demo] Cancelled or too small\")\n return\n\n x, y, w, h = rect\n print(f\"[Demo] Region: ({x},{y}) {w}x{h}\")\n\n # Step 1: Capture region\n img = capture.capture_region(x, y, w, h)\n print(f\"[Demo] Captured {img.shape[1]}x{img.shape[0]}\")\n\n # Step 2: OCR the whole region at once\n t0 = time.time()\n results = ocr_engine.detect_and_recognize(img)\n ocr_ms = (time.time() - t0) * 1000\n print(f\"[Demo] OCR found {len(results)} texts in {ocr_ms:.0f}ms\")\n\n if not results:\n print(\"[Demo] No text found\")\n return\n\n # Step 3: Collect all texts\n entries = []\n texts_to_translate = []\n for (bx, by, bw, bh), text, conf in results:\n abs_bbox = (x + bx, y + by, bw, bh)\n entries.append((abs_bbox, text))\n texts_to_translate.append(text)\n print(f\" [{conf:.2f}] \\\"{text}\\\"\")\n\n # Step 4: One API call for ALL texts\n t0 = time.time()\n translations = await translate_batch(session, texts_to_translate)\n api_ms = (time.time() - t0) * 1000\n print(f\"[Demo] API returned {len(translations)} translations in {api_ms:.0f}ms\")\n\n # Step 5: Display all at once\n entry_list = []\n for (bbox, _), trans in zip(entries, translations):\n entry_list.append((bbox, trans))\n print(f\" → {trans}\")\n\n bitmap = renderer.render(entry_list)\n overlay.update(bitmap)\n overlay.show()\n print(f\"[Demo] Done — all {len(entry_list)} texts displayed together ({ocr_ms:.0f}ms OCR + {api_ms:.0f}ms API)\")\n\n\nasync def main_async():\n if not DEEPSEEK_API_KEY:\n print(\"ERROR: DEEPSEEK_API_KEY environment variable not set.\")\n sys.exit(1)\n\n print(\"=\" * 50)\n print(\" OCRfanyi Demo — One-shot region translate\")\n print(\" Ctrl+Shift+R : Select region & translate\")\n print(\" Ctrl+Shift+T : Hide overlay\")\n print(\" Ctrl+C : Quit\")\n print(\"=\" * 50)\n\n capture = ScreenCapture()\n ocr_engine = EasyOCREngine(lang_list=LANG_INFO[\"ocr_list\"])\n renderer = TextRenderer(capture.width, capture.height)\n overlay = OverlayWindow(capture.width, capture.height)\n\n import aiohttp\n async with aiohttp.ClientSession() as session:\n # Register hotkeys\n if user32.RegisterHotKey(None, HOTKEY_ID, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, VK_T):\n print(\"Hotkey registered: Ctrl+Shift+T (hide)\")\n if user32.RegisterHotKey(None, HOTKEY_ID_REGION, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, VK_R):\n print(\"Hotkey registered: Ctrl+Shift+R (select & translate)\")\n\n print(\"[Demo] Ready.\\n\")\n\n running = True\n msg = MSG()\n\n while running:\n result = user32.PeekMessageW(ctypes.byref(msg), None, 0, 0, 1)\n if result:\n if msg.message == WM_HOTKEY:\n if msg.wParam == HOTKEY_ID_REGION:\n await do_selection_and_translate(capture, ocr_engine, session, renderer, overlay)\n elif msg.wParam == HOTKEY_ID:\n overlay.hide()\n print(\"[Demo] Overlay hidden\")\n user32.TranslateMessage(ctypes.byref(msg))\n user32.DispatchMessageW(ctypes.byref(msg))\n else:\n await asyncio.sleep(0.03)\n\n user32.UnregisterHotKey(None, HOTKEY_ID)\n user32.UnregisterHotKey(None, HOTKEY_ID_REGION)\n overlay.destroy()\n print(\"Goodbye.\")\n\n\ndef main():\n try:\n asyncio.run(main_async())\n except KeyboardInterrupt:\n print(\"\\nShutting down...\")\n\n\nif __name__ == \"__main__\":\n main()\n"}, "2d93cc0aae57dc8a6e4c9074330d5e310ddd86b3": {"t": 1785752878.6005204, "n": 6627, "h": "8caacb9a6c2dfd48", "c": "\"\"\"Demo: One-shot region select → OCR → translate → display all at once.\n\nCtrl+Shift+R : Select a region. OCRs the whole thing, translates all\n detected texts in one API call, displays all results together.\nCtrl+Shift+T : Hide overlay.\nCtrl+C : Quit.\n\"\"\"\n\nimport sys\nimport time\nimport asyncio\nimport ctypes\nimport ctypes.wintypes\nimport numpy as np\nfrom PIL import Image, ImageDraw, ImageFont\n\nfrom config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL, OCR_CONFIDENCE_THRESHOLD, BG_ALPHA\nfrom src.capture.screen_capture import ScreenCapture\nfrom src.ocr.paddle_ocr import EasyOCREngine\nfrom src.overlay.selection_window import SelectionWindow\nfrom src.overlay.overlay_window import OverlayWindow\nfrom src.overlay.text_renderer import TextRenderer\n\nMOD_CONTROL = 0x0002\nMOD_SHIFT = 0x0004\nMOD_NOREPEAT = 0x4000\nVK_T = 0x54\nVK_R = 0x52\nWM_HOTKEY = 0x0312\nHOTKEY_ID = 1\nHOTKEY_ID_REGION = 2\n\nuser32 = ctypes.windll.user32\n\n\nclass MSG(ctypes.Structure):\n _fields_ = [\n (\"hwnd\", ctypes.c_void_p),\n (\"message\", ctypes.c_uint),\n (\"wParam\", ctypes.wintypes.WPARAM),\n (\"lParam\", ctypes.wintypes.LPARAM),\n (\"time\", ctypes.c_uint32),\n (\"pt_x\", ctypes.c_int32),\n (\"pt_y\", ctypes.c_int32),\n ]\n\n\nSYSTEM_PROMPT = \"\"\"You are a precise English-to-Chinese translator.\n- Translate each line of English text to Simplified Chinese.\n- Return ONLY the Chinese translation, one line per input line.\n- Input lines are separated by \"|||\". Output translations separated by \"|||\" in the exact same order.\n- Do NOT add explanations, notes, or any extra text.\n- If a line is not English or cannot be translated, keep it unchanged.\"\"\"\n\n\nasync def translate_batch(session, texts: list[str]) -> list[str]:\n if not texts:\n return []\n joined = \"|||\".join(texts)\n url = f\"{DEEPSEEK_BASE_URL.rstrip('/')}/chat/completions\"\n headers = {\n \"Authorization\": f\"Bearer {DEEPSEEK_API_KEY}\",\n \"Content-Type\": \"application/json\",\n }\n payload = {\n \"model\": DEEPSEEK_MODEL,\n \"messages\": [\n {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": joined},\n ],\n \"temperature\": 0.0,\n \"max_tokens\": 2000,\n }\n async with session.post(url, json=payload, headers=headers) as resp:\n data = await resp.json()\n if \"choices\" not in data:\n raise RuntimeError(f\"DeepSeek API error: {data}\")\n content = data[\"choices\"][0][\"message\"][\"content\"].strip()\n translations = [t.strip() for t in content.split(\"|||\")]\n while len(translations) < len(texts):\n translations.append(texts[len(translations)])\n return translations[:len(texts)]\n\n\nasync def do_selection_and_translate(capture, ocr_engine, session, renderer, overlay):\n \"\"\"One selection cycle: select → OCR → translate → display.\"\"\"\n print(\"\\n[Demo] Select a region with your mouse...\")\n\n win = SelectionWindow(capture.width, capture.height)\n rect = win.run()\n win.destroy()\n\n if rect is None or rect[2] < 10 or rect[3] < 10:\n print(\"[Demo] Cancelled or too small\")\n return\n\n x, y, w, h = rect\n print(f\"[Demo] Region: ({x},{y}) {w}x{h}\")\n\n # Step 1: Capture region\n img = capture.capture_region(x, y, w, h)\n print(f\"[Demo] Captured {img.shape[1]}x{img.shape[0]}\")\n\n # Step 2: OCR the whole region at once\n t0 = time.time()\n results = ocr_engine.detect_and_recognize(img)\n ocr_ms = (time.time() - t0) * 1000\n print(f\"[Demo] OCR found {len(results)} texts in {ocr_ms:.0f}ms\")\n\n if not results:\n print(\"[Demo] No text found\")\n return\n\n # Step 3: Collect all texts\n entries = []\n texts_to_translate = []\n for (bx, by, bw, bh), text, conf in results:\n abs_bbox = (x + bx, y + by, bw, bh)\n entries.append((abs_bbox, text))\n texts_to_translate.append(text)\n print(f\" [{conf:.2f}] \\\"{text}\\\"\")\n\n # Step 4: One API call for ALL texts\n t0 = time.time()\n translations = await translate_batch(session, texts_to_translate)\n api_ms = (time.time() - t0) * 1000\n print(f\"[Demo] API returned {len(translations)} translations in {api_ms:.0f}ms\")\n\n # Step 5: Display all at once\n entry_list = []\n for (bbox, _), trans in zip(entries, translations):\n entry_list.append((bbox, trans))\n print(f\" → {trans}\")\n\n bitmap = renderer.render(entry_list)\n overlay.update(bitmap)\n overlay.show()\n print(f\"[Demo] Done — all {len(entry_list)} texts displayed together ({ocr_ms:.0f}ms OCR + {api_ms:.0f}ms API)\")\n\n\nasync def main_async():\n if not DEEPSEEK_API_KEY:\n print(\"ERROR: DEEPSEEK_API_KEY environment variable not set.\")\n sys.exit(1)\n\n print(\"=\" * 50)\n print(\" OCRfanyi Demo — One-shot region translate\")\n print(\" Ctrl+Shift+R : Select region & translate\")\n print(\" Ctrl+Shift+T : Hide overlay\")\n print(\" Ctrl+C : Quit\")\n print(\"=\" * 50)\n\n capture = ScreenCapture()\n ocr_engine = EasyOCREngine()\n renderer = TextRenderer(capture.width, capture.height)\n overlay = OverlayWindow(capture.width, capture.height)\n\n import aiohttp\n async with aiohttp.ClientSession() as session:\n # Register hotkeys\n if user32.RegisterHotKey(None, HOTKEY_ID, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, VK_T):\n print(\"Hotkey registered: Ctrl+Shift+T (hide)\")\n if user32.RegisterHotKey(None, HOTKEY_ID_REGION, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, VK_R):\n print(\"Hotkey registered: Ctrl+Shift+R (select & translate)\")\n\n print(\"[Demo] Ready.\\n\")\n\n running = True\n msg = MSG()\n\n while running:\n result = user32.PeekMessageW(ctypes.byref(msg), None, 0, 0, 1)\n if result:\n if msg.message == WM_HOTKEY:\n if msg.wParam == HOTKEY_ID_REGION:\n await do_selection_and_translate(capture, ocr_engine, session, renderer, overlay)\n elif msg.wParam == HOTKEY_ID:\n overlay.hide()\n print(\"[Demo] Overlay hidden\")\n user32.TranslateMessage(ctypes.byref(msg))\n user32.DispatchMessageW(ctypes.byref(msg))\n else:\n await asyncio.sleep(0.03)\n\n user32.UnregisterHotKey(None, HOTKEY_ID)\n user32.UnregisterHotKey(None, HOTKEY_ID_REGION)\n overlay.destroy()\n print(\"Goodbye.\")\n\n\ndef main():\n try:\n asyncio.run(main_async())\n except KeyboardInterrupt:\n print(\"\\nShutting down...\")\n\n\nif __name__ == \"__main__\":\n main()\n"}}}}