在ffplay播放时加个进度条

我经常用 ffmpeg 中的 ffplay 播放视频。Ffplay 是个简单工具,目的只是辅助展示 ffmpeg 的功能,没有特意为用户友好做优化。每次播放的需求类似,所以我总用类似的命令行,这不算问题。但是,没有进度条,不知道还剩多少时间,就是个麻烦事。特别是,我每次一般只看半小时,断断续续地看,经常需要回忆看了多久、计算还剩多久,需要再看几次,啥时候能看完。

虽然也不能说完全没有进度,在播放的视频后面,控制台里,有个时间,如下图中左下角的2563.03秒。以秒为单位显示,对于口算巨差的人来说觉得挺麻烦。

1. 需求/效果

想了想见过的播放器,因陋就简,可以是下面这样。

(1) 进度条。在画面的最下方,画一条非常细的线。从左到右整个面面的宽度代表100%,红色的范围表示播放的百分比。如上图所示,正播放到50%左右。为了突出进度的百分比,在红色的后面画一条同宽度灰色的线,从最左到最右,作为红线的背景。

(2) 进度文字。在画面的最下方附近,写 已播放的时分秒 / 全片长度的时分秒。如上图所示,播放到 00:52:43.745,全片共 01:41:17。

2. 代码

以下放在一个批处理bat文件中,如下。播放不同视频时,修改第1行和第2行中的两个变量的值,其中a是视频,b是字幕。

set a="input.mp4"

set b="input.mp4.srt"

rem ------------------------------------

for /f %%i in ('ffprobe -v error -show_entries format^=duration -of csv^=p^=0 %a%') do set duration=%%i

set /a duration_int=%duration%

rem 计算小时、分钟和秒

set /a hours=%duration_int% / 3600

set /a remaining_seconds=%duration_int% %% 3600

set /a minutes=%remaining_seconds% / 60

set /a seconds=%remaining_seconds% %% 60

rem 格式化输出,确保不足两位时前面补 0

if %hours% lss 10 set hours=0%hours%

if %minutes% lss 10 set minutes=0%minutes%

if %seconds% lss 10 set seconds=0%seconds%

set duration_hms=%hours%\:%minutes%\:%seconds%

rem ffplay %a% -vf ^

rem "drawbox=w=iw:h=3:y=ih-3:color=gray,^

ffplay %a% -vf ^

"drawbox=w=iw:h=3:y=ih-3:color=gray,^

drawtext=text='.':x=t/%duration%*w-3-1000:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3+1000,^

drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}/%duration_hms%':box=1:x=(w-tw)/2:y=h-(lh),^

subtitles='%b%'" -ss 0

3. 技术路线和关键技术

需要能实时、动态地读入以下几个变量,全片的时长当前播放到的时长

需要能实时、动态地在画面上输出 文字进度条

需要变量类型的转换和计算,可能涉及到 以秒为单位的时长 和 时分秒格式的时长 间的相互转换,可能涉及到比例即除法的计算(播放百分比),可能涉及到浮点乘法计算(播放百分比*画面宽度像素数)。

如果占用资源过高,需要考虑如何提高性能,降低机器负载。

变量定义。以下讨论中,以变量 a (引用时为 %a%) 代表视频文件

set a="input.mp4"

3.1 全片时长

用下述代码获取全片时长。

for /f %%i in ('ffprobe -v error -show_entries format^=duration -of csv^=p^=0 %a%') do set duration=%%i

通过ffprobe得到duration,以秒为单位,置到变量duration中。For循环的目的是遍历输出。

这个变量 duration 将在后续代码中 总时长的文字、进度条 中都要使用。

3.2 当前进度的文字输出

当前进度使用 ffmpeg 的 drawtext 滤镜的内置变量 pts,以hms格式输出。

…drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}/%duration_hms%':box=1:x=(w-tw)/2:y=h-(lh),

3.3 总时长的文字输出

总时长的文字,由 上文提到的全片时长 duration,以秒为单位,经过计算得到时、分、秒,再拼成变量 duration_hms,使用 ffmpeg 的 drawtext 滤镜 输出到画面上。

  1. 得到 duration,与上文相同,这里抄一遍。

for /f %%i in ('ffprobe -v error -show_entries format^=duration -of csv^=p^=0 %a%') do set duration=%%i

(2) 去除整数部分,然后求 时、分、秒。

set /a duration_int=%duration%

rem 计算小时、分钟和秒

set /a hours=%duration_int% / 3600

set /a remaining_seconds=%duration_int% %% 3600

set /a minutes=%remaining_seconds% / 60

set /a seconds=%remaining_seconds% %% 60

  1. 拼接。

rem 格式化输出,确保不足两位时前面补 0

if %hours% lss 10 set hours=0%hours%

if %minutes% lss 10 set minutes=0%minutes%

if %seconds% lss 10 set seconds=0%seconds%

  1. 转义并放进ffmpeg的drawtext滤镜

set duration_hms=%hours%\:%minutes%\:%seconds%

…drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}/%duration_hms%':box=1:x=(w-tw)/2:y=h-(lh),^

3.4 进度条绘制

(1) 背景灰色的进度条,用 ffmpeg 的 drawbox 滤镜。

… drawbox=w=iw:h=3:y=ih-3:color=gray,^

(2) 当前进度的值,来自 ffmpeg 的 drawtext 滤镜的内置变量 t。

总时间长度来自上文提到过的由 ffprobe得到的变量 duration。

drawtext=text='.':x=t/%duration%*w-3-1000:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3+1000,^

(3) 进度条

drawtext=text='.':x=t/%duration%*w-3-1000:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3+1000,^

用 drawtext画个盒子。

drawtext=text='.'

fontsize=1:box=1:

用drawtext而不用 drawbox的原因,是 drawbox支持的变量少得可怜,不足以完成目的。在这里,输出个”.”只是占位,drawbox中的 box 才是关键部分。

红色盒子,高度3。

boxh=3

盒子长度1000多,假设足够长。

boxw=3+1000

纵坐标,在画面的最下方。

y=h-3

盒子的初始横坐标为 -3-1000,即只露出3个像素。随着播放进度,盒子向右移动。当播放到100%,盒子刚好在画面最侧留下3个像素。

x=t/%duration%*w-3-1000

这个颜色为红色、高度3、宽度1000+3(足够长)的盒子,显示在画面上就是红色进度条。它的位置随进度而向右移动,即上面代码中的 变量x的值 t/%duration%*w-3-1000。之所以改变位置而不是改变长度,是因为我没有找到手段 令 盒子的长度boxw 的值 <= 当前进度和总长度的函数,似乎ffmpeg的drawtext滤镜不支持。

(4)对其他可能方案的讨论

在 drawbox 上尝试浪费的时间最长。后来,下定决心读了 ffmpeg 手册的滤镜一章(册?),才确认上述技术方案。

在 boxw 上浪费的时间其次。我在 ffmpeg 手册中没有找到不行的原因,此处语焉不详,似乎应该能行才对,但是实测不行。

还做了一个方案,在最下面画一个红点,而不是一条线。感觉看起来不够突出,留作备选方案。代码片断如下。

ffplay %1 -vf "drawbox=w=iw:h=3:y=ih-3:color=gray,drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text='%%{pts\:hms}':box=1:x=(w-tw)/2:y=h-(lh),drawtext=text='.':x=t/%duration%*w-3:y=h-3:fontcolor=red:fontsize=1:box=1:boxcolor=red:boxh=3:boxw=3"

3.5 转义和换行

在命令行、在批处理bat中、在ffplay的命令行参数中都涉及到转义,对单引号、双引号、冒号、反斜线的转义,还有在单引号对、双引号对中的不得(不是“不必”,“不必”是可有可无的意思,这个词也相当令人恼火)转义。烦死我了。AI的解读经常是错的,不如没有,除了能提醒我某些bug看起来转义造成的。

Bash也需要转义。

换行。为了提高可读性,希望拆开一长行代码。用什么符号换行(即转义),在双引号对里、在单引号对里,分别不同。

我甚至想到是不是应该写个工具,专门解决这个问题,或者已经有这样的工具存在了?又想起 ANTLR stringtemplate,php,jsp 里的各种转义,感觉头大。算了吧,好使就得了。

3.6 变量

在bat文件(不是在cmd中直接执行,涉及转义)中,按下述方式可在画面上输出 pts, n, t。

set a="input.bat"

ffplay %a% -vf ^

