"""应用路径:开发目录 vs PyInstaller 打包 exe。""" from __future__ import annotations import os import sys from pathlib import Path def is_frozen() -> bool: return bool(getattr(sys, "frozen", False)) def app_root() -> Path: """exe 所在目录 / 源码目录(可写:浏览器、输出 JSON)。""" if is_frozen(): return Path(sys.executable).resolve().parent return Path(__file__).resolve().parent def bundle_root() -> Path: """打包内资源目录(只读)。""" if is_frozen(): return Path(getattr(sys, "_MEIPASS", app_root())) return app_root() def ui_html_path() -> Path: for candidate in ( bundle_root() / "douyin_collector_ui.html", app_root() / "douyin_collector_ui.html", ): if candidate.is_file(): return candidate return app_root() / "douyin_collector_ui.html" def is_gui_mode() -> bool: """无黑窗口模式:pythonw / 无控制台 exe / COLLECTOR_GUI=1。""" if os.environ.get("DOUYIN_CONSOLE") == "1": return False if os.environ.get("COLLECTOR_GUI") == "1": return True if Path(sys.executable).name.lower() == "pythonw.exe": return True if is_frozen() and sys.platform == "win32": try: import ctypes return ctypes.windll.kernel32.GetConsoleWindow() == 0 except Exception: return True return False def splash_html_path() -> Path: for candidate in ( bundle_root() / "splash.html", app_root() / "splash.html", ): if candidate.is_file(): return candidate return app_root() / "splash.html" def launch_splash() -> None: """立即打开启动动画页(空白期反馈)。""" if os.environ.get("SPLASH_LAUNCHED") == "1": return splash = splash_html_path() if not splash.is_file(): return os.environ["SPLASH_LAUNCHED"] = "1" if sys.platform == "win32": try: import subprocess subprocess.Popen( ["cmd", "/c", "start", "", str(splash.resolve())], creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), close_fds=True, ) return except Exception: pass try: import webbrowser webbrowser.open(splash.resolve().as_uri()) except Exception: pass def use_incognito_ui() -> bool: """凭证页与登录页均在 Playwright 无痕窗口内打开(默认开启)。""" if os.environ.get("DOUYIN_CONSOLE") == "1": return False if os.environ.get("COLLECTOR_EXTERNAL_BROWSER") == "1": return False return True def browser_ready_file() -> Path: return app_root() / ".browser_ready" def clear_browser_ready() -> None: try: path = browser_ready_file() if path.is_file(): path.unlink() except Exception: pass def signal_browser_ready() -> None: try: browser_ready_file().write_text(str(int(__import__("time").time())), encoding="utf-8") except Exception: pass