Python实现屏幕时间监控工具:从计时到AI语音提醒的完整开发指南 1. 项目概述与核心思路作为一个长期和代码打交道的开发者我深知长时间盯着屏幕对眼睛和身体的消耗有多大。有时候一坐就是几个小时回过神来才觉得眼睛干涩、脖子僵硬。市面上有不少屏幕时间管理软件但要么功能复杂要么提醒方式生硬用几次就关掉了。最近看到一个挺有意思的项目一个13岁的小开发者用Python做了一个“屏幕时间监察官”它的核心创意在于用带点幽默和“压迫感”的AI语音比如模拟“愤怒的亚洲妈妈”口吻和图片来提醒你该休息了而不是冷冰冰的弹窗。这个点子让我眼前一亮决定沿着这个思路结合我自己的开发经验从头到尾实现并优化一个更健壮、更实用的版本。这不仅仅是一个编程练习更是打造一个真正能帮到自己和同事的健康小工具。这个程序的核心目标很明确在后台安静运行从你开启电脑或启动程序时开始计时。随着连续使用电脑的时间累积程序会分阶段、逐步升级提醒的“强度”。初期可能是温和的语音提示后期则会加入图片、GIF甚至循环播放严厉的语音直到你决定休息并关闭程序或电脑。技术栈选择Python因为它拥有极其丰富的库能让我们轻松实现定时任务、音频播放和网页/图片展示。我们将主要用到time模块进行计时winsound模块Windows平台或跨平台的playsound、pygame库来播放警告音频以及webbrowser模块来打开本地或网络的图片/动图进行视觉提醒。整个项目的价值在于它用一种略带趣味性和强干预性的方式将健康用眼的理念转化为可执行、可感知的日常程序。2. 项目核心设计与架构解析2.1 功能逻辑与流程设计在动手写代码之前我们必须把程序的工作流程想清楚。一个可靠的屏幕时间监控程序其核心逻辑应该是一个清晰的状态机。参考原项目的思路并加以完善我设计了以下工作流程初始化与启动程序启动初始化计时器start_time time.time()并加载配置如总时长阈值、各阶段提醒的时间点、对应的音频/图片文件路径。后台循环监控程序进入一个主循环在后台持续运行。在循环中它不断计算自开始计时以来经过的时间elapsed_time time.time() - start_time。阶段判断与触发提醒将经过的时间与预设的多个时间阈值进行比较。例如30分钟触发第一阶段提醒播放一段温和的语音如“已经看屏幕30分钟啦起来活动一下脖子吧”。50分钟触发第二阶段提醒播放语气更紧迫的语音并弹出一张休息提示的图片。60分钟或用户设定的专注时长触发最终警告循环播放严厉的语音提醒并可能打开一个全屏或无法轻易关闭的警告页面直到用户主动中断程序。提醒执行根据判断结果调用相应的函数来执行“提醒”动作如播放wav音频文件、用默认浏览器打开一张提醒图片的URL或本地文件路径。循环与退出主循环以一定的间隔如每秒检查一次时间避免CPU占用过高。退出条件可以是用户手动关闭程序或者如果我们设计的话在触发最终警告后用户完成了一段休息时间程序重置计时。这个流程的关键在于“渐进式”和“非侵入式”到“强干预式”的过渡。一开始的提醒要友好避免引起反感后期的提醒则需要足够有力能打断用户当前的沉浸状态。2.2 技术选型与工具准备原项目提到了PyCharm和winsound这是一个很好的起点但为了程序的健壮性和跨平台潜力我们需要考虑更多。开发环境与IDE任何Python环境都可以。PyCharm、VS Code、甚至Jupyter Notebook用于原型验证都行。我个人习惯用VS Code轻量且插件丰富。关键是创建一个独立的虚拟环境venv方便管理依赖。# 在项目目录下创建虚拟环境 python -m venv venv # 激活虚拟环境 (Windows) venv\Scripts\activate # 激活虚拟环境 (macOS/Linux) source venv/bin/activate核心库选择计时与循环Python标准库中的time模块是唯一选择time.time()获取时间戳既简单又精准。音频播放winsound仅适用于Windows最简单但功能有限主要播放.wav。原项目使用它对于纯Windows环境是OK的。playsound一个跨平台的纯Python库可以播放MP3、WAV等格式API极其简单playsound(‘sound.mp3’)。这是实现跨平台兼容性的推荐选择。pygame功能强大的多媒体库可以更精细地控制音频如音量、循环播放但重量也更大。如果我们需要复杂的音频混合或循环播放警告pygame是更好的选择。视觉提醒webbrowserPython标准库webbrowser.open(‘file:///path/to/image.jpg’)可以用默认图片查看器打开本地图片webbrowser.open(‘https://…’)可以打开网络图片。它的优点是简单、依赖少。缺点是会弹出浏览器或图片查看器窗口可能会干扰工作。PIL (Pillow)tkinter/PyQt如果想创建自定义的、更美观的提醒窗口例如一个始终置顶的半透明弹窗则需要用到图形界面库。这复杂度会高很多但体验也更好。配置管理使用json或yaml文件来存储用户配置如提醒时间点、音频文件路径、总时长等这样不用修改代码就能调整行为。注意考虑到项目的初衷是简单、有趣且易于理解和修改我们将主要采用timeplaysound跨平台 webbrowser的方案。如果确定只在Windows上使用winsound也是一个轻量选项。素材准备AI语音与图片AI语音原项目作者使用了在线TTS服务。现在这类服务很多如国内的百度AI开放平台、阿里云、腾讯云都有免费的TTS额度国外有Google Cloud TTS、Microsoft Azure TTS等。我们可以用这些服务生成不同语气温和、提醒、警告的语音文件保存为mp3或wav格式。切记要遵守服务条款生成的内容需符合公序良俗。图片/GIF可以自己制作或寻找一些有趣的、表达“休息”、“眼睛疲劳”主题的图片或动图。例如一个旋转的“休息一下”的标语或者一个搞笑的、捂着眼睛的表情包。将这些素材放在项目目录的assets文件夹下。3. 分步实现与核心代码详解3.1 项目结构与配置定义首先我们来搭建一个清晰的项目结构。这能让代码更易维护。screen_time_inspector/ ├── config.json # 配置文件 ├── main.py # 主程序入口 ├── requirements.txt # 项目依赖 └── assets/ # 资源文件夹 ├── audio/ │ ├── reminder_30min.mp3 │ ├── warning_50min.mp3 │ └── final_warning_60min.mp3 └── images/ ├── take_a_break.png └── eye_exercise.gifconfig.json文件内容示例{ total_duration_minutes: 60, reminder_stages: [ { trigger_minute: 30, audio_file: assets/audio/reminder_30min.mp3, image_file: , message: 已经连续使用电脑30分钟建议远眺20秒。 }, { trigger_minute: 50, audio_file: assets/audio/warning_50min.mp3, image_file: assets/images/take_a_break.png, message: 您已使用50分钟请立刻起身活动做一下伸展运动。 }, { trigger_minute: 60, audio_file: assets/audio/final_warning_60min.mp3, image_file: assets/images/eye_exercise.gif, is_final: true, loop_audio: true, blocking: true } ], check_interval_seconds: 1 }这个配置文件定义了总时长、多个提醒阶段触发时间、对应的音频和图片、以及程序检查的时间间隔。is_final,loop_audio,blocking等字段用于控制最终警告的行为。3.2 核心计时与状态监控循环主程序main.py的核心是一个循环它不断检查经过的时间并触发相应事件。import time import json import os from pathlib import Path # 我们将使用playsound进行跨平台音频播放 try: from playsound import playsound USE_PLAYSOUND True except ImportError: # 如果playsound安装失败且是Windows则回退到winsound import sys if sys.platform win32: import winsound USE_PLAYSOUND False else: print(错误非Windows系统请安装 playsound 库。) sys.exit(1) import webbrowser class ScreenTimeInspector: def __init__(self, config_pathconfig.json): self.load_config(config_path) self.start_time time.time() self.triggered_stages set() # 记录已触发过的阶段避免重复触发 self.is_running True self.final_warning_active False def load_config(self, config_path): 加载配置文件 with open(config_path, r, encodingutf-8) as f: self.config json.load(f) # 将分钟转换为秒便于内部计算 self.total_duration self.config[total_duration_minutes] * 60 self.check_interval self.config[check_interval_seconds] for stage in self.config[reminder_stages]: stage[trigger_second] stage[trigger_minute] * 60 def play_audio(self, audio_path): 播放音频文件处理跨平台兼容性 if not os.path.exists(audio_path): print(f警告音频文件不存在 {audio_path}) return try: if USE_PLAYSOUND: playsound(audio_path) else: # Windows winsound 只支持 .wav if audio_path.endswith(.wav): winsound.PlaySound(audio_path, winsound.SND_FILENAME) else: print(f警告winsound不支持播放 {audio_path}请转换为.wav格式或安装playsound。) except Exception as e: print(f播放音频时出错: {e}) def show_image(self, image_path): 使用默认程序打开图片或GIF if not os.path.exists(image_path): print(f警告图片文件不存在 {image_path}) return # 使用 file:// 协议打开本地文件 webbrowser.open(ffile://{os.path.abspath(image_path)}) def execute_reminder_stage(self, stage): 执行单个提醒阶段的所有动作 print(f\n[提醒] {stage[message]}) if stage.get(audio_file): self.play_audio(stage[audio_file]) if stage.get(image_file): self.show_image(stage[image_file]) # 如果是最终警告阶段启动一个阻塞循环 if stage.get(is_final, False): self.final_warning_active True print(!!! 最终警告已激活 !!! 请立即离开电脑休息。) if stage.get(loop_audio, False) and stage.get(audio_file): # 注意这里循环播放会阻塞主线程。更优解是使用线程但为简化我们先这样处理。 # 在实际中可以考虑用 pygame.mixer 实现后台循环播放。 while self.final_warning_active: self.play_audio(stage[audio_file]) time.sleep(5) # 每5秒重复一次警告语音 def check_and_trigger(self): 检查时间并触发相应的提醒阶段 current_time time.time() elapsed_seconds current_time - self.start_time # 检查是否超过总时长触发最终阶段如果配置了 if elapsed_seconds self.total_duration: final_stages [s for s in self.config[reminder_stages] if s.get(is_final)] for final_stage in final_stages: if final_stage[trigger_second] not in self.triggered_stages: self.execute_reminder_stage(final_stage) self.triggered_stages.add(final_stage[trigger_second]) return # 检查每个提醒阶段 for stage in self.config[reminder_stages]: trigger_sec stage[trigger_second] # 如果当前时间超过该阶段触发点且该阶段未被触发过 if elapsed_seconds trigger_sec and trigger_sec not in self.triggered_stages: self.execute_reminder_stage(stage) self.triggered_stages.add(trigger_sec) def run(self): 主运行循环 print(f屏幕时间监察官已启动。总时长设定为 {self.config[total_duration_minutes]} 分钟。) print(程序将在后台运行按 CtrlC 可终止。) try: while self.is_running and not self.final_warning_active: self.check_and_trigger() # 休眠指定间隔降低CPU占用 time.sleep(self.check_interval) except KeyboardInterrupt: print(\n程序被用户中断。) finally: print(屏幕时间监察官已退出。) if __name__ __main__: inspector ScreenTimeInspector() inspector.run()这段代码构建了一个完整的监控程序骨架。ScreenTimeInspector类封装了所有功能加载配置、计时、播放音频、展示图片以及主循环。使用类的方式使得代码结构清晰未来也容易扩展例如添加GUI、网络同步等功能。3.3 音频与视觉提醒的实现细节音频和视觉提醒是用户体验的核心。上面代码中已经实现了基本功能但还有一些细节可以优化。音频播放的优化异步播放playsound和winsound.PlaySound默认都是阻塞的即播放完毕前程序会卡住。对于较长的提醒音频这会导致主循环暂停。我们可以使用线程来异步播放。import threading def play_audio_async(audio_path): def _play(): playsound(audio_path) thread threading.Thread(target_play) thread.daemon True # 设置为守护线程主程序退出时自动结束 thread.start()音量与格式playsound不支持调节音量。如果需要更精细的控制pygame.mixer是更好的选择它可以设置音量、循环播放并且也是跨平台的。视觉提醒的优化使用webbrowser.open的问题它会打开系统默认的图片查看器或浏览器。如果用户正在全屏工作这个新窗口可能会被盖住起不到提醒作用。更醒目的提醒方案使用tkinter创建简单弹窗虽然会引入GUI依赖但可以创建始终置顶topmost的窗口。import tkinter as tk def show_popup(image_path): root tk.Tk() root.attributes(-topmost, True) # 置顶 root.overrideredirect(True) # 无边框窗口 # 加载并显示图片 (需要PIL/Pillow) from PIL import Image, ImageTk img Image.open(image_path) photo ImageTk.PhotoImage(img) label tk.Label(root, imagephoto) label.pack() # 10秒后自动关闭 root.after(10000, root.destroy) root.mainloop()使用系统通知对于macOS (osascript)、Linux (notify-send) 和 Windows (win10toast库)可以发送原生系统通知更轻量且不干扰当前窗口。实操心得在初期原型验证时用webbrowser是最快的。但如果你想做一个真正“讨人嫌”、无法忽视的提醒学习一点tkinter来做个置顶小弹窗是非常值得的。或者结合系统通知和音频也是一种平衡的方案。4. 功能增强与个性化定制基础版本已经能工作但我们可以让它更智能、更贴心。4.1 实现可配置化与用户交互目前的配置是写死在json文件里的。我们可以做一个简单的命令行接口CLI或配置文件生成器让用户无需编辑JSON就能设置。# cli_config.py - 一个简单的命令行配置工具 import json import sys def create_config(): print( 屏幕时间监察官配置向导 ) total_min int(input(请输入您希望的总专注时长分钟: )) stages [] add_stage True while add_stage: print(f\n--- 添加第 {len(stages)1} 个提醒阶段 ---) trigger int(input( 在多少分钟时触发: )) audio input( 音频文件路径留空则无: ).strip() image input( 图片/GIF文件路径留空则无: ).strip() message input( 提醒消息: ) is_final input( 是否为最终警告阶段(y/N): ).lower() y stage { trigger_minute: trigger, audio_file: audio if audio else , image_file: image if image else , message: message, is_final: is_final } if is_final: stage[loop_audio] input( 是否循环播放警告音频(y/N): ).lower() y stages.append(stage) add_stage input(是否继续添加提醒阶段(y/N): ).lower() y config { total_duration_minutes: total_min, reminder_stages: stages, check_interval_seconds: 1 } with open(my_config.json, w, encodingutf-8) as f: json.dump(config, f, indent2, ensure_asciiFalse) print(配置已保存至 my_config.json。) if __name__ __main__: create_config()这样用户运行python cli_config.py就可以通过问答方式生成自己的配置文件。4.2 休息计时与自动重置一个完整的健康周期应该是“工作 - 提醒 - 休息 - 重置”。我们可以在最终警告触发后不仅循环提醒还启动一个“强制休息”计时器。# 在 ScreenTimeInspector 类中添加 def start_break_timer(self, break_minutes5): 启动休息计时器 print(f\n[系统] 进入强制休息时间时长 {break_minutes} 分钟。) break_end time.time() break_minutes * 60 while time.time() break_end: remaining int(break_end - time.time()) mins, secs divmod(remaining, 60) # 可以在这里显示一个倒计时或者播放舒缓的音乐 time.sleep(1) print([系统] 休息时间结束计时器将重置。) self.reset_timer() def reset_timer(self): 重置所有计时和状态 self.start_time time.time() self.triggered_stages.clear() self.final_warning_active False print(计时器已重置。)然后在execute_reminder_stage中如果遇到最终阶段除了循环警告还可以调用start_break_timer。4.3 系统托盘与后台静默运行对于需要长时间后台运行的程序一个系统托盘图标是更好的选择它让程序存在感更低也方便用户随时暂停、退出或查看状态。我们可以使用pystray库跨平台来实现。# 这是一个高级功能示例需要安装 pystray 和 PIL # pip install pystray Pillow import pystray from PIL import Image, ImageDraw import threading def create_tray_icon(inspector): 创建系统托盘图标和菜单 # 创建一个简单的图标 image Image.new(RGB, (64, 64), colorwhite) draw ImageDraw.Draw(image) draw.ellipse([16, 16, 48, 48], fillred, outlineblack) draw.text((24, 24), ST, fillwhite) # ST for Screen Time # 定义菜单项 menu ( pystray.MenuItem(f已运行: {int(time.time()-inspector.start_time)//60}分, lambda: None, enabledFalse), pystray.MenuItem(暂停监控, lambda: toggle_pause(inspector)), pystray.MenuItem(立即重置, lambda: inspector.reset_timer()), pystray.MenuItem(退出, lambda: stop_program(inspector, icon)), ) icon pystray.Icon(screen_time_inspector, image, 屏幕时间监察官, menu) return icon def toggle_pause(inspector): inspector.is_running not inspector.is_running status 已暂停 if not inspector.is_running else 已恢复 print(f[系统] {status}) def stop_program(inspector, icon): inspector.is_running False icon.stop() print(程序正在退出...) # 在主程序中需要将主循环放在一个线程中运行以免阻塞系统托盘 if __name__ __main__: inspector ScreenTimeInspector() # 启动监控循环线程 monitor_thread threading.Thread(targetinspector.run) monitor_thread.daemon True monitor_thread.start() # 启动系统托盘 icon create_tray_icon(inspector) icon.run()这样程序启动后会在系统托盘区显示一个图标右键点击可以查看状态、暂停、重置或退出非常优雅。5. 常见问题排查与优化建议在实际开发和运行中你可能会遇到以下问题。这里我整理了一份排查清单和优化建议。5.1 音频播放失败问题程序运行无错误但没有声音。排查检查文件路径确保config.json中的音频文件路径正确。使用绝对路径或相对于主程序main.py的路径。打印出程序尝试加载的完整路径进行检查。检查文件格式如果使用winsound它只支持.wav格式。确保你的音频文件是未压缩的PCM WAV格式。playsound支持mp3和wav但需要系统有对应的解码器。检查音量与设备确认系统音量未静音且输出设备选择正确。检查库安装确保playsound已正确安装 (pip install playsound)。在Windows上它依赖系统媒体播放器在macOS/Linux上它可能依赖gstreamer等。解决方案统一使用.wav格式以确保最大兼容性。可以使用在线转换工具或pydub库进行转换。尝试使用pygame.mixer作为备选方案它通常更可靠。import pygame pygame.mixer.init() pygame.mixer.music.load(‘audio.wav’) pygame.mixer.music.play()5.2 图片/GIF无法打开或显示异常问题webbrowser.open没有反应或打开了错误的程序。排查检查文件路径和权限同音频文件。检查默认程序webbrowser.open使用系统默认关联程序打开文件。确保图片格式如.png,.jpg,.gif有默认的查看器。URL格式打开本地文件时路径需要是file://开头的URL。os.path.abspath可以获取绝对路径。解决方案使用subprocess调用特定命令打开图片可能更可控但牺牲跨平台性。import subprocess, os, sys if sys.platform darwin: # macOS subprocess.call([open, image_path]) elif sys.platform win32: # Windows os.startfile(image_path) else: # Linux subprocess.call([xdg-open, image_path])如前所述考虑使用tkinter或系统通知来展示提醒避免依赖外部程序。5.3 程序CPU占用过高或过低问题程序导致电脑风扇狂转或者提醒严重延迟。原因与解决主循环休眠间隔 (check_interval_seconds)设置得太小如0.01秒会导致循环执行过于频繁消耗CPU。通常设置为0.5到2秒之间是完全足够的对提醒精度几乎没有影响。阻塞操作如果音频播放是阻塞的且音频很长主循环会卡住。务必使用异步播放如线程。最终警告循环如果最终警告阶段是while循环播放音频且没有休眠会占用大量CPU。在循环内添加time.sleep()。5.4 如何实现开机自启动这是一个常见的需求让程序在用户登录后自动运行。Windows创建程序的快捷方式。按Win R输入shell:startup打开启动文件夹。将快捷方式拖入此文件夹。macOS系统设置 - 通用 - 登录项。点击“”号添加你的Python脚本或打包后的应用。Linux (GNOME)在/etc/xdg/autostart/或用户目录的~/.config/autostart/下创建一个.desktop文件。文件内容示例[Desktop Entry] TypeApplication NameScreen Time Inspector Execpython3 /path/to/your/main.py Hiddenfalse NoDisplayfalse X-GNOME-Autostart-enabledtrue重要提醒实现开机自启动前请确保你的程序已经过充分测试不会导致系统启动错误或冲突。最好提供一个清晰的选项让用户选择是否启用自启动。5.5 提升提醒的“有效性”与“趣味性”原项目的“愤怒的亚洲妈妈”创意是趣味性的核心。我们可以把这个点子发扬光大语音内容库不要只用一两条语音。建立一个语音库每次触发时随机选择一条增加新鲜感。内容可以从温和劝导到幽默吐槽再到“严厉警告”。视觉素材库同样建立一个图片/GIF库随机展示。可以是可爱的动物、励志名言、搞笑的梗图或者简单的护眼科普图。互动式休息在强制休息期间弹窗可以显示一些简单的、能在座位上完成的伸展运动动画或指导鼓励用户真正动起来。数据统计记录每天的使用数据和休息次数每周生成一个简单的报告让用户看到自己的进步。这个项目的魅力在于它介于工具和玩具之间。通过Python我们不仅实现了一个健康辅助工具更探索了如何用代码与人进行有趣、有效的交互。从简单的计时循环到音频播放、图形界面、系统集成每一步都涉及不同的编程知识点是一个非常好的综合练习。希望你在实现它的过程中既能保护好自己的眼睛也能享受到编程的乐趣。