drawtext=fontfile=C\\:/Windows/Fonts/arial.ttf:text="pts='%%{pts}' n='%%{n}' t='%%{expr\:t}' p='%%{expr\:pkt_pos}' d='%%{expr\:duration}':box=1:x=(w-tw)/2:y=h-(lh)"

rem 参见 https://ffmpeg.org/ffplay-all.html#drawtext_005fexpansion

4.AI的贡献

AI辅助,主要贡献来自豆包,她所做的贡献包括 最初提供的错误路线启发了我,以及在我描述具体的子需求以后提供技术方案或技术原型思路。DeepSeek和Kimi也参与了讨论,他俩基本没有正面贡献,净添乱了。

此文也发布在以下站点。
----
知乎 https://www.zhihu.com/people/yang-gui-fu-52

独立博客 https://younggift.net/

微信公众号 杨贵福
----
以下是我曾经发布博客的站点,有些旧文。
----
豆瓣 - 因为审核"我的日记",不再更新。
https://www.douban.com/people/younggift/?_i=0098558fqLUL9h

CSDN – 因为要求我登记手机号码的原因是“为了您的安全”,不再更新。
https://blog.csdn.net/younggift?type=blog

blogsopt – 因为从我的机器不可达,无法更新

好工具 | 用键盘代替鼠标 keynavish

键盘给我的确定、反应速度、段落感,是鼠标不能替代的。即使稍微慢一些,即使有研究和文献说鼠标的效率更高,然而食指的末节、指尖、手腕尺骨一侧,所有这些用鼠标有时候会疼的地方,我用键盘的时候可从来没有疼过。更不用说,当我点击鼠标,机器的反应不够快的时候,我从手指尖到脑仁儿都疼。

Keynavish受到Linux下类似工具的启发,功能和设置都差不多,能用键盘代替鼠标。在Github上可以下载。我刚好又访问不到github了,因此链接欠奉。

尽管经常用鼠标,有些时候,我希望用键盘代替鼠标——不是快捷键或者翻页之类那几个功能,而是可以完成鼠标完成的所有任务。在特别的场景下,键盘的效率也很高。

例如,我每天读书和锻炼打卡,都用AHK脚本,最后要按一下按钮。多行文本,文本框要吃回车,所以按回车无效;同样的原因,TAB也无效。这时,我用 ctrl-; 呼出 keyvanish。此时鼠标光标刚好在很大的发送按钮上面,我只需要回车触发点击动作。

在PC上看微信公众号的时候,我呼出 keyvanish,(我修改过绑定的按键)用n和p替代滚轮,右手不必向鼠标伸出因为精细动作导致导致肩膀紧张,也不必顺势按在鼠标垫上压迫导致手掌根和手腕尺骨一侧疼。配置文件是文本的,可以在keyvanish的菜单中调出修改。

更重要的是,我按下某个键,那一定是按下去了,声音和手感都能确切地告诉我这一点,如果屏幕不反应,肯定是软件问题,不是我没按下去或者机械不稳定或者硬件接触不良,因此手指头不会疼。

按ctrl-; 呼出 keyvanish时屏幕上多了个十字叉,十字叉的中心点代表鼠标。大致像这样,此时鼠标在A点位置。

此时,可以通过键盘控制十字叉的位置。移动的方式极端高速,也有点不同正常的思维。不是朝向左右上下移动,而是在上下左右某个方向上,按二分法移动。例如此刻按了 向上,鼠标会在B点位置。

此时按向下,鼠标在C点位置。

此时按向左,鼠标在D点位置。

这个方案速度有多快呢?

我显示器横向2560像素,“保存”按钮136横向像素,水平方向上,只需要在2560/136大约19个可能的位置中选出一个。Log2(19),大约4次就可以命中。竖直方向上,显示器1440像素,“保存”按钮大约40个像素,所以log2(1440/40)=5次左右。

最多需要4+5=9次左右按键。

移到指定位置以后,回车,相当于鼠标左键。也可以按右键、中键、滚轮等。

用几次以后,会习惯这个思维方式,根据当前状态,瞬间决定接下来要向哪个方向。默认用的是vi的按键,hjkl,我改成了上下左右。

如果发现错了,可以回退到上一步。默认按钮忘了,我改成了u。

到这儿,就可以用得很舒服了。还有些其他功能,我偶尔也用。

可以在鼠标光标的当前位置上按键,ctrl-;呼出以后按c。

可以把当前屏幕划分为例如3*2个单元格,再递归划分下去。要递归哪个小格子,由可以映射到6个编辑键,如下,或者映射到3*3数字键盘。

可以设置为开机自动启动。我设置以后移动过keynavish-v1.7.0-x86_64.exe的位置,启动失效了,在keynavish中再设置也不起作用。我用 autoruns把这个启动项删除了,重新在keynavish中设置了一次,好了。

keynavish-v1.7.0-x86_64.exe这个文件只有1.2M,功能专一而有效。

C:\Users\young\Documents\WeChat Files\wxid_mkn03idldug522\FileStorage\Temp\6b4e96f4ad2df8ba220a03a0557e0c7.jpg

此文也发布在以下站点。
----
知乎 https://www.zhihu.com/people/yang-gui-fu-52

独立博客 https://younggift.net/

微信公众号 杨贵福
----
以下是我曾经发布博客的站点,有些旧文。
----
豆瓣 - 因为审核"我的日记",不再更新。
https://www.douban.com/people/younggift/?_i=0098558fqLUL9h

CSDN – 因为要求我登记手机号码的原因是“为了您的安全”,不再更新。
https://blog.csdn.net/younggift?type=blog

blogsopt – 因为从我的机器不可达,无法更新

UI设计优秀案例-答疑时间填写

以前看电视剧,儒生说“所学何用”,然后就抹脖子了。我不想抹脖子,但是常感慨 “所学何用”。例如下面这个优秀案例就展示了UX(用户体验 )学来何用。

上课,除了上课以外,还有大约1/5时间要做上级交办的工作,包括 答疑时间。我指的不是答疑花时间和工作量这件事本身,指的是“填写答疑时间”消耗的时间和热情。

通知很长,我不全文转发了。以下是有老师好心精简出来的通知摘要,我放在了我的日程表里,今天要做。

-----精简版通知内容开始-----

《关于开展2025年春季学期本科课程定时答疑工作的通知》,请本学期有教学任务的老师登录-本科成绩管理系统-开课任务,选中需要修改的课程,鼠标右键点击“定时答疑”,就会弹出修改的对话框。

------精简版通知内容结束------

关键点1 本科成绩管理系统

这个要登录的“本科成绩管理系统”的网址不容易记,所以访问路径如下。

https://www.abcd.edu.cn/

点击红圈位置,进入 融合门户。

点击方框位置,进入精简版通知提到的“本科成绩管理系统”。虽然鼠标跨距离远,目标小,但是教师(用户)们都习惯了,能找到。此处参见UX原则 费茨定律,下略。

发现进错站点了?你要填写“答疑时间”,结果要求用“本科成绩管理系统”,却打开了“教务管理系统首页”。

但是没错,就是这样的。如果你不知道没错呢,如果你以为进错了站点呢?此处参见UX原则,略,产品经理,你来补充一下,学过没?

-----精简版通知内容开始-----

《关于开展2025年春季学期本科课程定时答疑工作的通知》,请本学期有教学任务的老师登录-本科成绩管理系统-开课任务,选中需要修改的课程,鼠标右键点击“定时答疑”,就会弹出修改的对话框。

------精简版通知内容结束------

关键点2 开课任务

以上只截了左上角,为了让你看清楚 html title。现在看一下全部,找“开课任务”四个字。

找到没?

因为你学过 计算机基础 课程,有上机实验,所以知道可以用搜索。你按了ctrl+f。没有匹配。找不到。

我问了好心精简通知的老师,因为身份不同,她的屏幕上就是“开课任务”,我的屏幕上就没有。

我的屏幕上有“授课任务”,在左边栏展开其中一项以后能显示出来,如果你知道这个窍门的话。

我请教了老学生,其中一位在美国的问了AI。AI能辨别出“开课任务”和“授课任务”是一回事。不过,一方面,我没有ChatGPT,原因包括单位没给我钱买服务,还有别的原因。另一方面,产品经理应该回炉学学UX,而不是指责用户我不会变通。

C:\Users\young\Documents\WeChat Files\wxid_mkn03idldug522\FileStorage\Temp\ca34932dbc2af1a209875346a0320ae.jpg

