import time import tkinter as tk import keyboard import win32api import win32con import win32gui POTPLAYER_CLASSES = {"PotPlayer64", "PotPlayer"} APP_TITLE = "PotPlayer 同步器" WINDOW_SIZE = "420x240" PLAY_PAUSE_COOLDOWN = 0.25 WM_APPCOMMAND = 0x0319 APPCOMMAND_MEDIA_PLAY_PAUSE = 14 def safe_get_class_name(hwnd): try: return win32gui.GetClassName(hwnd) except win32gui.error: return "" def safe_get_window_text(hwnd): try: return win32gui.GetWindowText(hwnd) except win32gui.error: return "" def is_potplayer_window(hwnd): return safe_get_class_name(hwnd) in POTPLAYER_CLASSES def build_key_lparam(vk_code, is_keydown): scan_code = win32api.MapVirtualKey(vk_code, 0) is_extended = vk_code in { win32con.VK_LEFT, win32con.VK_RIGHT, win32con.VK_UP, win32con.VK_DOWN, win32con.VK_CONTROL, win32con.VK_RCONTROL, win32con.VK_MENU, win32con.VK_RMENU, } lparam = 1 | (scan_code << 16) | (int(is_extended) << 24) if not is_keydown: lparam |= (1 << 30) | (1 << 31) return lparam def get_potplayer_windows(): hwnds = [] def callback(hwnd, _extra): if win32gui.IsWindowVisible(hwnd) and is_potplayer_window(hwnd): hwnds.append(hwnd) return True win32gui.EnumWindows(callback, None) return hwnds class PotPlayerSyncApp: def __init__(self): self.root = tk.Tk() self.root.title(APP_TITLE) self.root.geometry(WINDOW_SIZE) self.root.resizable(False, False) self.root.attributes("-topmost", True) self.root.protocol("WM_DELETE_WINDOW", self.on_close) self.status_var = tk.StringVar(value="等待操作...") self.last_play_pause_at = 0.0 self.hotkey_handles = [] self.build_ui() self.register_hotkeys() self.refresh_initial_status() def build_ui(self): tk.Label( self.root, text="PotPlayer 联动控制台", font=("Microsoft YaHei", 13, "bold"), pady=10, ).pack() tips = ( "只在 PotPlayer 窗口或本工具窗口内操作时生效\n" "\n" "空格: 同步播放 / 暂停\n" "Left / Right: 同步快退 / 快进\n" "Shift + Left / Right: 同步微调进度\n" "Ctrl + Left / Right: 同步大幅调整进度" ) tk.Label( self.root, text=tips, font=("Microsoft YaHei", 10), justify="left", fg="#333333", padx=16, pady=4, ).pack(anchor="w") tk.Label( self.root, textvariable=self.status_var, font=("Microsoft YaHei", 10, "bold"), pady=10, fg="#0066CC", wraplength=380, justify="left", ).pack() def register_hotkeys(self): self.hotkey_handles.extend( [ keyboard.on_press_key("space", self.handle_space, suppress=False), keyboard.on_press_key("left", self.handle_left, suppress=False), keyboard.on_press_key("right", self.handle_right, suppress=False), ] ) def refresh_initial_status(self): count = len(get_potplayer_windows()) if count: self.update_status(f"已检测到 {count} 个 PotPlayer 窗口,等待操作...") else: self.update_status("暂未检测到 PotPlayer 窗口。") def update_status(self, message): self.root.after(0, lambda: self.status_var.set(message)) def is_app_window(self, hwnd): return safe_get_window_text(hwnd) == APP_TITLE def get_target_windows(self): foreground_hwnd = win32gui.GetForegroundWindow() in_potplayer = is_potplayer_window(foreground_hwnd) in_app = self.is_app_window(foreground_hwnd) if not (in_potplayer or in_app): return [], "请先聚焦 PotPlayer 窗口或同步器窗口。" hwnds = get_potplayer_windows() if not hwnds: return [], "未检测到可同步的 PotPlayer 窗口。" targets = [hwnd for hwnd in hwnds if hwnd != foreground_hwnd] if in_potplayer else hwnds if not targets: return [], "当前只有一个 PotPlayer 窗口,无需同步。" return targets, "" def post_play_pause(self, hwnd): lparam = APPCOMMAND_MEDIA_PLAY_PAUSE << 16 win32gui.PostMessage(hwnd, WM_APPCOMMAND, 0, lparam) def post_combo(self, hwnd, vk_codes): for vk_code in vk_codes: win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, vk_code, build_key_lparam(vk_code, True)) for vk_code in reversed(vk_codes): win32gui.PostMessage(hwnd, win32con.WM_KEYUP, vk_code, build_key_lparam(vk_code, False)) def sync_action(self, action_name, sender): target_hwnds, message = self.get_target_windows() if not target_hwnds: self.update_status(message) return for hwnd in target_hwnds: try: sender(hwnd) except win32gui.error: continue self.update_status(f"已同步: {action_name} -> {len(target_hwnds)} 个窗口") def handle_space(self, _event): now = time.monotonic() if now - self.last_play_pause_at < PLAY_PAUSE_COOLDOWN: return self.last_play_pause_at = now self.sync_action("空格(播放/暂停)", self.post_play_pause) def handle_left(self, _event): if keyboard.is_pressed("shift"): vk_codes = [win32con.VK_SHIFT, win32con.VK_LEFT] action_name = "Shift + Left(微调后退)" elif keyboard.is_pressed("ctrl"): vk_codes = [win32con.VK_CONTROL, win32con.VK_LEFT] action_name = "Ctrl + Left(大步后退)" else: vk_codes = [win32con.VK_LEFT] action_name = "Left(后退)" self.sync_action(action_name, lambda hwnd: self.post_combo(hwnd, vk_codes)) def handle_right(self, _event): if keyboard.is_pressed("shift"): vk_codes = [win32con.VK_SHIFT, win32con.VK_RIGHT] action_name = "Shift + Right(微调前进)" elif keyboard.is_pressed("ctrl"): vk_codes = [win32con.VK_CONTROL, win32con.VK_RIGHT] action_name = "Ctrl + Right(大步前进)" else: vk_codes = [win32con.VK_RIGHT] action_name = "Right(前进)" self.sync_action(action_name, lambda hwnd: self.post_combo(hwnd, vk_codes)) def on_close(self): for handle in self.hotkey_handles: keyboard.unhook(handle) self.root.destroy() def run(self): self.root.mainloop() def main(): app = PotPlayerSyncApp() app.run() if __name__ == "__main__": main()