
第一次在同事的 MacBook 上看到 macOS Sonoma 那套慢镜航拍屏保我盯着京都竹林那段看了差不多一分钟没说话——云在动光在变镜头推得极慢完全没有传统屏保那种图片轮播的廉价感。回到自己的 Windows 11 台式机前再看那个二十年前的三维文字落差是真的大。后来花了两个周末我把 Sonoma 的慢镜素材扒出来、转码、写成屏保前后踩了七八个坑现在开机闲置五分钟后屏幕上那片缓慢流动的海面已经稳定跑了小半年。这篇就是把这套流程从头到尾讲清楚素材在 Mac 上的确切位置、HEVC 10bit HDR 素材在 Windows 上为什么会翻车、怎么转码最省事、以及如果现成工具都不满意怎么自己手搓一个 .scr 文件。整套方案适合三类人手边有 Mac 或者能接触到 Mac 的、想把 Sonoma 那套慢镜航拍搬到 Windows 上的用户对 Windows 屏保机制好奇、想搞明白.scr到底是什么东西的技术爱好者以及单纯想给自己那块 4K 显示器找个像样点儿的闲置画面的普通用户。不需要你有编程基础但如果会一点命令行整个过程会顺畅很多。1. 先把素材搞到手Sonoma 慢镜屏保文件藏在哪1.1 idleassetsd 缓存目录与 entries.json 的关系macOS 从 Big Sur 开始动态壁纸和航拍屏保的素材就不再直接打包在系统镜像里了而是交给一个叫idleassetsd的守护进程按需下载。你第一次在系统设置 - 壁纸里点中某个航拍视频系统才会去苹果的 CDN 把这几个 G 的素材拉下来缓存到本地。在 macOS Sonoma 上缓存目录是/Library/Application Support/com.apple.idleassetsd/Customer/老版本系统Big Sur / Monterey的路径略有不同有一部分素材在/System/Library/Compositions/下面Sonoma 之后基本统一到上面这个Customer目录了。这个目录里有两类文件一堆名字长得像 UUID 的.mov视频文件以及一个entries.json清单。entries.json才是真正有价值的东西。它是一份标准 JSON每一项包含素材的显示名、分类Landscape / Cityscape / Underwater / Earth 等、时长、以及最关键的远程 URL 和本地文件名。你想知道某个 UUID 文件对应的是迪拜天际线还是加州海岸线靠肉眼是看不出来的必须去这个清单里查sudo cat /Library/Application Support/com.apple.idleassetsd/Customer/entries.json \ | python3 -m json.tool | head -100读这个文件需要sudo因为它在/Library下面而不是用户目录。我建议的做法是先把entries.json拷出来用 VS Code 或者任意 JSON 格式化工具打开建立一份UUID → 中文名称的对照表后面筛选素材的时候会省掉大量时间。1.2 为什么这些 .mov 是 HEVC 10bit HDR把文件拷到 Windows 上双击很多人遇到的第一个问题就是打不开或者只有声音没画面。这不是文件坏了而是编码格式的问题。Sonoma 这套航拍素材的规格大致是这样的容器是 QuickTime.mov视频流是HEVCH.265Main10 规格色彩空间是Rec.2020 PQ 传输曲线也就是常说的 HDR10分辨率 4K部分素材还带 Dolby Vision 的元数据。单文件体积普遍在几百 MB 到 1.5 GB 之间时长通常在两到四分钟循环播放。这里有两个坑叠在一起。第一Windows 11 系统自带的电影和电视播放器对 HEVC 的解码支持是需要额外安装扩展的而且默认那个扩展要收费。第二就算解码器装上了HDR 素材在 SDR 显示器上的映射如果不做处理画面会严重发灰、发暗对比度全丢看起来像蒙了一层雾。这两点决定了后面第二章的转码几乎是绕不过去的一步。顺便提一句这些航拍素材本身拍摄时就是高帧率升格拍摄、降速播放的所以慢镜是刻在素材里的不是播放器加的特效。这一点很重要——意味着你在 Windows 上原样播放那种缓慢流动的质感是能完整保留下来的。1.3 导出前的准备动作让 Mac 把素材下全很多人兴冲冲打开那个目录发现里面只有孤零零一两个文件以为素材就这么点。原因很简单没下载完。正确的做法是在 Mac 上先把想要的那几段都点一遍。打开系统设置 - 壁纸把侧边栏切到航拍分类挨个点选你感兴趣的素材每点一次系统就会开始下载这一条。下载是带进度条的4K HDR 的单条素材在百兆宽带上大概要十几秒到一分钟不等。全部点完之后再回到Customer目录ls -lh看一眼文件数量和体积对得上才算齐了。导出的时候有两个细节值得注意。一是别用 Finder 直接拖拽拷贝到 exFAT 移动硬盘上.mov文件本身没问题但资源分支resource fork可能会在拷贝过程中丢失虽然对视频播放影响不大但没必要给自己找麻烦直接用cp命令拷到 U 盘或者走网络传输更稳妥sudo cp -v /Library/Application Support/com.apple.idleassetsd/Customer/*.mov /Volumes/YOUR_USB/二是记着把entries.json一起带上。没有这份对照表你面对的是一堆A8867A5F-2C1E-4B7D-9E4F-3D5A6B7C8D9E.mov这样的文件名完全不知道哪段是哪段。打开视频一个个看当然也行但七八段视频看下来光认脸就得花十几分钟。提示只在你自己拥有或有权使用的设备上导出素材导出的文件仅供个人在本机自用。网上确实有热心人整理的打包版但版本新旧、完整性、有没有被二次压缩过都说不准优先用自己的设备导出画质和可控性都好得多。2. 转码这关绕不过去HEVC 与 HDR 怎么落到 SDR2.1 Windows 侧补齐 HEVC 解码能力的三条路在动手转码之前先解决能不能播的问题。HEVC 在 Windows 11 上的解码支持分三层你可以按需选择。第一条是微软商店的 HEVC 视频扩展。搜索HEVC 视频扩展和来自设备制造商的 HEVC 视频扩展这两个包后者在很多品牌整机和笔记本上是免费的前者一般要收几块钱。装完之后系统自带的电影和电视和照片应用就能播 HEVC 了。缺点是这条路只在系统级生效而且对 10bit HDR 的色调映射支持很一般。第二条是装一个自带解码器的播放器。VLC、mpv、PotPlayer 这一类播放器都内置了完整的 libavcodec根本不需要系统扩展。我个人最推荐 mpv因为它的 HDR 色调映射是这几家里做得最细的一条--tone-mappingbt.2390就能把 PQ 素材映射得相当自然。第三条是装 K-Lite Codec Pack 之类的编解码器合集。这条路能让系统里所有基于 DirectShow / Media Foundation 的软件都获得 HEVC 能力代价是装的东西多、可能有版本冲突。如果你只是想让屏保能播前两条就够了。我的实际选择是装最小化的 HEVC 扩展保证系统兼容性同时保留 mpv 作为主力播放器做素材预览。转码则完全依赖 FFmpeg不经过系统解码器。2.2 FFmpeg 无损封装 vs 重编码什么时候选哪个拿到.mov之后第一步要判断的是到底要不要重编码。如果你的目标是用 mpv 或者 VLC 播放那根本不用转码直接无损封装成 mp4 就行ffmpeg -i A8867A5F-xxxx.mov -c copy -tag:v hvc1 京都竹林.mp4-c copy表示视频流和音频流原封不动搬过去只换容器格式这个过程几秒钟就完事画质零损失。-tag:v hvc1这个参数是给 QuickTime 系软件看的标记成 hvc1 而不是 hev1兼容性更好播不出来的时候加上它往往就解决了。但如果你的目标是塞进一个通用的视频屏保程序情况就不一样了。很多老牌视频屏保工具底层用的是 Windows Media Foundation 或者更老的 DirectShow对 10bit HEVC 的支持时好时坏能播也未必能硬件加速4K 60fps 跑满会直接把风扇拉起来。这时候重编码成 H.264 8bit SDR 反而更省心画质损失在屏保这个使用场景下几乎看不出来——毕竟没人会凑到屏幕前面逐帧盯着屏保看。所以判断标准很简单播放器自己控制就用无损封装交给第三方程序就用重编码降级。这个话题后面第三章还会展开。2.3 我实测的 HDR 转 SDR 参数与耗时重编码最关键的不是编码本身而是从 PQ HDR 到 SDR 的色调映射。FFmpeg 里能做这件事的滤镜有几种我按效果从好到差排一下。效果最好的是libplacebo走 Vulkan 做 GPU 色调映射速度快、曲线准需要 FFmpeg 6.0 以上且编译时带了 libplaceboffmpeg -hwaccel auto -i input.mov \ -vf libplacebotonemappingbt.2390:colorspacebt709:color_primariesbt709:color_trcbt709:formatyuv420p \ -c:v hevc_nvenc -preset p5 -cq 20 -tag:v hvc1 -an output.mp4其次是zscale tonemap这条路纯 CPU慢一些但兼容性无敌任何 FFmpeg 构建都带ffmpeg -i input.mov \ -vf zscaletlinear:npl100,formatgbrpf32le,zscalepbt709,\ tonemaptonemaphable:desat0,zscaletbt709:mbt709:rtv,formatyuv420p \ -c:v libx265 -crf 21 -preset medium -tag:v hvc1 -an output.mp4注意这里的滤镜链顺序不能乱先把传输曲线线性化转到浮点 RGB再做色调映射算子最后回到 BT.709 的 SDR 空间。tonemap那个desat0参数控制高光去饱和程度设成 0 保留最多色彩但极端亮部可能会溢出觉得高光发白可以调到 0.5 到 2 之间试。最省事的做法是colorspace 快速近似纯色彩空间旋转不做真正的色调映射HDR 的高光信息会直接裁掉画面偏平但胜在快ffmpeg -i input.mov -vf colorspaceallbt709:iallbt2020:fast1,formatyuv420p \ -c:v libx265 -crf 21 -preset medium -tag:v hvc1 -an output.mp4实测耗时我记录了一下供参考。素材是一段 3 分 20 秒的 4K HDR 航拍源文件 1.2 GB方案硬件耗时输出体积主观画质无损封装任意约 8 秒1.2 GB原始libplacebo NVENCi5-12400 RTX 3060约 55 秒420 MB最好高光层次完整zscale tonemap libx265 mediumi5-12400 纯 CPU约 26 分钟310 MB好暗部略有噪点colorspace 快速近似 libx265 mediumi5-12400 纯 CPU约 18 分钟300 MB一般天空容易糊成一片结论很清楚有独显就走 libplacebo 硬件编码这条路一分钟不到出一段纯 CPU 环境下一段素材得等二十分钟建议睡前挂着跑。2.4 批量处理脚本与命名规范素材一般有七八段起步一条条手敲命令不现实。Windows 上我习惯用 cmd 的 for 循环因为 PowerShell 对滤镜字符串里的逗号和冒号有时候会自作聪明地做参数分割反而容易出错echo off setlocal enabledelayedexpansion set VFlibplacebotonemappingbt.2390:colorspacebt709:color_primariesbt709:color_trcbt709:formatyuv420p if not exist out mkdir out for %%f in (*.mov) do ( echo 正在处理 %%f ffmpeg -hide_banner -hwaccel auto -i %%f -vf %VF% ^ -c:v hevc_nvenc -preset p5 -cq 20 -tag:v hvc1 -an out\%%~nf.mp4 ) echo 全部完成 pause如果你坚持用 PowerShell把滤镜字符串放进单引号里传给 ffmpeg 通常没问题但保险起见我一般还是退回 cmd 跑批处理。命名规范这件事看起来是小事实际影响很大。UUID 文件名在屏保程序里根本没法管理建议转码时就自动重命名。我的做法是先按entries.json查出中文名再统一成序号-名称的格式比如01-迪拜天际线.mp4、02-加州海岸.mp4。序号是给播放顺序用的名称是给你自己看的。这样后面在屏保里想调整播放顺序改个文件名前缀就行不用去翻配置文件。注意转码时务必加-an参数去掉音轨。Sonoma 这套航拍素材基本是无声的但个别素材带了静音轨留着的话某些屏保程序会因为有音频流而走音频渲染路径结果就是黑屏加无声排查起来非常费劲。3. Windows 11 播视频当屏保三条路子横向对比3.1 现成第三方视频屏保工具最省事的路子是找一个现成的视频屏保程序。这类工具的工作原理都差不多吃一个视频文件夹空闲触发后全屏循环播放检测到鼠标或键盘就退出。挑工具的时候我建议盯住三个点。第一是解码后端基于 libmpv 或 LibVLC 的工具兼容性最好HEVC、VP9、AV1 基本都能吃基于 Windows Media Foundation 的就得多留个心眼老工具尤其容易在 4K HDR 上翻车。第二是多显示器支持很多老工具只会在主显示器上放视频副屏直接黑着这在双屏桌面上非常出戏。第三是配置界面的可用性屏保程序的配置对话框是在一个特殊的小窗口里跑的设计不好的工具在这里会直接卡死。我前后试过四五个最后留下来的是一个基于 libmpv 的开源方案。它的好处是配置文件就是普通的文本可以直接指定--speed参数想让画面比原素材再慢一点改一个数字就行。缺点是没有图形界面初次上手要看一眼 README。3.2 系统自带屏保加图片序列的取巧方案Windows 11 系统自带的屏保程序其实还剩几个比如照片屏保可以指定一个图片文件夹做轮播。有人会想那我是不是把视频抽帧成图片序列让它当成照片轮播这个思路理论上成立实际上不推荐。4K 屏保要看起来流畅至少得 24 帧每秒三分钟的视频就是四千多张 4K 图片占几十个 G 的硬盘空间而且照片屏保的切换是硬切没有淡入淡出你费劲抽出来的帧序列在屏幕上会像幻灯片一样一卡一卡地跳。用来验证思路可以长期用完全是折腾自己。还有一种更取巧的做法是截图当静态壁纸然后锁屏时用系统自带的气泡或者丝带屏保。这个方案唯一的优点是不折腾缺点是它和慢镜屏保这四个字基本没关系了。3.3 自己写一个 .scr控制力最强第三条路是自己写。听起来吓人但实际上 Windows 的屏保机制简单到令人发指——一个 .scr 文件本质上就是一个改了两个字母后缀的 exeWindows 按约定给它传几个命令行参数你的程序按约定响应就行。自己写的价值在于所有东西都可控想加淡入淡出就加想在角落里叠一行时间和地点文字就叠想让它按特定顺序播就用配置文件控制想让它在副屏单独播另一套素材也不是不行。而且没有第三方工具那些莫名其妙的兼容问题4K 60fps 想怎么跑就怎么跑。代价是要写代码、要打包、可能还要处理杀软误报。如果你只是想快速用上前三节里的现成工具足够了如果你和我一样有点强迫症想让它的表现尽可能贴近 macOS 原版那种质感那第四章值得一读。三条路子的横向对比维度现成第三方工具系统屏保图片序列自己写 .scr上手成本低几分钟中抽帧很麻烦高需要写代码解码兼容性取决于工具只支持图片完全可控多显示器部分支持不支持完全可控过渡效果看工具实现硬切自己实现后续维护跟随上游更新无自己维护适合人群大多数用户不推荐有折腾意愿的人4. 手搓 .scr 全过程从参数解析到全屏播放4.1 .scr 本质就是 exe四个命令行参数必须响应Windows 调用屏保程序时会传这几个参数这是绕不开的契约/s—— 正式运行屏保进入全屏播放。/c或/c:窗口句柄—— 打开配置对话框父窗口是后面那个句柄。/p 窗口句柄—— 在屏幕保护程序设置对话框中间那个小显示器图标里做预览注意/p和句柄之间是空格不是冒号这一点和/c不一样搞错了预览区域就是一块黑。/a 窗口句柄—— 老式的修改密码对话框现在基本没人用了直接忽略即可。最容易踩的坑是没有处理参数就直接跑全屏。这样做的后果是你双击.scr文件Windows 会认为你在请求配置结果你糊了一个全屏黑窗上来而在设置对话框里点预览又会因为没响应/p而在小框里显示黑屏。花十分钟把参数分支写对能省掉后面一小时的怀疑人生。还有一点调试的时候把.scr改回.exe再运行这样能在控制台看到报错输出。直接以.scr后缀运行的话Python 或 .NET 抛出的异常你一个字都看不到。4.2 Python python-vlc 的最小可用实现我选了 Python 来实现原因是python-vlc能直接把播放器绑定到一个已经存在的窗口句柄上而tkinter提供一个极其轻量的全屏窗口。整套东西加起来不到一百行。import os, sys, glob, random, ctypes import tkinter as tk import vlc VIDEO_DIR os.environ.get(AERIAL_DIR, rC:\AerialVideos) ARM_DELAY_MS 900 # 刚启动时的鼠标余震忽略时间 class AerialSaver: def __init__(self): self.root tk.Tk() self.root.configure(bgblack) self.root.overrideredirect(True) u ctypes.windll.user32 x u.GetSystemMetrics(76) # SM_XVIRTUALSCREEN y u.GetSystemMetrics(77) # SM_YVIRTUALSCREEN w u.GetSystemMetrics(78) # SM_CXVIRTUALSCREEN h u.GetSystemMetrics(79) # SM_CYVIRTUALSCREEN self.root.geometry(f{w}x{h}{x}{y}) self.videos sorted(glob.glob(os.path.join(VIDEO_DIR, *.mp4))) if not self.videos: self.root.destroy() return random.shuffle(self.videos) self.idx -1 self.inst vlc.Instance(--no-audio, --quiet, --no-video-title-show, --file-caching2000) self.player self.inst.media_player_new() self.player.set_hwnd(self.root.winfo_id()) self.motion_armed False for seq in (Motion, Key, Button): self.root.bind_all(seq, self.on_input) self.root.after(ARM_DELAY_MS, self._arm) self.play_next() def _arm(self): self.motion_armed True def on_input(self, event): if self.motion_armed: self.stop() def play_next(self): self.idx 1 if self.idx len(self.videos): random.shuffle(self.videos) self.idx 0 media self.inst.media_new(self.videos[self.idx]) self.player.set_media(media) self.player.play() self.root.after(1000, self.poll) def poll(self): st self.player.get_state() if st in (vlc.State.Ended, vlc.State.Error, vlc.State.Stopped): self.root.after(260, self.play_next) else: self.root.after(800, self.poll) def stop(self): ctypes.windll.user32.ShowCursor(True) try: self.player.stop() finally: self.root.destroy() def show_config(parent): import tkinter.messagebox as mb mb.showinfo(Aerial, f素材目录{VIDEO_DIR}\n设置环境变量 AERIAL_DIR 可更换。) def show_preview(parent_hwnd): # /p 模式把播放器窗口塞进系统给的父窗口里 root tk.Tk() root.configure(bgblack) root.overrideredirect(True) root.geometry(100x100) video random.choice(glob.glob(os.path.join(VIDEO_DIR, *.mp4))) inst vlc.Instance(--no-audio, --quiet) pl inst.media_player_new() pl.set_hwnd(root.winfo_id()) pl.set_media(inst.media_new(video)) pl.play() root.after(100, lambda: None) root.mainloop() def main(): args sys.argv[1:] if args: a args[0].lower() if a.startswith(/p): parts args[0].split() if len(parts) 1: show_preview(int(parts[1])) return if a.startswith(/c): show_config(None) return ctypes.windll.user32.ShowCursor(False) AerialSaver() ctypes.windll.user32.mainloop() if hasattr(ctypes.windll.user32, mainloop) else None if __name__ __main__: main()最后那行mainloop是半开玩笑的写法实际上AerialSaver()构造时tkinter自己会接管消息循环不需要额外调用。真跑起来的时候你会发现在__init__末尾的self.play_next()之后Tk()会自动进入主循环。几个关键点解释一下。set_hwnd是把 VLC 的渲染输出直接绑到 tkinter 窗口的句柄上这样视频就是画在窗口里的性能比 VLC 自己开窗好也不会出现两个窗口抢焦点的问题。ARM_DELAY_MS那 900 毫秒是必须的——系统刚把屏保调起来的时候鼠标通常还停在屏幕上的某个位置窗口一出现就会触发一次Motion事件如果不做延时屏蔽屏保会立刻退出看起来就像屏保打不开。ctypes.windll.user32.GetSystemMetrics(76~79)这四个常量取的是整个虚拟桌面的包围盒用这组值做窗口几何多显示器会被当成一整块大画布视频横跨所有屏幕播放。如果你希望每块屏幕各播各的那就得枚举EnumDisplayMonitors给每块屏各开一个窗口和播放器实例复杂度会上去不少。4.3 打包成 .scr 并装进系统目录代码写完之后用 PyInstaller 打包pip install pyinstaller python-vlc pyinstaller --noconsole --onefile --name Aerial ^ --add-binary C:\Program Files\VideoLAN\VLC\libvlc.dll;. ^ --add-binary C:\Program Files\VideoLAN\VLC\libvlccore.dll;. ^ --add-data C:\Program Files\VideoLAN\VLC\plugins;plugins ^ saver.py--add-binary和--add-data这两组参数很容易被忽略。python-vlc只是一个绑定层真正的解码能力来自libvlc.dll和plugins目录下那一大堆解码器插件。如果不把 VLC 的这些文件一起打包程序在你自己机器上能跑因为系统里装了 VLCDLL 能被搜到但换台机器就报找不到 libvlc。打包完成后dist\Aerial.exe就是成品改名成Aerial.scr。安装位置有两个选择放到C:\Windows\System32\这样所有用户账户都能选中它。放在任意一个固定目录在屏保设置里点浏览手动选。我推荐后者。系统目录需要管理员权限写入而且以后更新文件的时候还得重复提权麻烦。放一个固定目录比如D:\Screensavers\Aerial.scr更新直接覆盖就行。装好之后屏保设置对话框的屏幕保护程序下拉框里就能看到Aerial了。4.4 多显示器、鼠标唤醒与预览窗口预览窗口/p模式是很多人第一次写屏保时会忽略的。那段代码里的show_preview就是干这个的Windows 会把设置对话框中间那个小显示器的窗口句柄传给你你要在那个句柄对应的区域里画点什么。最简单的做法就是像我上面写的那样随便挑一段视频往那个句柄上贴让它动起来就行。不处理的话那块区域会一直黑着看着像程序坏了。鼠标唤醒有个细节值得说。理想的行为是鼠标移动超过若干像素才退出而不是任何微小的移动都退出。原因是有线鼠标的光学传感器在桌面上会被轻微震动触发你的手臂碰到桌子屏保就退了。要做得讲究一点可以记录初始坐标比较位移量def on_motion(self, event): if not self.motion_armed: return if self.origin is None: self.origin (event.x_root, event.y_root) return dx abs(event.x_root - self.origin[0]) dy abs(event.y_root - self.origin[1]) if dx dy 12: self.stop()阈值 12 像素是我试出来的手感太小了容易误退太大了会显得不灵敏。这个数字可以根据自己的习惯调。鼠标指针隐藏用ShowCursor(False)是有效的但要注意这是进程级的计数器配对调用ShowCursor(True)才不会让系统里的指针状态错乱。别忘了在退出路径上恢复。还有一点如果设置了/p参数但没传句柄某些调用场景下会这样别崩直接return就好。屏保程序崩溃会弹一个错误框那个框比屏保本身还显眼。5. 真正用起来之后才会遇到的坑5.1 黑屏、有声无画、直接闪退的排查顺序屏保这东西的麻烦之处在于它跑在无人看管的场景下出错了你也看不到报错。我把这大半年遇到的故障整理成一个排查顺序照着走一般能定位。第一步把.scr改回.exe单独运行。加一个调试开关让程序在无参数运行时也走全屏分支。这一步能立刻排除掉参数解析的问题——如果 exe 双击能正常播那就是.scr的参数分支写错了。第二步检查素材能不能被独立播放器打开。用 mpv 直接播那个 mp4如果 mpv 都打不开问题在转码环节不在屏保程序。常见原因是用-c copy从.mov封的 mp4 里带了hev1标签而不是hvc1Windows 的解码器不认。重封一次加上-tag:v hvc1就行。第三步看是不是只有声音没有画面。这个症状指向set_hwnd没生效。可能的原因有两个一是窗口句柄还没真正创建出来你就调用set_hwnd了解决办法是把set_hwnd放到root.update_idletasks()之后二是窗口被overrideredirect(True)之后句柄变了得用root.winfo_id()拿最新的。第四步检查是不是杀软在作祟。未签名的.scr文件被 SmartScreen 或者某些安全软件拦截是很常见的情况。尤其是 PyInstaller 打包出来的单文件 exe被误判的概率不低。可以在安全软件里给这个目录加个排除项或者把文件放到C:\Windows\System32\下有时反而不会被拦因为那个目录通常在白名单里。这一点确实有点反直觉。第五步也是最容易被忽略的一步屏保根本没被触发。Windows 11 里如果前台有一个全屏排他模式的应用很多游戏、视频播放器都会请求这个系统的屏保和睡眠都会被压制。同样的任何调用了SetThreadExecutionState(ES_DISPLAY_REQUIRED)的程序都会阻止屏保。排查的时候把这些程序都关掉再试。5.2 画面发灰发暗HDR 与显示器的锅前面提过HDR 素材不做色调映射直接播画面会发灰。但还有一种情况是你明明做了色调映射电脑上看起来还是不对。这时候大概率是色彩管理的问题。Windows 的桌面色彩管理默认是 sRGB如果你的显示器工作在广色域模式比如 P3 或者 Adobe RGB又没有加载正确的 ICC 配置文件那视频里本来饱和度正常的画面在屏幕上就会显得寡淡或者偏色。我的建议是给屏保专用的视频单独做一次稳妥版输出不做广色域保留直接烘焙到 sRGB/BT.709 空间然后确保显示器的色彩配置是标准的 sRGB。屏保本来就不是用来做色彩校准的稳定统一比色域覆盖重要得多。另一个可能的原因是显示器的省电模式。有些显示器在检测到长时间静态或者低亮度画面时会自动降低背光而慢镜航拍素材恰恰有一大半时间是缓慢的天空渐变很容易触发这个机制。翻一下显示器 OSD 菜单里的动态对比度节能模式Eco Mode之类的选项关掉就好。这个坑我找了两天才找出来一度以为是转码参数写错了。5.3 屏保和睡眠策略互相打架上一个章节提了一句电源策略这里展开说因为这块的坑特别多。Windows 11 里和屏幕相关的超时有三层按触发顺序依次是屏保超时设置 个性化 锁屏界面 屏幕保护程序、关闭显示器超时设置 系统 电源和电池 屏幕和睡眠、进入睡眠超时。三者必须满足屏保超时 关屏超时 睡眠超时否则屏保要么没机会显示要么刚出来两秒钟显示器就黑掉了。我的配置是 5 分钟 / 10 分钟 / 30 分钟各留了足够的缓冲。调这个用命令行更快powercfg /change monitor-timeout-ac 10 powercfg /change standby-timeout-ac 30 powercfg /change monitor-timeout-dc 5 powercfg /change standby-timeout-dc 15-ac是接通电源时的策略-dc是电池供电时的策略。笔记本上电池策略要更激进一点因为 4K 视频解码是真的费电我实测过一段三分钟的 4K HEVC 屏保一块 60Wh 的电池大概能撑不到两小时。还有一点要特别注意屏保运行的时候别让系统进入现代待机。现代待机S0 Modern Standby下屏幕会先黑掉然后系统进入低功耗状态这时候屏保实际上已经停了。如果你的电脑一闲置就进入现代待机需要在高级电源设置里把在此时间后进入睡眠和在此时间后休眠都调大或者干脆用一个轻量的SetThreadExecutionState调用把显示器保持在唤醒状态。我在屏保程序里加了这个调用问题就消失了ES_CONTINUOUS 0x80000000 ES_DISPLAY_REQUIRED 0x00000002 ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED) # 退出时恢复 ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS)另外一定要在屏保设置里勾上在恢复时显示登录屏幕不然从屏保唤醒之后直接就进桌面了安全性上说不过去。5.4 4K 60帧的代价风扇、功耗与笔记本续航最后聊点现实的问题。4K HEVC 10bit 的解码在现在的硬件上不算重活但也不是白干的。我用量化工具统计过几台机器闲置时跑屏保的功耗增量硬件平台解码方式CPU 占用整机功耗增量i5-12400 RTX 3060NVIDIA NVDEC 硬解1% 左右约 18 Wi7-1165G7 核显Intel QSV 硬解3% 左右约 6 Wi5-8250U 核显Intel QSV 硬解6% 左右约 5 WXeon E3-1230v3 GTX 750Ti回退到 CPU 软解40%约 45 W差距主要取决于有没有硬件解码单元。GTX 750Ti 那个年代还不支持 HEVC 10bit 硬解只能靠 CPU 硬扛4K 素材直接吃满两个核心风扇开始起飞。这种情况下有两个选择要么把转码输出降到 1080p要么换成 H.264 8bit解码负担会小很多。台式机基本无所谓笔记本上就得多想想。我的做法是给笔记本单独准备一套 1080p 的素材接电源时用 4K 版用电池时用 1080p 版。切换方式就是换一下AERIAL_DIR环境变量或者干脆建两个.scr命名为 Aerial-4K 和 Aerial-1080p按需选。还有一点是硬盘读取。4K 素材单个文件几百 MB如果放在机械硬盘上每次切换视频会有明显卡顿。建议放在 SSD 上或者用--file-caching参数把 VLC 的缓存调大一点我在代码里写的 2000ms 就是这个用途。6. 让它更像 macOS 本身的几个细节6.1 切换节奏、淡入淡出与随机顺序macOS 那套屏保之所以好看很大一部分功劳在过渡。两个航拍片段之间不是硬切而是有一段柔和的交叉淡化整个观感高级了不少。在 VLC 里实现淡入淡出比较麻烦它没有原生的视频过渡滤镜。有两条路一是切片段之前让程序把窗口调成全黑再调回来用root.attributes(-alpha, x)做窗口级透明度动画x 从 1 渐变到 0 再渐变回 1中间换播放源。这个方法简单粗暴效果尚可缺点是窗口透明度变化在某些显卡驱动上会有闪烁。二是在转码阶段就把过渡烘焙进素材让每段视频的开头和结尾各带 0.8 秒的黑色渐变然后连续播放两段中间那段黑就是天然的过渡。这个方法完全避免了运行时开销代价是素材本身被改动了ffmpeg -i in.mp4 -vf fadetin:st0:d0.8,fadetout:st180:d0.8 -c:v libx265 -crf 21 out.mp4我个人用的是第二种因为它不依赖运行时的图形特效在任何机器上表现一致。关于再慢一点这个需求素材本身已经是慢镜了但如果你觉得还不够慢mpv 系的播放器可以直接加--speed0.75VLC 可以用--rate0.75。用 FFmpeg 重新生成也可以ffmpeg -i in.mp4 -vf setpts1.333*PTS -an out_slow.mp41.333对应 0.75 倍速。注意这样做视频长度会变成原来的 1.33 倍一段三分钟素材变成四分钟。文件体积不变但循环周期拉长了观感上更舒缓。我不建议慢过 0.5 倍那时候画面会有明显的拖影感看起来像是掉帧而不是慢镜。随机顺序其实比顺序播放更接近原版体验。macOS 是每次唤醒随机挑一段你永远不知道下次会看到哪段风景。我在程序里用random.shuffle打乱列表一轮播完之后再重新洗牌避免出现两轮之间同样的顺序这种巧合带来的重复感。6.2 叠加时钟与地点文字的可行做法原版 macOS 屏保是纯画面不加任何文字。但 Windows 用户往往习惯在屏保上看到时间毕竟锁屏的时候顺便瞄一眼几点是有实际价值的。要在视频上叠字最干净的做法是用tkinter的Canvas在视频窗口上再盖一层透明窗口或者直接把文字画在同一个窗口的Label上。但有个问题VLC 用的是独立的渲染表面set_hwnd之后它会直接往窗口的客户区画tkinter自己画的控件会被盖住。可行的方案有三个。一是用 VLC 的marquee滤镜它能在视频上叠一层文字支持设置位置和字号但字体不好看而且不支持阴影白字压在亮画面上会看不清。二是自己开第二个无边框透明窗口设置WS_EX_LAYERED | WS_EX_TRANSPARENT点击穿透文字画在这层上。这个方案最灵活字体阴影描边都能做代价是要写一点点 Win32 调用。三是干脆不加保持原汁原味。我用的是方案二效果是右下角一行细体的时间加日期白色带 60% 透明度的黑色描边压在画面上很干净。如果你要加地名可以在文件名里预留位置比如01-京都竹林.mp4程序解析文件名的时候把横杠后面那段取出来显示。6.3 换素材、换主题和长期维护这套东西搭起来之后维护成本其实很低但有几个地方值得提前规划。素材目录的独立性。我建议所有视频放在一个独立目录比如D:\AerialVideos程序通过环境变量AERIAL_DIR读。这样以后想把素材移到别的盘只改环境变量不用重新打包程序。环境变量的设置位置是系统属性 - 高级 - 环境变量或者用命令行setx AERIAL_DIR D:\AerialVideos /M/M表示写入系统级变量需要管理员权限。不加/M只对当前用户生效一般也够用。新建素材的接入流程。从 Mac 上拿到新的.mov之后流程是固定的查entries.json拿到名字跑转码脚本输出到素材目录改一下序号前缀。整个过程五分钟不到如果转码用的是硬件加速一个批次也就一两分钟。版本管理。程序本身我放在 Git 仓库里包括那个 py 脚本和打包脚本。转码脚本里的滤镜参数改过好几版从最早的colorspace快速近似到后来的zscale tonemap最后定在libplacebo。把参数变更记录在 README 里过半年回头看能省很多当时为什么这么写的困惑。关于系统升级。前面提过 Windows 11 的屏保功能还在但从微软近几年的动作看这个功能一直处于能用但不推广的状态。如果你哪天发现屏保设置界面找不到了直接用这条命令调出经典对话框control desk.cpl,,screensaver这个入口从 Windows XP 时代一直活到现在绕过设置应用的各种改版相当好用。最后分享一个我自己摸索出来的小技巧。屏保设置里可以配置等待时间但如果你想让屏保在某些情况下不触发比如正在看视频或者开会投屏的时候最省事的办法是让那个应用请求显示常亮——大多数视频播放器和视频会议软件都会这么做Windows 会自动压制屏保。所以不用担心屏保会在你全屏看剧的时候突然跳出来这个冲突系统层面已经处理好了。至于我自己那台机器现在的情况是下班回家放下鼠标五分钟后屏幕慢慢暗下来然后一片慢镜海面从左边缓缓推过来。有时候端着水杯路过瞄一眼还是会停下来看几秒——这大概就是当初折腾这么多天的全部意义了。