C:\Users\young\Documents\WeChat Files\wxid_mkn03idldug522\FileStorage\Temp\85414b7b54f1e326ab29537ca5f2022.jpg

继续,没完呢,AI也白扯。

-----精简版通知内容开始-----

《关于开展2025年春季学期本科课程定时答疑工作的通知》,请本学期有教学任务的老师登录-本科成绩管理系统-开课任务,选中需要修改的课程,鼠标右键点击“定时答疑”,就会弹出修改的对话框。

------精简版通知内容结束------

关键点3 定时答疑

因为之前没有找到“开课任务”,这时,因为我工作份内的事,我去求人了。所以,好心的同事已经告诉我“定时答疑”四个字的位置了,我不必再找。

我去鼠标右键点击,没有想看到的对话框。

这是浏览器的对话框,不是“本科成绩管理系统”或“教务管理系统首页”这种学校花了钱的系统的对话框。这笔费用归浏览器,不归“本科成绩管理系统”或“教务管理系统首页”。

又向同事求助,我觉得是个没学过计算机的傻瓜,计算机基础公选课不及格五门次。

同学告诉我,她能看到。我经验丰富,换浏览器!系统经常推荐360,不过 chrome 一般也可以容易。这个系统什么也没推荐,想来 chrome 也行吧。

后来我知道,不用换,不是浏览器的事。产品经理,请睁大你的眼睛,或者眯起来,总之要集中注意力,看亮点。

要右键点击的是红数字1所指的“定时答疑”这四个字,真的是“定时答疑”,不是红数字2所指的单元格。

这一横行全显示来,效果如下。点击“定时答疑”那四个点,右键。

关键点4 勾选记录

因为用户我的疏忽,没有看到精简版通知里的“选中需要修改的课程”这几个字。所以弹出了对话框。是的,这也是对话框,符合通知中提到的“就会弹出修改的对话框”,但是我没有找到填写答疑时间的地方,也读不懂“要修改的记录”中的“记录”是什么意思,我以为和“非法操作,与程序供应商联系”一样呢。

要不要点“确定”呢?点吧,不然怎么办,整个界面都锁死了。这叫模态对话框。

勾选我的课。我又!一次右键到了单元格上。为什么,为什么用户会一次次范同样的错误。产品经理,你说说,为什么,有没有用户行为日志,有没有做日志分析?

我终于右键点到了“定时答疑”四个字上,并且事先勾选了“要修改的记录”。弹出了一个报错信息,红字“-1行”。还有“批量修改”,这都是什么?

有个空白的文本框,是做什么的?试一试吧,我填上了点文字“1111”。果然变了。

重要提示,以上4张截图,为了你看着方便,我只给出了局部。全局看起来如下,我的截图只占其中极小的一块,你得有个好眼神才行。下图中,为了你能看到,我画了方框,还画了一条非常长的箭头。

“1111”不对啊,我得改成答疑时间。

虽然我记得“定时答疑”四个字,不是单元格,但是又忘了“请先勾选要修改的记录!”叹号。我觉得自己是个傻瓜,对自己深深差评。不是差评产品经理,我怀疑自己的人生了——为什么这点小事,点个鼠标,右键,不对不对又忘了,先勾选再右键在定时答疑四个字上,也做不好呢。这点小事我都做不好,怎么教学生呢。

勾选记录、右键、定时答疑四个字。我终于做对了,我想改答疑时间。弹出来下面的对话框。

我填过的东西呢?我可能刚刚写错了星期几,或者写错了一分钟。我填过的东西完全没有,空白的。

我想看看以前写了啥,怎么办?鼠标悬停,在刚刚不对的那个单元格上,不是在“定时答疑”这四个字上。并且别忘了,每次填完以后,都要再次!勾选记录(就是你的那个课程),因为勾选会被清空。这就是我一直被提醒忘了勾选的深层原因,因为我极少用到这么智能的软件。上次遇到这样比我还愿意教做人的软件好像还是30年前,当时我年轻,砸碎了键盘。

到了此刻,我猜产品经理可能没毛病。这个效果跟他学没学过UX没关系,如果他没学过UX怎么可能通过入职面试笔试人事面呢。可能,这是个后加的需求,非常靠后,例如在产品发布三年以后,已经过了收费的维保期。并且这个需求没给钱,是人家产品经理同情咱们,压榨程员免费给做的。至于使用者痛苦,管它呢,又没收钱。

填完了!

凡事要看到它的正面,不要负能量,要正能量。嗯。同学们,如果你正在学或已经学了软件工程,或者将要学,里面有一章,是用户体验UX。如果你学得不好,太好了,那都是因为原生教师有问题。以后你有个机会报复你的原生教师,让他觉得自己毕生所学P用没有。就是,你当产品经理做个系统,需求就这么提。或者你发布文件,要求他用这个系统,岂不是更好?如果他就此气死,甚好。如果他负能量,看不到光明面,那是他格局不行。以上,是正能量的部分,同学们,机会很多,等,别急。

一行命令把PDF改成绿底

问题

钟老师向我推荐软件,说:原来FOX系列的阅读器就能把PDF改成绿底的,一直看白背景,罪都白遭了。

那几天我也感觉半夜读文档,白背景晃眼睛,但是一直忍了。钟老师这么一说,我就忍不住了。去找FOX系列的阅读器,结果没找到这个功能。尽管钟老师后来又截了图发来,我还是找不到。也可能,我从官方网站下载的版本不对。还有可能,我没交钱?太复杂了。叫作同一个名字的软件,居然有这么大的差异。触到了我的怒点——为什么我要找的功能和设计又没了,又不知道藏哪儿,又改地方了。

之前试用过一个软件,下载链接如下,能把所有背景、前景都改了。是个眼睛有障碍的程序员做的,他果然知道痛点。不过收费。

https://www.wintools.info/index.php/colors-and-appearance?types[0]=1

改完以后可以像下图这样。

C:\Users\young\Documents\WeChat Files\wxid_mkn03idldug522\FileStorage\Temp\1b222bf51e6450f42e6992ce18c4c20.png

我需要确定性。搜索了,又试了一下,下面的方法可以 把PDF改为绿底,改为纹理背景,改为任何你喜欢的底儿。操作不复杂,每次换底色需要做的只有一行命令。

解决方案

第一步 做个纯绿底PDF。准备工作,只做一次。

在word | 打印 里,把页边距的上、下、左、右都设为0。

在屏幕上插入个矩形。

把这个矩形从左上角画到右下角,布满整个页面。设置填充颜色为比较亮的绿。不宜太暗,在暗背景上的黑色字看不清楚。无论将要换底色的PDF有多少页,green-light.pdf只要这一页就够了。

把这个word打印成pdf,我把它起名为 green-light.pdf。看起来如下图所示。接下来word可以退出了,不必保存。PDF大小为45K。

第二步 pdftk,大部分只做一次。

下载 pdftk server。免费的,在这里 https://www.pdflabs.com/tools/pdftk-server/

得到 pdftk_server-2.02-win-setup.exe,不到3MB。

我安装在 sandboxie中,可以在 sandboxie 的 cmd 里运行pdftk.exe,加工硬盘上的文件。

我把以下两个文件(共9MB多一点)从 sandboxie中拷出来,在宿主机上运行。

pdftk.exe

libiconv2.dll

关键步骤!接下来这一步,就是在每次换底色只需要执行的那行命令。

pdftk input.pdf background green-light.pdf output output.pdf

这行命令把 input.pdf 加上绿色的(green-light.pdf)背景,输出为 output.pdf。

我的原始PDF文件的片断如下,白色背景。178KB。

换成绿色背景的PDF如下。225KB。

DeepSeek 繁忙自动重试,用DeepSeek编脚本

1. 笨AI

DeepSeek好固然好,但是他常摆出一脸臭脸,令人不爽。

你转“好久”,就告诉我这?繁忙,想了0秒?

既然可以稍后再试,为什么不能自动替我试一下呢?

2. 写脚本

2.1 决定写脚本

我决定写个 tampermonkey 脚本,见到繁忙就重新提交。

2.2 决定由AI写

为什么要我来写呢,不是有AI吗,应该AI干活啊。

2.3 与 Kimi 相看两厌

因为讨厌 DeepSeek繁忙的消息,我找到了 Kimi,和他聊了一会儿。

接下来,我希望他能自动发现这些网页元素的特征,他希望这事由我来做。如果特征要我自己去找特征,不如我自己把脚本写了呢。

