初步版本

This commit is contained in:
2026-03-15 01:05:43 +08:00
Unverified
parent bfb68e1fd1
commit 21944b4621

234
main.py Normal file
View File

@@ -0,0 +1,234 @@
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()