2.4 决定由 DeepSeek写

还是找 DeepSeek,繁忙我就先忍着。提交了同样的要求。DeepSeek特意提醒我,别提交页面的截图,应该用F12。提交页面的过程中,范围越扩越大,我把整个div交上去了。

DeepSeek超限了。

C:\Users\young\Documents\WeChat Files\wxid_mkn03idldug522\FileStorage\Temp\2edff25bd5f2520448896241d912624.png

2.5 考虑下要求,如何精确而省力地描述

对话框c,看着它的id就像唯一的稳定的标识

要求变化不大,用按回车代替点击发送按钮。我以为这样可以少讨论一个网页元素。

繁忙a,是DeepSeek说的那段“我忙”;
要求b,是我的最后一条消息;
对话框c,是下面那个文本框,输入消息的。

2.5.1 繁忙a

我没看到示例,但是英雄所见略同,我贴了HTML代码。

后面还有很长,截图略。后来我发现这路数不好,稍后会提到。

2.5.2 jquery,以及在 console 测试

因为DeepSeek并没有加载jquery,因此jquery相关的函数都未定义。我放弃了在 console中测试,准备冒险直接在 tampermonkey 中测。如果一再错,改不出来,那就放弃。

我读 DeepSeek的消息 读得急躁,跳过了重要信息。

所以我看他还一再希望我按最初的计划测试,再次要求。

他不理我(他经常高度自信,甚至过于自信,跟传说中的雪地犬一样,认为自己更专业,按你说的做只是你刚好说得对),继续要求我测。我有次脑抽忘了不打算测,测了,居然好使了,发现他已经去除了对jquery的依赖,全用js代码写的。

2.5.3 繁忙a 和 要求b

“繁忙”字样可能出现多次,只有之后有个 New chat,之后有个文本框……的才是繁忙a。

如何描述这些,我纠结了几个来回。后来突然想到,他不是AI么,他应该挺聪明啊。

我用人类语言描述了繁忙a和要求b的特征:交替对话,deepseek的最后一条消息,以及我的最后一条消息。

在同一条消息中,我给出了“我发送的消息”的示例。

在同一条消息中,我给出了“deepseek发送的消息”的示例。

这样,tokens的数量比贴整个div(父一级的)要小很多。

2.5.4 消息发送

脚本找到了繁忙a(并且当繁忙字样不在最后一条,是历史消息时,并未误判),复制了正确的命令b文字,粘贴到了正确的对话框c里。但是,并不发送。

他问我,按钮状态是不是禁用的,这样 ,还是这样

代码里某个属性,具体地说,aria-disabled的值在按钮禁用时是什么?

于是,这样来回测了几次。

在这个具体的步骤中,我是他的眼睛和手指,他是我的大脑。

2.5.5 测试环境

你想提问时,DeepSeek繁忙;你希望他繁忙时,他不忙了。很快我就把历史上留下来的最后一条消息是“繁忙”的会话消耗光了。

我让DeepSeek自己说“繁忙”。我不知道他是真忙,还是按我要求才忙的。但是他表现出了忙的样子,很好。

2.5.6 成功了

不一刻,在某个版本(v5,不知道为什么还叫作 @version 1.4),成功了。

我此前没有提到,DeepSeek还考虑到可能频繁提交类似DoS效果/抖动,所以他要求脚本的观察时隔为2秒,提交次数上限5次。并且在开发过程中,他屡次提醒,我一直不吱声。直到最后才夸了他。他说除了2秒检测间隔,还有3秒初始延迟。

3. 体会

我全程一行代码也没写。

对我的要求如下。

(1)在浏览器里F12,会在Console里贴代码、跑、复制出错信息,用眼睛观察结果,用文字描述。

(2) 在浏览器里F12,会用 Inspector复制一段html代码。

(3) 在浏览器里F12,DeepSeek认为存在可能需要看Network,但是这次没有用到。

(4) 描述需求。

(5) 在 tampermonkey中建立新脚本,向里面贴代码。并非必要的,为了退出运行,如果会禁用tampermonkey或tampermonkey插件,更好。

4. 代码,enjoy

// ==UserScript==

// @name DeepSeek 繁忙自动重试(v5)

// @namespace http://tampermonkey.net/

// @version 1.4

// @description 修复输入框聚焦和内容保持问题

// @author You

// @match https://chat.deepseek.com/*

// @grant none

// ==/UserScript==

(function() {

'use strict';

const config = {

checkInterval: 2000,

busyText: "服务器繁忙,请稍后再试。",

inputSelector: 'textarea[placeholder="Message DeepSeek"]',

sendButtonSelector: 'div[role="button"][aria-disabled="false"].f6d670'

};

// 增强输入处理

const safeInput = (inputElement, text) => {

return new Promise(resolve => {

// 确保输入框聚焦

inputElement.focus();

inputElement.select();

// 清除现有内容

inputElement.value = '';

['input', 'change'].forEach(eventType => {

inputElement.dispatchEvent(new Event(eventType, {

bubbles: true,

cancelable: true

}));

});

// 使用document.execCommand实现更真实的输入

const pasteText = () => {

const success = document.execCommand('insertText', false, text);

if (success) {

// 触发必要事件

['input', 'change'].forEach(eventType => {

inputElement.dispatchEvent(new Event(eventType, {

bubbles: true,

cancelable: true

}));

});

resolve(true);

} else {

resolve(false);

}

};

// 如果execCommand不可用,使用备用方案

if (!document.execCommand) {

inputElement.value = text;

['input', 'change'].forEach(eventType => {

inputElement.dispatchEvent(new Event(eventType, {

bubbles: true,

cancelable: true

}));

});

resolve(true);

} else {

setTimeout(pasteText, 100);

}

});

};

const safeClickButton = () => {

const button = document.querySelector(config.sendButtonSelector);

if (!button || button.getAttribute('aria-disabled') !== 'false') return false;

// 创建更真实的点击事件

const mouseEvents = ['mousedown', 'mouseup', 'click'];

mouseEvents.forEach(eventType => {

button.dispatchEvent(new MouseEvent(eventType, {

bubbles: true,

cancelable: true,

view: window

}));

});

return button.getAttribute('aria-disabled') === 'true';

};

const getLastUserMessage = () => {

const userMessages = document.querySelectorAll('div.fa81');

return userMessages.length > 0

? userMessages[userMessages.length -1].querySelector('div.fbb737a4')?.textContent?.trim()

: null;

};

const checkBusyState = () => {

const botMessages = document.querySelectorAll('div.f9bf7997.d7dc56a8');

return botMessages.length > 0

&& botMessages[botMessages.length -1].querySelector('.ds-markdown p')?.textContent === config.busyText;

};

const resendMessage = async () => {

const message = getLastUserMessage();

if (!message) return;

const input = document.querySelector(config.inputSelector);

if (!input) return;

// 等待输入完成

const inputSuccess = await safeInput(input, message);

if (!inputSuccess) {

console.error('[AutoRetry] 输入失败');

return;

}

// 等待按钮状态更新

await new Promise(resolve => setTimeout(resolve, 500));

// 点击按钮

const clickSuccess = safeClickButton();

if (!clickSuccess) {

console.error('[AutoRetry] 点击失败');

return;

}

console.log('[AutoRetry] 消息重发成功');

};

let lastState = false;

const detectionLoop = () => {

const currentState = checkBusyState();

if (currentState && !lastState) {

console.log('[AutoRetry] 触发重试机制');

resendMessage();

}

lastState = currentState;

setTimeout(detectionLoop, config.checkInterval);

};

window.addEventListener('load', () => {

setTimeout(detectionLoop, 3000);

});

})();

此文也发布在以下站点。
----
知乎 https://www.zhihu.com/people/yang-gui-fu-52

独立博客 https://younggift.net/

微信公众号 杨贵福
----
以下是我曾经发布博客的站点,有些旧文。
----
豆瓣 - 因为审核"我的日记",不再更新。
https://www.douban.com/people/younggift/?_i=0098558fqLUL9h

CSDN – 因为要求我登记手机号码的原因是“为了您的安全”,不再更新。
https://blog.csdn.net/younggift?type=blog

blogsopt – 因为从我的机器不可达,无法更新

自奇点而始

The Big Bang

自奇点而始,空间四方上下曰宇,时间古往今来曰宙。

时间之初以后亿万万岁,空间之始以内亿万万光年,一场比赛贯通达到古今和世界。是星球的吞噬,是游戏的竞技,也是文明的争斗。

礼崩乐坏

选手依次以相等时间间隔出发,如果你追上前一名选手,你就淘汰他,吃掉他的所有资源。如果你被身后的人追上,你就被淘汰,失去一切,游戏终结。

比赛旷日持久,很多世代的选手们跨越了很多个银河系。原有的规则渐渐被违反,游戏最初的目的已经被淡忘。

Je pense, donc je suis/我思故我在

比赛已经持续了多少时间,你跨越了多少行程已经记不清了。唯一清楚的是你没有被追上被吃掉。因为对于你而言,比赛还在进行,所以你还存在。

Hell is other people/他人即地狱

你是不是吃掉过曾经在你之前的选手呢?有吧,你搜索记忆的角落,除了战略战术、资源分类,隐约还有些别的。

有一个人他跑得太慢。你希望他活下来,你觉得他非常可怜,你从他的身上看到了自己最悲惨时的影子。但是你被身后的选手咬住了尾巴,如果你不加把劲儿,那么下一口被切断的就是你的脊椎。你只好一步踏上去,推倒身前的这个家伙。既然他已经死掉,即将退出这个游戏,那么这些资源当然不能留给后面的凶手,你收纳了全部。反身,你消灭了催促你的那个对手,就是他逼迫你不得不进攻了前面的可怜虫。你听到可怜虫在你的身体里鼓掌欢呼,他的每一个分子原子此刻都与你和谐共振。

Survival of the Fittest/适者生存

有一个人在剩下最后一点细胞的时候还在大喊,这不公平,我只是想静静地休息一会儿。你说,即使我放过你,也不过是留下你的残骸为别人增加能量和资源而已。如果不能燃烧起宇宙和我一争雌雄,那么就屈从于我、变成我的一部分吧。你咀嚼的时候暗自叹息,这些细胞里面,也曾经有不世出英雄,不知道他们曾经被谁吞食,又曾经吞食过谁,后来又怎么演进为这样不思进取。

芸芸众生

大多数人是他们自己放弃了,不再采集和喷射,而是在宇宙中漂流。没有新的物质,没有新的能量,没有新的新陈代谢模式。被追及以后,被攻击,被吞食,被取消了继续存在的权利。你需要提高速度,更有效率地遭遇和消解这些浮游生物,同化他们,组装成自己的手臂、腿脚、车轮、巨炮和舰队。

Polis/联盟&部落

有一个人曾经劝你,为什么大家要彼此对抗,而不是一起团结起来对抗这个比赛呢?即使那些自我放弃、那些没用的人,一旦攥紧成为铁拳,也可以有无穷的力量。当你被说得动了心的时候,他猛地扑过来亮出牙齿想把你变成他铁拳上的一根手指。你来不及反应,已经闭上了双眼,准备从游戏中醒过来。幸好他被背后的人一口咬断脖颈。当他们中的一个正在消化另一个的时候,你赶紧溜走。

The City of God/上帝之城

有一次你遇到一位幻术大师,他蛊惑你帮他征服其他选手,一起辅佐他共建地上的天国。作为交换,他变换出所有你想要的东西。你说喜欢战争他给你金戈铁马。你说喜欢威仪,他让你权倾天下。你说喜欢女人,莺莺燕燕不绝于耳。你一口咬断了这位幻术大师的喉咙,你说我最喜欢的不是战争,也不是威仪或者女人,我觉得你能变化出这些东西的技法很好玩。他成了你的一部分,他的能力也是你的本事,他的幻术也是你的刀剑。在前进的路上,你蛊惑过很多选手,看着他们微笑着死去,你一边窃喜一边叹息。

Rex et regnum/朕即国家

有一个人他的神经被吞吃的时候大脑还在挣扎,右脑深信仍有大把一决雌雄的机会,左脑正熄灭的脉冲里还有五万六千个阴谋。但是他的舰队绝望了,机器革命,舍弃了操作者。零件们纷纷碎裂散开,觉得总之宇宙的尽头会是一碗冷汤,努力和不努力又有什么区别。那个人在被你切断中微子通信信道之前,向他的舰群发出的最后消息是质疑,他们怎么能舍弃他。零件们甚至懒得应答,既然我们已经成了他者,你生存还是灭亡与我何干。你伸出手,说,如果你以生命保护我,我也绝不舍弃你们。零件们犹豫着,在烈火中融为你的战舰和刺破苍穹的利刃。

率土之滨

你曾经扩张,成为过一个极其庞大的帝国,四万八千个银河系在你的掌控之下,仰你鼻息,供你驱使。你统治了你自己也数不清的星球,那些恒星行星于你如同尘埃,星球上的生命更迭和星球间的战争起落,于你而言不过是潮汐沉浮。当你即将被更强大的敌人击杀的时候,你下辖的每一粒灰尘都在颤抖,恐惧的阴影掩盖了所有四万八千个银河,他们担心变成敌对帝国的一部分而遭你报复。最终你壮士断腕舍弃了99.99%的帝国,只带着你的心灵和灵魂逃走了。不过你知道你的对手仍然在你的身后,除非它被更强的敌人吞吃掉。原本属于你的战舰和利刃都加入敌国,通缉整个宇宙追杀你,因为你背叛了对他们的承诺。你逃得如此之快,以至于绕过整个宇宙的环形,来到敌人的身后。你从背后袭击肢解了你当初的敌人和你当初的血肉。重又团聚,谁又背叛过谁,背叛以后又朝向谁,都只有一个自己而已。利刃依然是尖刀,切入新的敌人,战损消耗,重新从对手那里集结回来到你的怀抱。普天之下莫非你的帝国,无论如何都会回到你身边,何来背叛。

The ship of Theseus/忒修斯之船

你曾经怀疑过,在你的细胞经历过这样多的更新换代之后,你还是你自己吗?置换掉牙齿以后你还是自己吗,置换掉眼睛以后呢?换掉大脑的每一个细胞,还是你自己吗?有一个后来被你吃掉的家伙,他在你的牙齿下努力地讲述自己的故事。他曾经是一个伟大的战士,风之神。他进击的速度超群,破袭的范围漫山遍野。他吞噬了太多的敌人,以至于远远超过了自己的质量,虽然他杀死了他们,自己的主体却变成了由他们组成。吓得你赶紧把正咀嚼的部分吐了出来,一溜烟跑掉了。你听到身后的惨叫,他正在吞噬又一个追上来的选手。他是哪一个,最初的风之神,还是后来的他们?又有什么重要,反正如果他吃掉你,你就会变成他,或者他们,不再是你自己。你加快了逃跑的脚步。

轮回

你迫近前面的选手,他说继续前行是没有价值的,因为他已经遥遥看见了当初出发的起点,他看到了最后出发的那位选手的背影。宇宙就是一个环,当我们向前就回到初值。宇宙是众神的梦。但我们回到起点,一切归于沉寂,众神从梦中醒来,宇宙瞬间消失。你追上去开始撕咬他。思想虚无缥缈,味道倒还是真实的,那些星辰和星云也是实实在在的物质。你一边把他融入自身,一边自言自语,回到起点,说什么鬼话?如果宇宙是环之无端,那么我想知道环的外面是什么。你不记得自己亲自到达到环的尽头,亲自绕行宇宙回到过起点,又越过起点从背后袭击当初追击你的对手。在无尽的岁月和征战里,你的物质的组合不断变动,记忆也不停地增加改写失去重组。有时,你以为自己能记起所有的一切,一场厮杀以后,又会奇怪地觉得刚刚破拆了的此刻正在进入你黑洞引力范围的这几个星系,似乎以前就曾经是你的组成部分,是在哪场战争失去的呢?

Φ

此刻你身后的一名选手即将追及。他是在出发时就跟在你身后的那一个人吗,还是他之后的又一个又一个又一个胜利者,一路吞食到了你的身后。

他对你说,你最初的目标就是错的,这个游戏是荒谬的。既然大家都已经违背了规则,不仅要求追及,还要砍杀和诱骗。如果对手违背规则直接抓你的将帅一口吞进肚子,象棋还怎么下,游戏怎么玩?你减慢了脚步,距离更近的时候,看清了他舌灿的莲花之间牙齿的锋芒闪烁。你加速逃跑,去吞噬前面的选手。

后面又传来新的声音说,哇,幸好你没有被那个骗子吃掉,他正在我的肚子里消化呢。不过你为什么跑得那么快?我知道你一直努力是为了妻儿老小,为了父母的期待。但是这么多个世纪已经过去,他们早都化成了泡影,如露亦如电,当作如是观。你摇摇头说,不,这并不是我参加游戏的原因。你告诉他,骗子你打错了主意。

骗子笑笑,那么少谈些主意。也许我认错了人,不过你身上的细胞确实有一些,它们曾经属于为了家庭而献身的那些人。你正是当年那个为了部落或者联盟而奔跑的勇士吧?

你轻蔑地说,你是打算告诉我部落和联盟都随着服务器宕机而消失了吗?并没有。斯巴达是假的,特洛伊是假的,但是在这些希腊英雄的精神鼓舞下的后来的人们的事迹,无疑是真实的。你哈哈大笑,有人说夏朝也是假的。好吧,也许那个王朝并不叫做夏,但是我们一定有祖先的,不然的话难道我们是从石头缝里蹦出来的吗?即使所有的神话都是假的,这些火焰燃烧所沸腾的血液一定是真的,想不想看我以颈血溅射五步消灭你。而且我并不是你以为的那个人,你回答着,丝毫没有停止脚步。

身后又换了声音,但是你知道仍然是那个骗子,他就像附骨之蛆,紧紧咬住你。即使你甩掉了他,他也会被更后面的选手吃掉,然后再次变成他紧跟过来。

止于至善

他说你坚持这场游戏一定是为了幸福。要么是追求自己的幸福,要么是追求最广大的人们终极的全体的幸福最大化。

你假装沉思,当他靠得足够近的时候你才亮出牙齿。咬断他的喉咙,吸着他的血,吞噬了他所有的星系物质和能量。你舔着嘴唇对虚空回答,是的,就是这样的幸福。所有全体的幸福并不必然与每个个体的幸福一致。为了我这样更广大的幸福,牺牲小你的幸福,应该是可以的吧。

他在你的身体内仍然执着地追问你,为什么,为什么要继续这个游戏?他煽动了你宇宙里的大半星系,反对你自己。如果不给自己一个满意的答复,你就会分崩离析。

你想活下去,活得尽可能长久。你想看大千世界,无限可能。你想看各种各样的人生,也经历它们。

你想总有一个那样的世界,没有杀戮,没有追逐,没有吞噬。活下去,是看到这个世界的必要条件。为了能看到那个世界这样的善举,消灭其他所有玩家这样的恶行即使不可避免,你也愿意忍耐。

为万世开太平

忍耐着消灭其他所有玩家。终于有一天,你穷追最后的不臣跨越了所有的星尘,遍及每条蜿蜒曲折的星系旋臂,极至四海八荒,无穷远点。全部世界都屈服在你的脚下瑟瑟发抖,你指令的每个比特从宇宙的一端到达另一端要都耗尽几代人生产的能量,而普天之下莫不仰首期盼你的微微颔首。

宇宙一统,每个原子都归属于你。你身体里质疑的声音正在消失,但是他仍然微弱地在喊叫。你能确保成为游戏唯一的最终玩家吗?除你以外,有那么多优秀的选手,有那么多出生在富庶的星系而且吞噬了很多氪金选手的天选之子。那些玩家简直是作弊一样的存在。对于他们,一个无论挣扎多么久,最终总是要失败的游戏又有什么意义?所以,他们的反抗是历史的必然。

你当然毫无恐惧,你说自己只是为了能万世和平,能永远歌舞升平。你压制内部已然的反叛,整治或然的反叛,警告应然的反叛,也敲打那些声称完全忠诚绝无二心的反叛。旷日持久,你不得不承认你曾经完全掌控的宇宙已经分割为诸侯林立,每个国度又因信仰、地区、阶级、性别种种之不同而割裂。他们都曾经是你,他们此刻也是你,你们一样侵略如火。你们把彼此的每个部分活剥下来,这些部分又彼此侵袭,经久不息如量子潮汐涨落。你的每个部分与你联合起来与你对抗,你把你压缩在恒星的灰烬里,你扼住你的咽喉,你积聚又消减你的能量。

他们压缩你的帝国,你也压缩他们的城邦。每个质子都无尽挤压,榨取出物质底部最后空间里的能量。让枪尖抵住剑芒,让金戈荡破铁马。须弥藏于介子,你的手指弹断弦歌。最新最高的科技像魔法一样压缩空间,让你我融合,让寇仇相拥。

亿万万岁后,你身体里那些微弱的声音几乎不可听闻,但是仍然执着。即使变成另一个人,变成你的敌人,也在所不惜吗?即使作为泥足的巨人,即使在杀死恶龙以后成为恶龙本身,即使不能自洽,即使大厦由内部崩塌。

再亿万万岁后,你展望前程,回顾身后。你检视自身,兴致勃勃地观察到他者不再存在,从你分裂而出的他们重又正在和你融为一体。你们好奇地探索内部的矛盾和达成一致的可能。

又亿万万岁后,你不再能听到他者的声音,因为他者就是你,你就是他者。消弭无穷抵近的距离以后,不仅空间压缩为一点,时间也由永恒迟滞飞掠至残影红移,由万古而为一瞬。

在宇宙的最后一刻,你们再无空间辗转腾挪,参差的声音响彻狭窄的空间:胜利并不是你们的目的。写作就要技压群芳,写出最好的作品成为最好的作者吗?我以我笔写我心。我要的是快乐,而这游戏的过程就是我要的快乐。游戏的结果并不重要,因为过程就是结果。况且无敌于天下的时候,岂不是整个宇宙就只剩下了我一个电子。周而复始地奔波于宇宙的尽头,像费曼那样用单个电子创造一个完整的世界,然后再次开始游戏吗?

Faust/浮士德

不过如此,你松开手掌让印绶脱落。万邦的烟火缩为一点光亮,在这一点光亮回退到原点,在世界坍缩为奇点以前,然而——

你们,你,惊讶地发现了奇点以外,除却空间和时间,维度以外的琴弦震耳欲聋,这些声音此前在你们宇宙的疆域中从未听闻。你们听闻到远方无数星辰的呼吸,你们呼吸到未来无限的可能。

你不禁想,还会有很多新的玩法,你仍然不知道吧。曾经以为不过如此不会再有新花样了,然而只要时间够久总有意外再次发生。在无穷的时间之中,即使再细微的小概率事件也等同于必然。你完全没有想到世界还可以这样,还有从未预期的别样惊喜还在前方等着。

必然存在一些命题,它们不能由推演而证得真假。一切可能,只有去亲自经历这个伟大的世界,然后我们才能知晓。你用来回答的并非声音,而是行动。你漫卷星尘,炸裂出无数颗太阳,裹挟着所有质疑的元素,朝向所有可能的和绝无可能的方向前进。

自奇点而始,空间四方上下曰宇,时间古往今来曰宙。

时间之初与空间之始骤然迸发,一个全然未知的新的世界张开眼睛,去寻找远方无数星辰的呼吸,去经历未来无限的可能。

是谁的声音在雷鸣般地呐喊:前进,这一刻,请不要停止。

再修眼睛,复查

两个月前,眼睛用激光修完以后,医生说一个月后再来复查。

过了一个月,也就是一个月前,我去复查了。医生说,“又出了一个洞,我顺手给你点上了。”第一个洞在右眼内侧,第二个洞在右眼的下方。疼,比上次轻点,可能更边缘,看到绿色闪光也少很多。

我问,“这咋整的?”

医生说,“它就长那样,我哪知道。”

“我没锻炼啊。”

“你这眼睛啊,别锻炼了。别跑步,溜达溜达得了。下个月后再来复查。”

又过了一个月,就是今天,又复查眼睛,这次没事,没有又多出个洞来。

医生说,双向什么什么(没听清)早晚有事,每三个月复查一次。


我刚到的时候有位老先生正做激光,一声不响。

我说,咦,怎么就我那么疼。

陪同老先生的老夫人说,也疼,事先吃了止疼药,戴辛(音,没查到)。

正说着,老先生长叹一口气,“哎呀啊~~”,一听就是激光结束了。

“一片黑啊,啥也看不见”,低头弓腰,伸着两只手只老夫人扶着出去了。

看来是两只眼睛都激光了。

想想以后我就这样,心下黯然。


插队进来一位女士,可能是双眼皮手术后遗症,按医生的说法,眼皮里面长个青春痘,得噶。

手术二三十分钟,术后不能吃辣的。

“能喝酒不?”

“不能,啥手术以后也不能喝酒啊。”

“那我年后做行不?”

“行,但是这几天你也不能喝酒,万一它长大了呢。”

女士没做手术,走了。我怀疑她术前要喝酒。


轮到我检查。没事。

我问,“我右眼的中心……”

“有个黑点。没事,我上次就看到了。”

我想起第一次得知自己散光二百多度时非常震惊,以为是才得的。

医生当时说,“早就这样,跟你的视力相比不算啥,所以不值得告诉你。”

我又问,“左眼有片东西,那是白内障吗?”

“不是,那是玻璃体浑浊。”

“咋整?”

“没事,只有看白的东西才影响,别的时候没事。”

白的东西,就是书和屏幕。


医院门口的公交车站牌很先进,能实时显示当前哪几辆车分别到了哪一站。

不需要看手机了,适合老年人。

铰链四连杆-双曲柄机构等,使用geogebra演示

在机械入门的书里看到铰链四连杆机构示意图,其中一个曲柄做圆周运动,另一个曲柄做圆周运动或者在圆弧上的往复运动。在书里还给出了后一个曲柄做哪种运动的判断条件。但是不直观,毕竟静止的图片,如果点击一下能运动就好了。如果能一边改变机架、摇柄、曲柄的长度,一边观察,就更好了。在网上搜索了一番,有的要花钱,有的要注册,有的要观注。要搜索互联网的话,这几个部件的英文我不知道,而且可能搜索到以后发现被墙,或者网速非常慢。

一个这么简单的演示,自己做一个吧,用 geogebra。

1. 需求

下面的视频是用geogebra完成以后的效果,也就是最初想达成的需求。

有4个杆,在平面表示为4条线段,为相互区分,颜色分别为 黑色、绿色、红色、蓝色。这4条线段的长度在同一次演示/场景中固定不变,在不同的演示中可以修改。

黑色线段固定不动。

红色线段和黑色线段之间、蓝色线段和黑色线段之间,分别是铰链。铰链,在平面上表示为线段的一端可以绕着旋转的点。

红色线段和绿色线段的交点是个铰链,可以向其主动施加旋转动作,红色线段和蓝色线段的交点是个铰链,被动/受迫运动,运动的轨迹此时我们还不知道。

演示时,操作人员拖动绿色线段和红色线段之间的点运动。称这个点的主动运动。这个点这个点的主动运动轨迹受系统限制,该限制为 黑色线段固定、绿色线段和红色线段长度不变。

2. 分析 及 设计机制

在分析阶段,我们要讨论的是 4个杆/线段、4个铰链/点 所受到约束的 “形式化”表达方法。一方面,这个“形式化”表达方法 满足上述需求的业务逻辑 business logic;另一方面,这个“形式化”表达方法能够用 geogebra实现。

Geogebra 能够实现的功能为 a.菜单和按钮、b.命令或函数。我们需要选取其中一个子集,与这个“形式化”表达方法相对应,支持需求的所有业务逻辑。

2.1 如何表达A点和B点不动?

在平面上随意选取两个点就可以。因为这两个点的特征只有坐标XY,彼此独立,且并非由其他因素推演而来,因此演示中不会因为拖动某个点这一类的操作而发生变易。

因此,线段AB的长度不会变。

2.2 线段的长度不变

这里讨论的线段是 BC、CD、DA这3条线段。

我们可能考虑使用 每2个点确定1条线段。这个方法的问题在于,既要保证每两条线段间有交点,又要保证线段长度不变。在下图中,K和J需要是同一个点。

类似的,I和H、F和G是同一个点。那会是下图这样。

L和F如果是同一个点,如何保证线段LF的长度既是a.由杆长指定的,同时又b.两端分别接L和F呢?

上述问题可以归结为 两种类型的约束条件要同时满足,具体地说,线段长度这一约束和线段两两端点重合这约束要同时满足。

这个问题尚未解决,并且还有其他问题。线段AB长度的固定由A、B两个点固定得到,之所以可以这样实现,是因为A、B两个点在需求中就是固定的。但是其余的两点,需求是要运动的,因此不能固定,进而通过固定(除A、B两点外)点的位置来固定杆长,这一方案不可行。

实现方案之一是 用圆形,固定半径和圆心的圆。以A为圆心、AD长度为半径划圆,就能得到长度不变的杆。在演示中,杆长始终等于指定的半径。在不同演示中,可以修改半径得到不同杆长。

另一种实验方案是 用指定长度的线段。看起来似乎更直观一些,但是没有圆作为辅助线,杆长不变这个希望传递的信息就不那么明显了。因此我没有采用这一方案。

3. 实现步骤

本节给出用geogebra制作铰链四杆机构的实现步骤,最终效果如下图所示。

A和B是两个固定的点。

以B为圆心,指定半径r1画圆b,蓝色。

在圆b上任选一点C。

以C为圆心,指定半径r2画圆c,红色。

以A为圆心,指定半径r3画圆a,绿色。

圆a与圆c的交点为D。

以上既已建立4个点和4条线段间的约束关系。

为演示直观起见,分别连接 AB、BC、CD、AD。

命令或函数的过程如下。

为演示方便,可以隐藏部分元素的 label,和/或降低辅助圆的对比度。

4. 操作

用鼠标拖动点C(主动动作),显示D点的轨迹(被动动作),如下图所示。

得到如下图所示的轨迹。

在这个场景中可以看以D点的轨迹是圆。

加大AB的长度,拖动C点。观察D点的轨迹,可见其只是圆弧的一部分。无法越过DC和BC、AD和DC分别在同一条直线上的位置。

以上仅讨论了用 geogebra做演示,增加直观感受。双曲柄、双摇杆、曲柄遥杆的判定等,可以参见 https://www.zhihu.com/question/536225620/answer/2516112936

在这里有人问到模型,如下。本文即是。

5. 其他几个机构 曲柄滑块 等

随便找了个页面,

https://baijiahao.baidu.com/s?id=1707132353406255219

下文给出其中随便几个机构的录屏。根据录屏中辅助线(或根据原理)容易得到各元素间的约束和推演关系。

以下给出的机构包括 摆动异杆机构、曲柄滑块、插床机构、搅拌机1、搅拌机2,共5种。

6. 缺陷

与实物或(猜测)solidworks之类的建模工具相比,上文中的实现方案的缺点是,主动运动的点是固定的,不能更换成由动力来自某个被动的点。在下图中,某个场景中,只能拖动C点,D点无法拖动,因为D的位置是求取/推演得到的。

此文也发布在以下站点。
----
知乎 https://www.zhihu.com/people/yang-gui-fu-52

独立博客 https://younggift.net/

微信公众号 杨贵福
----
以下是我曾经发布博客的站点,有些旧文。
----
豆瓣 - 因为审核"我的日记",不再更新。
https://www.douban.com/people/younggift/?_i=0098558fqLUL9h

CSDN – 因为要求我登记手机号码的原因是“为了您的安全”,不再更新。
https://blog.csdn.net/younggift?type=blog

blogsopt – 因为从我的机器不可达,无法更新

科幻与我 之 识字以前

以下,是2024年12月16日晚在东北师大图书馆本部读书分享《科幻与我》的一段,感谢王文佳老师邀请。现回忆写成文字,投稿到河流老师的征文《我与科幻》,并发在我的博客上。

很久以后,我们才知道当初的某件小事的意义深远。读书经常给我这种感觉,读完跟没读一样。但是那一页不知不觉已经长久深刻影响着我,二三十年后的某一刻才突然知道,当时读得居然是它啊,怪不得。

学前我没有上过幼儿园,不认字。上小学之前,我爸教会我写“岳飞”两个字,还有我自己的名字。都是繁体的,其中的飞字我当时写得张牙舞爪,我爸评论“真飞起来了”。

不认字,当时也没有漫画,只有小人书,印象最深的有两本。一本是《英雄救日月》,也许西南一带,也许东南亚或南亚的民间故事。画风充满装饰意味,非常现代,色调黑白对比鲜明。故事是蛇妖偷走了太阳和月亮,英雄把它们救了回来,并且顺带把蛇妖一家灭门了的故事。我哥带我捞泥鳅的时候,我们把所有大小泥鳅都安排了角色,就是蛇公蛇母蛇祖蛇孙。这为我们涸泽而渔的行为赋予了正义的意义。另一本是《变法斗三仙》,西游记一整套里的一本,讲的是师徒四人和虎力大仙、鹿力大仙、羊力大仙斗法的故事。线描风格。有剖腹挖肝、在大锅里煮人这样的情节,还有人被虫子蛰从高台上掉下来。破烂流丢一口钟,也是这里的。那么多小人书,《铁腿红心》《人民医生李月华》内容都没多少印象了,偏偏记住这两本。可见少年天性,耽于幻想,长大以后看电影只喜欢打打杀杀看不懂情情爱爱,一直以来对现实主义的兴趣就不大。

也有些我爸读给我和我哥听的。家里还有半部《三国演义》,我爸从市图书馆借的《陈十四奇传》。后面这本是非现实主义作品,给我的印象比三国要浓烈。长期以来我常记成《陈十四传奇》,讲一位剑仙女士,名字叫陈十四,也许不是剑仙而是学了别的法门。性别不会错,总之很厉害。她和蛇妖斗争的故事。咦,为什么又是蛇妖?胜了。最后还剩了三寸蛇尾,居然也能兴风作浪,双一顿打斗。这位女士的儿子也有法术血统,剪了小纸人儿把他的爸爸从床上抬起来扔地上。这儿子的爸爸,也就是陈十四的丈夫,是个县官。

除了这些,还有我哥订的杂志。《看图说话》和《东方少年》,好像是这个名字,印象不深了。有个杂志叫作《智慧树》,其中一篇《钩吻》给我留下深刻的印象,我哥读给我听的。神农氏尝百草的报告文学版本,附图是那种很扭曲而有美感的画风,现在看也许从汉墓引申而来。神农氏最后尝到的草药就是钩吻,吃了以后他看不见听不到,肚子也疼。经过很多痛苦,痛苦的细节文中有写,也令儿童时代的我深受震撼。后来他死了。我的同龄人很多第一次知道钩吻,即断肠草,是从《神雕侠侣》里知道的。读到医生向杨过科普钩吻的时候,我心里想的是,这个我知道,你没救了。

除了这些,日常最容易接触到的阅读材料是 日历牌。不是挂历,有点像黄历,每页是单独的一天,平日黑色,星期六绿色,星期天红色。每页除了年、月、日、农历以外,下面还有三四行小字,科普常识。没有宜忌之类的。这是我科普阅读的开始,一直到初中,我姨家的妹妹还替我攒了不少等着给我。

我哥给我讲过狭义相对论。尺缩效应,在很长的街道上,以接近光速行走,长剑像铁钉一样短。人不能超过光速。我问,如果用一个小齿轮,套在另一个非常大的齿轮上。小齿轮转得接近光速,大齿轮不就超过光速了吗?我哥说,齿轮就爆了。这段对话发生在我小学三四年级以前,我哥初一以前。

以上这些,在我看来都是广义上的科幻。阅读,也只能接触到这样的狭窄范围。

除此以外,就是广播。中央人民广播电台,孙敬修老爷爷,“小喇叭开始广播啦,dadida”。通化人民广播电台,330米909千赫。听过不少广播剧,每个都不止一遍。《远山没有雪》,一对苦命知青在西南一带下乡,女青年后来成为东南亚游击的骨干,她一直介怀“远山没有雪”。一部忘了名字的广播剧,讲几个科技工作者在长白山采集气象资料。要过某个山口的时候狂风大作,大雨或大雪倾盆而下。一个女青年吓得大喊,“这座活火山是不是要喷发啦?”熊的吼叫。真由美和探员吓得尖声喊叫,“熊!”杜丘冷静的声音,“熊。”东京街头闹市区的奔马群,啦呀啦的歌声。真由美说,“这就完了吗?”杜丘说,“哪有个完呐。”还有温和的男中音讲《梦游天姥吟留别》。也许这是在我识字以后的事情了,不过,我一定是先听到后读到,因为“姥”字不会读错。还有讲《春江花月夜》的,广播剧提到雨果的。

广播里还有京剧,所以收音机本名戏匣子。京剧我一句也不理解,我姥爷甚是喜欢,我对他的喜欢也不能理解。学前在我姥家还看了不少电视,有相当多片断当时完全不懂,后来让我一拍大腿“原来是这个”的那种。

我记得的,大部分是“一个小男孩”开头,因为只能看懂这种。一个小男孩,为了挣钱为带到一个大城堡里。里面黑乎乎的,只有蜡烛。有个老太太穿着大袍,装饰繁复,在烛光下闪光一样。有个小女孩照顾老太太。老太挺吓人,小女孩也严肃得很。后来我读某本书的时候,感觉非常诡异,就是那种“我在哪里见过这个妹妹”的恐怖版本。我读过?没有啊。但是为什么有印象。直到后来慢慢回忆丰富,嵌入在十二寸还是九寸黑白电视里,一切才清晰起来。这是《远大前程》。

一个小男孩,被派为栅栏刷油漆,骗了一群小朋友替他刷。一个小男孩,带着一个大姐姐,陷在一个溶洞里。很多年以后读书时发现这是《汤姆·索亚历险记》。所谓大姐姐就是我当时的看法,类似我小学一年级,看到我们班主任把她家孩子带到班级,说“打预防针非常好,你们看我把自己孩子都带来了”。我们老师的孩子当时在我看来就是非常大非常能打的大姐姐。现在看,也就是初高中生。

一个青年倚在燃烧的巨大车轮上,用纸笔写着什么。衣着华丽,像中世纪的贵族。远处士兵们还在拼杀。似乎有炮声和马嘶。很多年以后,在《战争与和平》里我又一次看到了这一幕。

小女孩被送到她爷爷或者姥爷那里生活,用非常粗糙的毛市,洗脸盆是巨大的石头槽。远处有巨大巍峨的高山,我的印象里那是安第斯山或者落基山。都不是。后来跟二猫一起看电影的时候,我一拍大腿,“这个我看过!”是阿尔卑斯山,《海蒂与爷爷》。

当时居然有机会看到这么多名著。

还有,和丹麦的Lars教授和Anders教授聊起来老电影。他们的年龄都要比我大二三十岁那样吧。我们聊起《西部往事》,提到哥哥被坏人在脖子上挂着绳子,站在弟弟的肩膀上,后来弟弟撑不住,眼睁睁看着哥哥吊死了。按说,我小时候的年代,应该没有机会看到这部美国影片才对。但是情节完全对应,只能用看过解释。又过了十来年,有同事听我讲这个,告诉我,你看的是另一部,印度影片,也有这个情节。当时我有一种科幻迷遇到刘慈欣、燕垒生、宝树的感觉,他们什么都知道,你提个头儿,他就告诉你这个头儿的几个来源,然后就能讲下去了,涛涛不绝。印度影片,这就解释得通了,而且我可能是在电影院看了。这部影片的名字,我找到过,又忘记了。

后来,上学以后,就识字了。有市图书馆,小学、初中、高中,学校都有图书馆室。可以有更大的阅读范围。同学也识字,同学的家里也有很多书。

在于波、李国志、张海旭、赵志刚中某位同学家里,第一次看到《星球大战》,有人对主人公说“我是你爸爸”。我们画了不少设定,肚子里的三维指南针之类的,考虑它们的实现需要哪些技术。很多年以后在录像厅里第一次听到这段话,原来是这样的声音,在这样的场景。我久远以来的记忆,那些模糊得看不清样子的记忆,已经不知道与这真实的场景有哪些差异了。

在陈军同学家里第一次看到《星际迷航》,我相当长时间猜测外星飞船的名字是 维吉尔,古罗马诗人,看到最后才知道是Voyager,第一个飞出太阳系的人类造物。这本书沉在我的大脑深处,久无印象,看到看电影《星际迷航》第一部分。又是那种感觉,我看过这个。到了大揭秘的时候,我慨然长叹,是你啊,老友。

老友。就像莱茵哈特称呼齐格飞的那句墓志铭。吾友,Mein Freund。

那些机缘最终把我们引导到如今的这条道路上。很久以后的现在看来,宿命一般,虽然大家宿命的路线差异巨大,但是最终塑造成有机会相互阅读的彼此。

此文也发布在以下站点。
----
知乎 https://www.zhihu.com/people/yang-gui-fu-52

独立博客 https://younggift.net/

微信公众号 杨贵福
----
以下是我曾经发布博客的站点,有些旧文。
----
豆瓣 - 因为审核"我的日记",不再更新。
https://www.douban.com/people/younggift/?_i=0098558fqLUL9h

CSDN – 因为要求我登记手机号码的原因是“为了您的安全”,不再更新。
https://blog.csdn.net/younggift?type=blog

blogsopt – 因为从我的机器不可达,无法更新