MoreRSS

site iconChawye Hsu

在互联网行业里摸爬滚打,平时最爱好听音乐,略懂写一点点代码。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Chawye Hsu的 RSS 预览

倒带,关于虚幻勇士的记忆

2023-09-01 01:00:00

发现 Code Lyoko 相关异常信号,正在进行检测与确认……

是否进行记忆扇区重建? [y/N]

正在准备虚拟化环境…… 加载完成。

启动同步……


回放

2010 年,彼时高中成绩还算稳定的缘故,我获得机会,拥有了人生中第一台属于自己的电脑。那是一台戴尔的迷你上网本,配置很低,CPU 是英特尔的凌动处理器。以今天的眼光来判断,此选择真是失败,同样的预算当时完全可以得到一台配置更高的。只是那时我对硬件一窍不通,完全是凭借「品牌+便携」两个关键词做下的选择。

虽然是一台笔记本电脑,一台比常规笔电更加便携的上网本,但彼时寄宿制高中生活的我,有且只有在节假日和寒暑假时段才能触摸到放在家中的这台电子设备。而这也让我对这台电脑的使用,变得与初中走读时期偷偷去网吧玩游戏的感受完全不同。虽然也偶尔会玩各种游戏,但更多的时间是用来给我的 MP3 播放器下载拷贝歌曲以及下文讲到的字幕组工作。

寄宿高中禁止携带电脑,在那个网络资费还是高昂的时期,即使默许携带(以便于联系家长)的手机拥有访问网络的能力,30M 每月的数据流量对于我来说还是连一首歌曲也不敢下载。流量更多地被用于文字类的聊天、内容搜索浏览以及「文字偷菜」等游戏上。这时候,更小巧的 MP3/4 播放器就成了同学们的掌中宝物。MP3 除了能听单词助记忆外,还能听有声小说和音乐,待机时间也长。听音乐便成了我高中生活的一个重要组成部分,同时也极大地影响我往后的生活。

那会儿,我的 MP3 里面有相当一部分音乐是一些动画片的 OP/ED 之类的歌曲,除了包括知名的《数码宝贝》的 Butter Fly、Break up!、Brave Heart 等,《中华小当家》、《名侦探柯南》的众多 OP/ED(其中也有我的本命 band GARNET CROW 的 tie 曲)外,还有非常喜爱的《我为歌狂》的我的舞台和有梦好甜蜜,《四驱兄弟》、《光能使者》的国语版 OP 等国语曲目。在这些之外,有一首特别的英文曲目,它的名字是「A World Without Danger」。

是的,它便是《虚幻勇士》的英文版 OP 原曲。我使用手中的电脑,借助搜索引擎从百度 mp3 等平台听到了不少以上提及动画片的歌曲,《虚幻勇士》就是其中一个。除了找到了这首英文 OP 外,我当时还从土豆网上看到了这部动画片未曾在大陆播出的第三、四季,同时了解并加入了 Code Lyoko China 论坛社区。只是那时候并不知道 CLC 彼时已经是一个濒临解散的社区,这也为我后续涉足字幕组埋下了伏笔。

靠着土豆网上由 CLC 制作组自发译制的《虚幻勇士》第三、四季的视频,我几乎补完了这部动画片的后续剧情。

土豆网 CLC 制作组播单快照
土豆网 CLC 制作组播单快照[1]

说「几乎」是因为当时并未通过 CLC 制作组播单看完全部剧集。根据我的记忆加上历史档案,其播单最终停在了第四季第 90 集,最后 5 集因片源问题未能完成译制,直至论坛关闭。不甘 CLC 衰落的坛友计划筹建新的社区基地,也就是后来的 CLCN。当时我正渴望补完最后几集,在 CLC 还在过渡到 CLCN 的阶段中,借着找到片源的契机,我跟着坛里的成员合作完成了第四季其中的第 93、94 集的译制。

那时 CLCN 制作组的名号还未被启用,译制作品还是以 CLC 名义发布的,而最后也只正式发布了第 93 集,第 94 集的「遗产」则是被后来的 CLCN 在其重制计划中吸收继承了。

封存于网盘里的 Code Lyoko 内容
封存于网盘里的 Code Lyoko 内容

在之前,我对字幕组的工作流是一无所知的。凭借着搜索引擎与一腔热血,我先后把 SRT/SSA、VobSub/VSFilter 以及 MeGUI 等工具学了一遍。还记得当时为了保持与 CLC 制作组相似的风格,要把英语音频抽出来切割后与法语视频在时间上对齐就花了不少时间。回头看那时候真是无知者无畏,拿着一台笔记本电脑搞字幕组的活,要知道那是一台不到 11 寸,板载低功耗低频的凌动处理器的上网本。最后开着它挂通宵机二压出成品的日子,让我至今难忘。

断档

除了「小刀砍大树」的方式进行字幕组工作外,那段时间我还因为发现 IFSCL 这个制作优良的同人 Flash 游戏,给其翻译了一版用户手册分享给坛友们,没记错的话那份文档大概是当时我在坛里的最终贡献。后来 CLCN 成立,我加入其中并活跃了一段时间,也参与了第五季真人版最早期的一些译制。在现实生活与兴趣变化的影响下,字幕组相关的工作于我并没有持续多久。在之后我把所学的技能用在给 GARNET CROW 的歌曲做字幕上,完成了其最终 Live 的字幕制作的同时,也给我短暂的字幕组经历画上了句号[2]

虽然当初学习的各种 MeGUI 压制参数等等已经完全忘记,一些多媒体知识仍然在我脑海中留下了痕迹,特效字幕的学习与制作也相当程度提升了我对技术乃至编程的热情。现今视频自媒体、AI 翻译与字幕各种先进技术和方案普及,多媒体已不再是普通人难以接触的领域知识。

字幕组的这段经历是深刻的,然而引发这一切的根源之于我却是陌生的。在很长时间里,我想不起我具体在什么时候看过这部动画片。某段时期在浏览网络上他人关于《虚幻勇士》印象的各样讨论,我甚至一度对自己在哪个就学时期看过这部动画片产生了怀疑。也许是现实渐长的迷惘和焦虑心情,对那段美好时光产生了挤压和覆盖。我热切希望寻找到关于这个断档时期的一些直接痕迹。

重建

年中,我在给 MusicBrainz 平台贡献 GARNET CROW 以及其他音乐人的音乐元数据时,意外地开启了一小段高频率使用搜索引擎和互联网档案Internet Archive 的时间。为了提供准确的音乐元数据信息,我除了在各音乐发行版权登记平台上蒐集标准数据字段外,还借由互联网档案工具查询一些已经难以正常访问的信息,比如不同音乐曲目之间的关联。在这个过程中,我发现了一些我此前未曾了解的有趣信息。比如其中一个是关于陈慧娴的《跳舞街》超多个不同演唱版本的信息[3],另一个则是关于 GARNET CROW 最早期地下专辑《first kaleidscope 〜君の家に着くまでずっと走ってゆく〜》在正式发行前的预发行版本的切实证据。

在完成了音乐元数据的搜寻工作后,我没有停下来。我想趁热打铁,继续去寻找我在什么时间看过《虚幻勇士》的线索。在此之前,我曾尝试利用搜索引擎几次检索关于《虚幻勇士》在大陆地区电视台上播出的信息。只是,一方面众所周知大陆中文互联网历史数据留存情况和可访问性并不乐观,另一方面便是被传播最多的《虚幻勇士》「盗版永封」事件,导致中文区很难找到相关有用的线索。这次我想借着音乐元数据蒐集工作的好运气,再次尝试一下。而这次的结果是——我那段断档的记忆,变得无比清晰。

我使用的线索是我唯一确信的一点:我在南方电视台上收看的《虚幻勇士》。我对此印象最深的就是台标是 TVS 标,至于是哪个频道我印象不深,网络上的评论一说是 TVS-6 一说是 TVS-5。通过南方电视台的维基页面得知其在 2014 年已被合并,官网已关闭,另外维基指出 TVS-6 在 2005~2006 年间台标出现过变更,从原来的 TVS-6 改成了 TVS-5,不变的是仍然为少儿频道,播出动画等内容。

通过关键字「虚幻勇士 国产」我找到了广电于 2007 年发布的对 2006 年度电视动画发行许可的通告原文[4][5],其中谈及《虚幻勇士》假冒国产动画的封禁:

由上海录像影视公司、上海文广新闻传媒集团、广东安利伟影视制作有限公司(现已更名为广东原色动力影视制作有限公司)联合制作,并已取得国产电视动画片发行许可证的动画片《虚幻勇士》(沪动审字〔2006〕第003号)是一部假冒的国产动画片;

通过 Wayback Machine 我发现南方电视台的官网有快照信息,并且在 2010 年之前的快照页面上可以浏览到快照当天的部分节目预告清单。我挑了几个关键年份快速浏览了每个首页快照的节目预告信息,发现首页快照上的节目预告是所有频道混排的,能提供的信息有限,我转而去浏览了 TVS-5 和 TVS-6 的频道页面快照。经过一番浏览我发现一个信息,在 2007 年 8 月份的一快照上显示 TVS-5 频道播出的仍然是科教节目,同年 10 月的快照才显示了少儿频道的节目清单。也就是说,事实上 TVS-6 到 TVS-5 的频道切换最少是在 2007 年 8 月至 10 月之间完成的,并非维基百科上所言的 2005~2006 年间。我结合广电的通告时间,重点关注了 TVS-6 频道的页面快照,并最终在其 2006 年 9 月 2 日的快照上找到了我想要的信息。

超级卡通影城播出《虚幻勇士》
超级卡通影城播出《虚幻勇士》[6]

找到这张图的结果并非写得这样容易。事实上我在第一遍翻查快照时,翻遍了 Wayback Machine 上 TVS-6 从 2003 年至 2008 年的全部数百个快照,也许是粗心还是其它因素,并没有发现上图。在第二遍翻查时,重点浏览 2006 至 2007 的快照,才找到了 2006 年 9 月 2 日这一天。而在之后的第三遍翻查时,我才意识到,整个 2006 年 9 月到 10 月,就刚刚好只有 9 月 2 日这一天有快照,并且上面刚好登记了「17:30 超级卡通影城栏目播出《虚幻勇士》第 3、4 集」的预告。换句话说,上图中的信息是「独此一份」,是目前网络上公开可查且唯一的关于当年南方少儿频道 TVS-6 曾播出过《虚幻勇士》的直接证据!音乐元数据搜寻工作的好运气没有消散。

由于快照唯一,我无法得知完整的播放档。但是《虚幻勇士》一二两季共 52 集,按照每天播出两集计算,一个 9 月份即可播完。按照 9 月 2 日播出第 3、4 集的事实,我推测 9 月 1 日播出了第 1、2 集。换句话说,《虚幻勇士》于 2006 年 9 月 1 日首播于南方电视台 TVS-6 频道。我便是在这一个月,收看到了它。夏末秋初,傍晚黄金档,相信符合不少人关于此的记忆。对于在 9 月 1 日首播的推测,我通过当年的一则关于禁播境外动画片的新闻予以佐证:

据《广电总局关于进一步规范电视动画片播出管理的通知》,自2006年9月1日起,全国各级电视台所有频道在每天17时—20时之间,均不得播出境外动画片和介绍境外动画片的资讯节目或展示境外动画片的栏目。

根据该则新闻[7],2006 年 9 月 1 日恰好是相关规定实施的第一天。《虚幻勇士》在次年被查出假冒国产,也曾被大多数看过的人误以为是国产动画片,有理由相信当年其被披上国产作品身份过审并播出。通过快照可以看到,当年与 9 月最近的两次存档(分别在 8 月以及 11 月)可以得知,禁播令未生效的 8 月 21 日,TVS-6 在黄金时段 17:30 播放的是《名侦探柯南》,黄金时段二 18:45 播出的是《网球王子》,晚间时段 20:00 播出的则是《虹猫蓝兔卡通天地》[8];而在禁播令已然生效的 11 月,黄金时段 17:30 已改播出由广东一家公司于 1999 年制作国产动画片《新少年黄飞鸿》(我对这部动画片的印象也非常深刻,尤其是其中一段「在塔中通过武术打败石头象棋」的故事),晚间时段 20:00 播出的则是《四驱兄弟》[9]。如此节目档期调整后便完全符合生效的境外动画片禁播令要求。至此在禁播规定生效之日改播「国产的作品」以顶替此前播出的相对知名的日本动画片绝不无道理。而在更早些的 7 月份,黄金时段 17:30 超级卡通影城播出的是《光能使者》。如此连串起来,这一系列熟悉的动画名字,实在让我不得不重新回忆起那段时光。

后话

我找回了属于我的那段关于《虚幻勇士》的真实记忆,但同时我也因此意识到了一些原本我大概率不会想到的,让我觉得些许无奈又有些惊讶的事情。一个是原来早在我刚步入中学的年代,进口动画片的相关限制就已然开始了,这是我这个当年还有幸接触到那些优秀作品的 90 后未曾想到过的。对国产动画的倾斜扶持开展得比我想象中的要更早,只是咱们期待的崛起到来了吗?我浅浅认为原神能算一个?可是它不是动画啊…… 而另一个,则是关于《虚幻勇士》的,我开始思考,《虚幻勇士》与像我一样的其他人们在南方少儿台相遇的故事,到底是一场多大概率的偶然而又必然的事件?《虚幻勇士》的「国产化」是一次对境外禁播令的必然预判,还是一次偶然的漏网之鱼?禁播令生效、调档期改播国产、挑中「国产」的《虚幻勇士》,这一连串事件少发生一个都不会有我这篇文章了。这就是蝴蝶效应吧?

2007 年 1 月底《虚幻勇士》的 VCD 被发行上市[10][11]

……

写于 2023-09-01,与《虚幻勇士》相遇的第 17 年之际。

完。


  1. https://web.archive.org/web/20100512092320/http://www.tudou.com/playlist/id/1455688 ↩︎

  2. https://www.bilibili.com/video/BV1is411Z7VL/ ↩︎

  3. https://t.me/s/chawyehsu/19 ↩︎

  4. https://bangumi.tv/blog/314291 ↩︎

  5. https://web.archive.org/web/20071014174009/http://www.sarft.gov.cn/articles/2007/04/05/20070910172345930217.html ↩︎

  6. https://web.archive.org/web/20060902154439/http://www.tvscn.com:80/tvs6/ ↩︎

  7. https://web.archive.org/web/20230514005302/https://news.sina.com.cn/c/2006-08-13/01269732023s.shtml ↩︎

  8. https://web.archive.org/web/20060821001300/http://www.tvscn.com/tvs6/ ↩︎

  9. https://web.archive.org/web/20061105012111/http://www.tvscn.com/tvs6/ ↩︎

  10. https://web.archive.org/web/20070314005906/http://www.gdqh.com.cn/ ↩︎

  11. https://archive.ph/ia5HP ↩︎

用 Rust 写的 Scoop 再实现

2023-08-10 22:31:43

开门见山,先放上项目地址:https://github.com/chawyehsu/hok

前言

了解 Scoop 或者已经是其用户的朋友从标题已经知道本文主题,大概点了上面的链接跳走了。没点的只好留下来,听我先给不了解的人稍微介绍下 Scoop。Scoop 是一个在 Windows 下的工具,通过一套规则来描述应用软件[1],然后提供命令行的方式来让用户相对方便地安装和管理使用的软件。它最初的开发受到过 Homebrew 的启发,虽然随着发展二者已大相径庭, Scoop 仍然在其 README 中留着对 Homebrew 的 Credit[2]

一直以来 Scoop 都没有标称自己是一个包管理器[3],但是它的主要功能和主流包管理器的功能又是有几分相似,所以一些用户愿意称其为 Windows 下的包管理器。相应的,也有(比如偏好 MSYS2 的)用户批评过 Scoop 根本没有提供包管理器「标配」的软件构建环境及 build from source 的能力,到底只不过是一个「软件安装器」。我作为使用 Scoop 已经有 8、9 年,于 20152018 两年先后为其写过两篇「荐」的用户,以为 Scoop 的这种形式更多是 Windows 软件分发现状下的一个些许无奈的折中选择,只能说是有人喜有人愁。强如微软官方出品 Windows Package Manager —— Winget 也还是没有走出这个窠臼。AppX/MSIX 的打包分发方式不是所有应用开发者都接受并统一采用,曾几何时我从 UWP 上看到的一点点软件分发整合的希望也在其放宽 Win32 API 使用后慢慢消去(虽说就开发者角度来说这是好事)。所以 Scoop 在某些场景下仍有它的一席之地,放到末端用户就是各取所需。


这是什么

接下来谈论的是本文的主角 Hok。它是一个用 Rust 写的 Scoop 再实现,提供与 Scoop 类似的 CLI 接口,目标是实现 Scoop 已有的功能,如应用的检索、安装卸载、列表与状态、桶管理等等。实际上 Hok 只是一个 CLI 前端,其背后的 libscoop 才是这个再实现的核心。后文会再提到 libscoop。

Scoop 用户在饱受着 scoop search 极度缓慢的折磨[4],带防火墙 debuff 的用户更是如此。早期的 Scoop 还不支持远程搜索[5],那时候搜索速度其实是很可以的。为了解决搜索体验问题,社区里出现了各种各样的解决方案,比如 Go 实现的 scoop-search 是社区里很流行的一个[6],也有其它如 Python + SQLite 的实现,以及依托 Azure Search 实现的 ScoopSearch(后来成为了 Scoop 官网上提供的在线搜索[7])等等。

然而社区里这些方案,都不约而同地只专注于解决搜索问题,并没有在其它方面做更多的尝试。也许是搜索问题太过于「碍眼」,导致其它如 scoop listscoop statusscoop update 等命令的效率问题不入法眼。而 Hok 便是在解决了搜索问题的基础上,更进一步去尝试解决这些也让我难受的点,最终成为一个奔着完整实现而进行的项目。


开始尝试

假设你已经有在使用的 Scoop 环境,那么你可以使用一下命令来安装体验 Hok:

scoop bucket add dorado https://github.com/chawyehsu/dorado
scoop install dorado/hok

dorado 是我维护了许久的一个桶,里面有不少我在用的或者接收 Pull Request 进来的软件。 Hok 是 native 的,可以脱离 Scoop 运行,所以其实也可以从 GitHub Release 页面下载二进制文件直接使用。但是因为 Scoop 的一些功能在 Hok 上还未被实现,所以单独用 Hok 的话会缺失关键功能,建议还是附在 Scoop 上。

安装完成后,可以使用 hok help 先查看 Hok 的帮助信息,了解下 Hok 的命令行接口:

$ hok help
Hok is a CLI implementation of Scoop in Rust

Usage: hok.exe <COMMAND>

Commands:
  bucket     Manage manifest buckets
  cache      Package cache management
  cat        Inspect the manifest of a package
  cleanup    Cleanup apps by removing old versions
  config     Configuration management
  hold       Hold package(s) to disable changes
  home       Browse the homepage of a package
  info       Show package(s) basic information
  install    Install package(s)
  list       List installed package(s)
  search     Search available package(s)
  unhold     Unhold package(s) to enable changes
  uninstall  Uninstall package(s)
  update     Fetch and update subscribed buckets
  upgrade    Upgrade installed package(s)
  help       Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

Type 'hok help <command>' to get help for a specific command.

为了照顾一些个使用习惯,Hok 的命令行接口被设计得与 Scoop 的接口有一定的相似性,比如 hok buckethok homehok hold/unhold 等。但是由于 Hok 背后的 libscoop 在内部实现上与 Scoop 不尽相同(主要是多了一些我个人的思考及针对我个人使用习惯的调整),所以体现到 Hok 的命令行接口上也有了一些变化。接下来我会逐一对目前 Hok 提供的各个命令做具体的介绍。这部分也许该写成使用文档放到项目里,但目前就先放在这吧。


hok bucket

桶管理命令。这个子命令与 Scoop 的 scoop bucket 子命令几乎一致,唯一的一个不同是 Scoop 使用 scoop bucket known 来列出内置的(官方)桶[8],而 Hok 使用 hok bucket list -k|--known 来做同样的事情。

$ hok bucket --help
Manage manifest buckets

Usage: hok.exe bucket [COMMAND]

Commands:
  add     Add a bucket
  list    List buckets
  remove  Remove bucket(s)
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

hok cache

下载缓存管理,对应 scoop cache。Scoop 使用 scoop cache [show] 列出下载缓存列表, Hok 使用 hok cache list [query],其中 query 可以作为过滤条件,只列出包含 query 的缓存。

Scoop 使用 scoop cache rm <app|*> 来删除缓存,类似的 Hok 使用 hok cache remove <query> 来删除缓存,其中 query 同样支持通配符 * 删除全部缓存。

$ hok cache --help
Package cache management

Usage: hok.exe cache [COMMAND]

Commands:
  list    List download caches
  remove  Remove download caches
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

hok cat

全文输出包的 manifest 详情。hok catscoop cat 的用法完全一致,但是 hok cat 多了对同名包的选择功能,如果你本地的不同桶之间存在同名的包的话,scoop cat <app>会自动选择第一个找到的包,而 hok cat <app> 则会列出所有同名包,让你自己选择。

$ hok cat --help
❯ hok cat
Inspect the manifest of a package

Usage: hok.exe cat <package>

Arguments:
  <package>  Name of the package to be inspected

Options:
  -h, --help  Print help

$ hok cat git
Found multiple packages named 'git':

  0. dorado/git (https://gitforwindows.org)
  1. main/git (https://gitforwindows.org)

Please select one, enter the number to continue: 0

同名包的选择功能在 hok home 等其它命令中也同样存在。

hok cleanup

清理命令,对应 scoop cleanup。Hok 暂未实现 cleanup 功能,所以在这里它只是个 placeholder。

$ hok cleanup --help
Cleanup apps by removing old versions

Usage: hok.exe cleanup [OPTIONS] [app]...

Arguments:
  [app]...  Given named app(s) to be cleaned up

Options:
  -k, --cache  Remove download cache simultaneously
  -h, --help   Print help

hok config

配置管理命令。Scoop 使用 scoop config 展示配置,Hok 使用 hok config list 以 JSON 格式展示。Scoop 使用 scoop config <key> <value> 设置配置,Hok 使用 hok config set <key> <value> 来设置配置,Scoop 允许给任意 key 设置值,而 Hok 会检查 key 是否合法。Scoop 使用 scoop config rm <key> 删除配置,Hok 使用 hok config unset <key> 来删除配置。

$ hok config --help
Configuration management

Usage: hok.exe config [COMMAND]

Commands:
  edit   Edit the config file [default editor: notepad]
  list   List all settings in key-value
  set    Add a new setting to the config file
  unset  Remove a setting from config file
  help   Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

Hok 支持使用 hok config edit 命令调用外部编辑器来编辑配置文件。Hok 会使用环境变量 EDITOR 的值作为调用的编辑器,比如设置了 EDITOR=nanonano 就会被用作打开配置的编辑器。如果没有 EDITOR 环境变量,Hok 会使用 notepad 来打开配置文件。

直接调用 nano 编辑配置文件
直接调用 nano 编辑配置文件

Hok 完全继承并兼容 Scoop 的配置文件,虽然 Hok 不会去使用其中如 aria2 等的配置项,但是为了兼容性 Hok 支持解析和编辑这些配置。Hok 甚至已经率先支持 portable config file[9]

hok hold/unhold

包锁定/解锁命令,对应 scoop hold/unhold。Hok 的 holdunhold 命令均与 Scoop 对应命令的用法一致。

$ hok hold --help
Hold package(s) to disable changes

Usage: hok.exe hold <package>...

Arguments:
  <package>...  The package(s) to be held

Options:
  -h, --help  Print help

$ hok unhold --help
Unhold package(s) to enable changes

Usage: hok.exe unhold <package>...

Arguments:
  <package>...  The package(s) to be unheld

Options:
  -h, --help  Print help

hok home

打开包主页的命令。Hok 的 home 命令与 Scoop 对应命令的用法一致,但是 hok home 多了对同名包的选择功能,具体说明见上文 hok cat

$ hok home --help
Browse the homepage of a package

Usage: hok.exe home <package>

Arguments:
  <package>  The package name

Options:
  -h, --help  Print help

$ hok home pixitracker
Found multiple packages named 'pixitracker':

  0. dorado/pixitracker (https://www.warmplace.ru/soft/pixitracker/)
  1. extras/pixitracker (https://warmplace.ru/soft/pixitracker/)

Please select one, enter the number to continue: 1

hok info

包信息命令,对应 scoop info。Hok 的 info 命令与 Scoop 对应命令有所不同, scoop info <app> 只会精确匹配包名,而 hok info <query> 中的 query 是一个正则表达式入参,当输入 hok info git 时会列出所有包名中包含 git 的包的信息。

$ hok info --help
Show package(s) basic information

Usage: hok.exe info <query>

Arguments:
  <query>  The query string (regex supported)

Options:
  -h, --help  Print help

hok install

包安装命令。在对包的安装、更新、卸载操作上,Hok 有着一套相对于 Scoop 改动比较大的设计,这也是 Hok 及其背后的 libscoop 之于 Scoop 区别最大的部分。依托于这部分设计改动, Hok 得以实现对同名包的选择与替换、锁定包强制更新等功能。

$ hok install --help
Install package(s)

Usage: hok.exe install [OPTIONS] <package>...

Arguments:
  <package>...  The package(s) to install

Options:
  -d, --download-only   Download package(s) without performing installation
  -f, --ignore-failure  Ignore failures to ensure a complete transaction
  -o, --offline         Leverage cache and suppress network access
  -y, --assume-yes      Assume yes to all prompts and run non-interactively
  -D, --ignore-cache    Ignore cache and force download
  -I, --independent     Do not install dependencies (may break packages)
  -R, --no-replace      Do not replace package(s)
  -S, --escape-hold     Escape hold to allow changes on held package(s)
  -U, --no-upgrade      Do not upgrade package(s)
      --no-hash-check   Skip package integrity check
  -h, --help            Print help

但是截至目前 libscoop 在这部分功能的实现上还不完整,所以 Hok 的 install 命令也是不完整的,一些参数的功能也还未实现。具体实现到什么程度,可以自行体验或者查看源码和 CHANGELOG。hok install <package> 中的 package 同样支持 Scoop 那样的桶前缀指定,比如 dorado/git

$ hok install git -DS
Resolving packages...
Found multiple candidates for package 'git':

  0: dorado/git
  1: main/git

Please select one, enter the number to continue: 1
Calculating download size...
The following packages will be REPLACED:
  doradomain/git

Total download size: 55 MB

Do you want to continue? [y/N]: n
Not implemented yet.

hok list

已安装包的列表命令。Hok 的 list 命令与 Scoop 对应命令的基础用法一致,同时提供复杂的筛选功能。hok list [query] 中的 query 是一个正则表达式入参,支持以正则表达式的方式对已安装包的列表进行筛选。同时 query 还支持桶前缀指定。结合正则和桶前缀,可以实现诸如 hok list extras/hok list "dorado/^[c|g]"hok list main/git$ 这样的筛选。Hok 还支持使用 hok list --held 查看被锁定的包。

$ hok list --help
List installed package(s)

Usage: hok.exe list [OPTIONS] [query]...

Arguments:
  [query]...  The query string (regex supported by default)

Options:
  -e, --explicit    Turn regex off and use explicit matching
  -u, --upgradable  List upgradable package(s)
  -H, --held        List held package(s)
  -h, --help        Print help

Hok 将原本属于 scoop status 的查看可更新包的功能移到了 hok list 中,使用 hok list --upgradable 可以查看可更新包的列表。

$ hok list dorado/^g --upgradable
git/dorado 2.41.0.windows.1 -> 2.41.0.windows.3 [held]
gnupg-np/dorado 2.4.2 -> 2.4.3

hok search

包搜索命令。搜索功能可以说是整个 Hok 项目的核心,也是因为 Scoop 的搜索效率问题,才有了 libscoop 和 Hok。Hok 的 search 命令与 Scoop 对应命令的不同点在于, scoop search 会远程搜索那些没有挂载到本地的官方桶,hok search 只会搜索本地桶。另外,scoop search 强制对包名、包描述以及 shim 均进行匹配,而 hok search 默认只对包名进行匹配,提供 --with-description--with-binary 选项来开关对 shim 和包描述的匹配。这些调整为的都是最大化搜索效率。

$ hok search --help
Search available package(s)

Usage: hok.exe search [OPTIONS] <query>...

Arguments:
  <query>...  The query string (regex supported by default)

Options:
  -e, --explicit          Turn regex off and use explicit matching
  -B, --with-binary       Search through package binaries as well
  -D, --with-description  Search through package descriptions as well
  -h, --help              Print help (see more with '--help')
Benchmark of search
Benchmark of search

hok list 一样,hok search <query> 中的 query 同样是一个正则表达式入参,支持正则搜索,可以使用 --explicit 选项来关闭正则,转为使用精确匹配。同样的,这里的 query 也支持桶前缀指定,也是一个变长入参,可以同时指定多个查询。

$ hok search minecraft extras/^clash ^git$
clash-verge/extras 1.3.5
clash.mini/extras 0.2.2
git/dorado 2.41.0.windows.3 [installed: 2.41.0.windows.1]
git/main 2.41.0.3
minecraft/games nightly

hok uninstall

包卸载命令。上文讲到,与 Scoop 相比,Hok 对包的安装、更新、卸载操作有较大改动。 hok uninstall 执行时默认会检查待卸载包的依赖关系,如果有其他包依赖待卸载包,则会终止卸载操作,避免破坏依赖。这应该是正常策略,只是 Scoop 好像没有考虑到这点。通过使用 --no-dependent-check 可以关闭这个依赖检查,强制卸载待卸载包,这可能会导致其他包无法正常运行。

$ hok uninstall --help
Uninstall package(s)

Usage: hok.exe uninstall [OPTIONS] <package>...

Arguments:
  <package>...  The package(s) to uninstall

Options:
  -c, --cascade             Remove unneeded dependencies as well
  -p, --purge               Purge package(s) persistent data as well
  -y, --assume-yes          Assume yes to all prompts and run non-interactively
      --no-dependent-check  Disable dependent check (may break other packages)
  -S, --escape-hold         Escape hold to allow to uninstall held package(s)
  -h, --help                Print help

默认情况下,被锁定的包无法被卸载,使用 --escape-hold 可以绕过这个限制,强制卸载被锁定的包。此外,hok uninstall 支持使用 --cascade 选择开启「级联卸载」,这会在卸载包时同时检查其依赖,如果依赖包不再被其他已安装包依赖,则会一并卸载。 --purge 选项与 scoop uninstall --purge 一致,用于卸载包时一并清理包的持久化数据。

$ hok uninstall swift --cascade
Resolving packages...
The following packages will be REMOVED:
  dorado/swift-5.8.1  main/dark-3.11.2

Do you want to continue? [y/N]: n

hok install 一样,hok uninstall 的主体功能尚未完整实现,请留意项目 CHANGELOG。

hok update

桶更新命令。Hok 的这个命令只保留了 scoop update 里更新所有订阅到本地的桶的功能。

$ hok update --help
Fetch and update subscribed buckets

Usage: hok.exe update

Options:
  -h, --help  Print help

hok upgrade

包更新命令。与 Scoop 的 scoop update 更新包相对应的功能,在 Hok 中被移到了 hok upgrade 这里。hok upgrade 类似于 scoop update *,会更新所有已安装包,除了被锁定的包。被锁定的包可以通过 --escape-hold 选项来强制更新。使用带参的 hok upgrade [package] 可更新指定的包。

$ hok upgrade --help
Upgrade installed package(s)

Usage: hok.exe upgrade [OPTIONS] [package]

Arguments:
  [package]  The package(s) to be upgraded (default: all except held)

Options:
  -f, --ignore-failure  Ignore failures to ensure a complete transaction
  -o, --offline         Leverage cache and suppress network access
  -y, --assume-yes      Assume yes to all prompts and run non-interactively
  -S, --escape-hold     Escape hold to allow to upgrade held package(s)
      --no-hash-check   Skip package integrity check
  -h, --help            Print help

无论是 hok upgrade 还是 hok install 默认状态下都会结合本地下载缓存的情况预先获取并计算需要下载的文件大小,而通过 --offline 选项可以关闭网络访问,强制只使用本地的下载缓存来安装或更新。但这取决于本地缓存的有效性,如若无效,安装或更新操作会失败。

$ hok upgrade git --escape-hold
Resolving packages...
Calculating download size...
The following packages will be UPGRADED:
  dorado/git-2.41.0.windows.3

Nothing to download, all cached.

Do you want to continue? [y/N]: n

以上就是 Hok 目前阶段提供的所有命令的介绍。通过这些介绍,应该可以大致了解到 Hok 的开发思路。因为还在绝赞进行中,所以命令的功能还不完整,有些甚至还未实现,只是预留了个坑在那里。所以完全有可能会变化,还是那句请留意项目 CHANGELOG 以了解最新进展。


为什么做

关于这个,在文章开头部分其实已经有所提及。一开始完全是因为 scoop search 的搜索效率问题,继而想去解决更多我个人使用 Scoop 时遇到的痛点。Hok 这个项目其实是「老坑新开」的,有心的可以去仓库翻 git log 了解一下,早在两年前我就已经下锄头挖坑了,只是中途断了一段时间,最近才又开始填坑。个中原因不便细说,反正是按自己需求来的个人项目,见步行步。

为什么不直接贡献 Scoop

实话说,我作为 Scoop 的 maintainer team 成员之一,不去贡献 Scoop 而是自己写一个新的东西,这好像是有点说不过去。但是 Scoop 作为一个近 10 年的项目,拥有我觉得还挺庞大的用户量,我是不太能按个人想法去改动一些东西的,会很容易影响到其他用户。而且不是说一个 maintainer 就能确定发展方向,有积累的项目就会有惯性,想要改动一些东西也不是那么容易的。按自己的需求来做个新的相对容易推动些,另外这也算是个试验田,也许能在后续给 Scoop 带来一些反向作用力,进而反哺回去。

为什么是 Rust

这似乎是个逃不开的话题,尤其是在好像「Rewrite Everything in Rust」被调侃成为一个梗之后。既然选择了 Rust 写 Hok,是要回答一下。理由其实很简单,我单纯想学 Rust,所以就用 Rust 来开坑了。这几年是切身感受到了 ripgrepbathyperfine 以及 starship 等项目给我带来的愉悦,也许这些项目背后的 Rust 语言不过是一个次要因素。这里并不是如那般狂热的 RIIR 信徒[10],因为不是用 Rust 写一遍就能解决所有问题,任何语言,背后关键的还是人和思想。Rust 的学习曲线也确实挺如网络上传说的那般陡峭,对于我这种仍未学会 C++ 的人来说更甚,真的是有「从入门到入门,到再三入门」的感受。而且学了没地方用,非常容易忘,我好像又忘了 Pin/Unpin 和一些其它的东西了。虽说如此,那个连续数年被评为最想学语言的 Rust 还是值得我去探索一下,学 Rust 后反过来再尝试读一些 C/C++ 的代码,至少我觉得会有一些帮助。

Hok 完全可以用其它语言来写。Scoop 是用 PowerShell 编写的,理论上用 PowerShell 背后的 .NET/C# 来写会更方便和直接一些,甚至可以无缝过渡。PowerShell 直到 v7 才支持ForEach-Object -Parallel[11],Scoop 目前还得支持 v5 的 Windows PowerShell,所以继续用 PowerShell 改善 Scoop 的性能问题是有点技术包袱的。然后 Scoop 最近的一些优化改动,多少其实都在渐渐引入一些 .NET/C# 的接口,并非纯纯 PowerShell 实现。


libscoop 的 roadmap

Hok 项目的核心是其中的 libscoop 库,之所以抽出 libscoop 这层抽象,当然是为了后续 hok-gui 甚至 hok-tui 的可能,不过这都是待定的事情,也不一定做(得出来)。目前短中期的路线图主要是完成 (un)install/upgrade 等核心命令的实现。这里面我认为的一大难点是如何处理那些带着一长串 PowerShell 脚本的包,这些脚本基本都是用来做一些复杂的安装卸载工作,而且这些脚本的上下文多少都是依赖于 Scoop 本身的。Scoop 允许利用钩子脚本做一些额外的工作,却完全将其内部所有细节暴露了出来,导致不少 manifest 有点滥用 Scoop 内部功能的情况。在 Rust 侧除了考虑是直接 Command call 还是实现 native hosting 来执行这些脚本外,还得考虑如何提供这些执行上下文。另外 shimming 如何处理以什么形式分发也是个问题。

结语

Hok 毕竟是一个实验性质的个人项目,但我想把它分享出来,收获一些正向反馈也好负向反馈也罢,至少有些作用力。埋头搞事只有输入没有输出的状态挺让人难受的,是需要些正反馈。

上一次写博客还是世界巨变的几年前,博客至少还在线哈哈。这是我这些年以来第三次写关于 Scoop 的内容,按这个越写越长的趋势,不知道还会不会有第四次呢。

如果你对 Hok 这个项目感兴趣,或者有什么想法,欢迎借由各种途径留言。

回见。


  1. https://github.com/ScoopInstaller/Scoop/wiki/App-Manifests ↩︎

  2. https://github.com/ScoopInstaller/Scoop#inspiration ↩︎

  3. A command-line installer for Windows. ↩︎

  4. https://github.com/ScoopInstaller/Scoop/issues/4239 ↩︎

  5. https://github.com/ScoopInstaller/Scoop/commit/bea31895 ↩︎

  6. https://github.com/shilangyu/scoop-search ↩︎

  7. https://github.com/ScoopInstaller/scoopinstaller.github.io-indexer ↩︎

  8. https://github.com/ScoopInstaller/Scoop/blob/master/buckets.json ↩︎

  9. https://github.com/ScoopInstaller/Scoop/pull/5369 ↩︎

  10. https://github.com/ansuz/RIIR/issues/ ↩︎

  11. https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/ ↩︎

十款好评如潮的独立游戏与十张一定要听的原声带

2020-04-10 22:22:00

在起笔写这篇文章之时,我看了下我的 Steam 帐号最近的一次启动游戏距离现在又过去了一段 6 个月的时间。我果然并不是一个极其热衷于玩游戏的「游戏热爱者」—— 即使在网易公司熏陶了几年下来,我也并没有过上像同事朋友那样在朋友圈刷屏的“动森”生活。手游就更加谈不上来……

但就像每日三餐那样,我还有一个每天必定要汲取(至少 1.5 个小时)的精神食粮 —— 音乐,能够让我时常记着我那个玩了几百小时新手级别的《以撒的结合:重生》,还有几十个成就没有解锁,以及一堆还在愿望单里的好游戏。原来我这个早早过气的云村达人不止听电子、流行,还对游戏原声有强烈的嗜好,就像对 Future Funk、合成器浪潮那样。于是,听那些没时间、没队友玩的游戏的 OST,便成了我在通勤或者闲时“感受游戏”的方式。于是,又写音乐相关的内容了。


Minecraft / 我的世界

拿 Minecraft 的原声带作为这次“数家珍”的「开场白」,我想应该足以表达出《我的世界》的 OST,或者直接说 Minecraft 这游戏在我心中所处的位置。

如果不是 2013 年的那个夏天从我表弟那里偶然看到了它,我大概是不会认为我跟 Minecraft 还会在其它的某个时间某个地点相遇,继而也就不会发展到在之后的从某一天开始,为它而工作了一段时间并真正认识到了社会的残酷。

从 1.6.4 的强烈好奇心,到 1.7.2 的渐入佳境与 1.7.10 的坚守,再到 1.13 的新生;从难以“忘却的旋律”,到无人不晓的 HMCL,再到归宿 MultiMC;从开始一个人,到后来五个人,再到如今一个人。再怎么变,它就在那。

心烦意乱的时候,打开这张专辑随心地挑选一首,点击播放按钮,缓缓响起的音乐将思绪带回到了那些个没有顾虑的暑假。原来在那个时候的我,即使在一个偌大的没有尽头的世界里孤身一人,也不会因孤独而感到难受。而现在,却在人满为患得让人难受的地铁站里,用力地去尝试孤独。硬是要挑一首 Favorite 真难呀……

Minecraft was often my safe place through the struggles of my life during middle school. I will forever cherish the many memories I made through the game, and each of those memories is attached to a song from this album. —— hs_doubbing

最喜欢的单曲:Living Mice


Celeste / 蔚蓝

2018 年 IGN 的第一个 10 分满分游戏。《蔚蓝》虽然一直一直还在我的 Steam 愿望单里面躺着,但是它的原声带其实我已经听了不知道多少遍了。

之所以还没有买,一个原因是我实在是很不想它又像我的很多其它库存游戏那样,在入手后迫于生活,一直都是零小时的游戏时间。买了吃灰,没买至少能记着愿望单还有超想玩的游戏。而另一个原因是,我觉得它的原型 —— PICO-8 版的 Celeste 本身就极具“学习”价值,想先把原型钻透了。当时购买了一份 PICO-8 副本的其中一个目的,就是为了瞻仰这个当时在 PICO-8 卡带排行榜上排名第一的游戏[1]……

玩过《蔚蓝》的玩家会这样评价它:玩法与剧情及音乐的完美融合。但是我想说,不管是云通关亦或是没有通关,这些在游戏里是恰当到几乎完美的配乐,拿出来便组成了这张每一曲都是精品的专辑。没有拥有、没有玩过《蔚蓝》,不代表不能从这一张专辑中感受到那份溢出来的痛楚、焦虑与忧郁。

I don't even own the game, but this set of songs is so good. Aching and melancholic in all the best ways. —— Ben Pirrie

另外,说一个非常有意思的事,该《蔚蓝》原声带的作者 Lena Raine 在 C418 之后,也成为了 Minecraft 游戏原声的作者。Minecraft 1.16 的下界更新中,新增了几首下界 BGM 以及新的唱片,而这些音乐的作者正是 Lena Raine。

最喜欢的单曲:Resurrections


Into the Breach / 陷阵之志

久仰《Faster Than Light(超越光速)》的大名,也早早就买了,但是却说不出什么原因,一直就没去玩。也是 2018 年知道厂商推了这个新作,看宣传片一下子就爱了,而且事实上《陷阵之志》的口碑也非常的不错。我在发售没几天就入手玩上了,虽然后面没怎么玩,但开始那段时间我也是连续“烧”了好几天的脑子。

不过说实话,其实我并不是一个 SLG 游戏的爱好者,因为策略这个东西实在是太烧脑了,我静不下心去研究和规划。然后巧的是,战棋类型游戏因为耗时一般都很长,我玩得又非常少。但是我在玩《陷阵之志》时,却一点也不觉得有不耐烦的感觉。

现在回想起来很大程度上就是因为它的游戏原声,我经常能把游戏就放置在那里不动它,然后就光听着 BGM 去思考下一步下几步该怎么走好,又会去想世界末日怎样怎样。BGM 就这样一直在不断地放着,就很奇妙……

I hope you enjoy this unique take on the end of the world. —— Ben Prunty

最喜欢的单曲:Antiquity Row


CrossCode / 远星物语

《CrossCode(远星物语)》这款游戏其实早在 2015 年就开启了早期测试(Early Access)。历经了三年之久的测试,最终在 2018 年发布了正式版,并没有像 Steam 上许多早早开 EA 的游戏那样流产而终。(我突然想起了开发 11 年最终登录 Steam 的《表层指挥》)

能撑过 3 年才发正式版的独立游戏,看起来应该是特别受欢迎才是。但其实 CrossCode 作为开发商 Radical Fish Games 的首款游戏,由于缺乏较好的宣传,知名度一直都不怎么高。尤其在国内,中文名就有“交叉准则”、“交叉源代码”等民间译法。后来由良心汉化团队轻语工作室引入官方中文汉化和推广,才有了译名《远星物语》。

因为很早就发布了 Demo 和 Beta 版,并且开发团队也有定期在 Twitch 上直播开发进度并与玩家交流,CrossCode 在国外缓慢积累着人气。虽然我 Steam 上 CrossCode 的游戏时间很短,但是我花在与它相关的内容上的时间不少,其中就有好几次的 Twitch 直播开发的观看,也正因此才了解到这款游戏居然是用 JavaScript 开发的(用的是 nw.js + ImpactJS 引擎)[2]

Heavily inspired by Japanese game music of the late 1990s and early 2000s, CrossCode beautifully combines electronic beats, melodic tunes and epic JRPG scores to a unique and timeless blend. —— Deniz Akbulut

CrossCode 的原声带有着浓厚的旧式 JRPG 的味道,与游戏本身很是搭配。我相信,这种日系 RPG 与 2D 复古像素画面的结合,一定会让你情不自禁地去回想些什么……

最喜欢的单曲:Title


Risk of Rain / 雨中冒险

自 2014 年建号以来,我的 Steam 帐号已经因为各式各样的喜加一而积攒了好几百个游戏。这看似不多的几百,真要花时间进去游玩感受,会花很多很多的时间。于是,真正打通的游戏其实并没有多少个,涉及到多人游戏的,则更少。

而《雨中冒险》便是其中少数的一个。要非常感谢我那位愿意陪我一起玩 RoR 的好友,如果不是当时第一次费尽周折地用蛤蟆吃(Hamachi)连起了大内网下的两台电脑,开启了多人模式的 Risk of Rain,显著降低了游戏难度。我想我是不会在开始那个一个人玩时使劲死亡的状态当中坚持下来,并把游戏打通同时解锁全成就。

《雨中冒险 2》已经发布了,但是我跟其他许多玩过初代的玩家一样会觉得,还是那些曾经在我们不断“刷刷刷”的过程中,穿过我们的耳膜进入到大脑深处的初代游戏原声,更能够让我们去回想和思考更多…… You Monster...!

The first time I played this game I had some feeling of awe from wanting to explore an alien planet. Since then I have unlocked everything in the game, but the soundtrack NEVER felt old to me. —— nageneck

最喜欢的单曲:Coalescence


Undertale / 传说之下

截至本文出稿前,《传说之下》的原声带在网易云音乐上有高达 10.4W+ 的收藏量,所有原声曲的评论量加起来近 25W,受平台限制我无法统计出这些原声的累积播放量,但是否有播放量这个数据不重要。

如果你仍然还没玩过这款发布于 2015 年的游戏,不要紧,因为也许就像 Toby Fox 不介意说的 —— 这款游戏「不适合每一个人」,它仍然“小众”。

如果除了没玩过游戏本身外,你也还没有听过《传说之下》的 OST 的话,那么我还是相当建议你去找一个在线音乐流媒体平台(比如网易云音乐、Bandcamp),听一听 Undertale 的原声带。为了避免过度依赖他人,除了部分美术之外,Toby Fox 尽可能地独立完成了《传说之下》整个游戏,也就包括了这张原声带。这是真正意义上的“独立游戏”。另外,在听的时候有条件的话,不妨翻一翻曲子底下那些评论,看一看那些评论。

在那之后,你对自己对事物,也许,就会充满了决心❤︎︎

I love the game. I love the soundtrack. I love everything about it. 10/10 —— Derek Beck

最喜欢的单曲:Undertale


The Binding of Isaac / 以撒的结合

在相当长的一段时间内,《以撒的结合》系列游戏都会是我 Steam 帐号里总游戏时数最长的游戏,如此的肯定当然是 Minecraft 不在 Steam 售卖的缘故。截至撰文时《以撒的结合》与《以撒的结合:重生》总计 650 小时的时长,虽然与陈哥的约 9000 小时比起来小巫见大巫,但是也真的不少了。

本文更多的是推荐音乐,关于以撒本身游戏性这里不想多谈其实也无需多言,确实优秀。不过我还是想稍微推一推两篇跟以撒有关的文章 ——《好主意的结合》[3]及《Roguelike 到底是啥》[4],确实写得很有意思,觉得值得一看。

说回以撒的原声带,我相信很多从初代玩到重生的玩家,都会认为重生的配乐真没有初代的代入感那么强,即使重生的游戏性更高。这也是为什么我在玩重生时会关闭配乐(声效保留)播放外部音乐,而玩初代时会更愿意播放原声的原因。这真的不是游戏时长太长生厌的原因。

The spooky video game harpsichord dirge sound of $4cR1f1c14|_ is probably my most favorite chiptune from the modern era. —— Monsterberry

还要说的就是原声的作者 Danny Baranowsky,也就是很多玩家嘴里说的“D 胖” —— 我此前翻译过的文章《推特背后的 Notch 之道》里就有谈到 D 胖以及他跟 Notch 的小故事。Danny 还为其他一些知名的独立游戏出过 OST,比如《Super Meat Boy(超级肉食男孩)》以及《Crypt of the Necrodancer(节奏地牢)》。

最喜欢的单曲:$4cR1f1c14|_


Fez / 菲斯

谈到 Fez 与独立游戏,我想许多玩家都会想到《独立游戏大电影》这部纪录片。Fez 在这部纪录片里作为被主要展示的对象之一,自然给了很多观众深刻的影响。只是后面的事情,玩家们都知道 —— 因为卷入游戏门事件,作者 Phil Fish 抛弃了 Fez 续作的开发,出售了公司资产,宣布退出游戏界。

Fez 已然是游戏圈的瑰宝,以至于我觉得作者是否有回归已经不怎么重要了。人嘛,确实应该像 Notch 说的「我真的不在乎别人怎么看我,不管网上的人怎么说,我知道我自己是谁,我的朋友知道我是什么样的人」。

回到音乐上。除了为 Fez,原声带的作者 Disasterpeace,还有为另一款也颇受好评的独立游戏 ——《Hyper Light Drifter(光明旅者)》出过 OST。本文我在写到 Fez 这里时,其实有纠结过到底是选《Fez》的这张原声,还是选择《光明旅者》的原声,毕竟后者同样优秀。最后考虑到《Fez》游戏本身在游戏界更高的荣耀,以及其原声相对于《光明旅者》来说,没有那么的缥缈灰暗,可能是因为 chiptune 风格的缘故?如果感兴趣可以去听听《光明旅者》的原声,我想你会有个判断。受文章篇幅“同一作者/游戏只写一次”的限制,这里就不展开来讲。

不过,Fez 这张原声带真的蛮适合 Chill 或者要睡觉的时候听。那种未曾有过的,「小时候在沙发前对着“大方块”电视机玩游戏玩到累一头栽在沙发上,游戏音乐持续放着」的场景,一下子就出现在脑海里了……

Ethereal, haunting, the sort of music your NES listens to while it sleeps. —— Camden Power

最喜欢的单曲:Home


One Step From Eden / 伊甸之路

似乎我对这种带有浓厚日系风的游戏原声有一种特别强烈的感应,不论是前文讲到的《CrossCode》的 OST,还是这里的《伊甸之路》的 OST。

《伊甸之路》正式发布于 2020 年 3 月 26 日,离本文的出稿日其实相隔非常近。我也是最近才将这款游戏加入愿望单,所以更别提是否玩过。这张 OST 对我来说是非常的“新鲜”,以至于我有一种“这才刚知道就分享不太好吧”的感觉。但真的是第一次听就喜欢上了,甚至可以说本文起笔意愿的苗头就是想分享一下这张原声带。

回头去看,其实在 2019 年初网上就有《伊甸之路》相关的内容了,只是那时候包括我在内的许多人并没有了解到这款如此“快节奏”、“高难”的游戏。IGN 评出 9 分的好评,Steam 上的特别好评,预示着又一款优秀独立游戏的诞生。洛克人游戏我只玩过以前 NES 和 GameBoy 上的主系列,没有玩过洛克人 EXE 系列,所以说《伊甸之路》的玩法对我来说也是新鲜的。我想我会去玩的,毕竟我认为这张原声带本身就已经非常值得!

该原声带的作者 STEEL_PLUS(Keisuke Hayase)是日本人,为台湾 Rayark 社(音游《VOEZ》的厂商)和 KONAMI 社提供过游戏音乐[5]。《伊甸之路》的这张 OST 真的是宝藏,不要错过!

Would it be premature for me to judge this album as a best FM hybrid VGM OST of the year? —— tobokegao

最喜欢的单曲:Eden's Door


Terraria / 泰拉瑞亚

在《泰拉瑞亚》与《我的世界》两款游戏的重合玩家圈里,经常被提及的一个趣事便是这两款游戏的“商业互吹”佳话。在两款游戏各自的主界面上,你会发现它们在彼此推荐:

  • Minecraft: Also try Terraria!
  • Terraria: Also try Minecraft!

《我的世界》发布于 2009 年,《泰拉瑞亚》发布于 2011 年。如果从年份上来看的话,我相信大部分的“双厨”玩家都是像我这样是从 Minecraft 开始再到 Terraria。不过当然不能一概而论,肯定也有许多是后知后觉的玩家是玩过 Terraria 后再入的 Minecraft 圈。这种如此高调的“好基友一起走”的故事,在游戏界里应该是很难得一见的吧。这也是我把《泰拉瑞亚》的原声带放到本文最后一个的原因,开篇讲的是《我的世界》嘛~

无论在玩 Minecraft 亦或者 Terraria,我都特别喜欢在地下挖矿时的状态,尤其是泰拉。造成大部分游戏时间都是在地下,所以说这个下矿的魔力到底是出自哪里……

Just another zombie-free day of dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig dig. —— Banjo Kaboom

最喜欢的单曲:Underground


至此,十款独立游戏的十张原声带分享终于写完了。不清楚这其中是否有一些原声也是你特别喜欢的,又或者有让你眼前一亮的,还是说意犹未尽的。由于实在是受篇幅限制,看看光这十张碟就已经是万字长水文了。我不能再列出其它一些我也是非常喜欢的 OST,只能挑出了其中十张鲜明(你会发现十款游戏好像都是像素风)的,而把诸如《To the Moon(去月球)》、《Life Is Strange(奇异人生)》、《Ori and the Blind Forest(奥里与迷失森林)》等其它原声好碟继续藏在我的播放器里。

如果你也有非常喜欢的独立游戏原声带,咱们说说?


  1. Celeste - PICO-8 Forum ↩︎

  2. CrossCode Web Demo ↩︎

  3. pre-sence - 好主意的结合 ↩︎

  4. pre-sence - Roguelike 到底是啥 ↩︎

  5. 鋼鉄音源 ↩︎

除了 GitHub 官方移动客户端,还有这些东西值得注意

2019-11-16 00:15:00

不管是 GitHub Game Off,抑或是 HacktoberFest,都是 GitHub 近些年来,每年固定参与举办的活动。像去年的 HacktoberFest 我就有参与,虽然就是做个说难不难 PR 任务,相关文章 ——《历经数月,GitHub 章鱼猫寄来的 T 恤竟然……》,也是挺有趣的(今年的 HacktoberFest 我也参与了,Game Off 也挺想参与,但我一次 gamejam 都没玩过太水了)。

GitHub Universe 倒是一个更加正式的开发者大会,每届都会发布一些关于 GitHub 的新功能新特性,以及其合作友商的动(A)态(D)。不知道是不是我的错觉,总觉得被财大气粗的微软收购后的 GitHub,搞起各种事情来,就跟同样被微软收购的 Minecraft 搞起 MineCon 一样,越来越正式高端大气上档次了。不过也是因为这都已经是好几届过来的活动了,举办流程都颇为完善。


这次的 GitHub Universe 还是蛮有看点的,干货不少,不过好像都被 GitHub for mobile 这颗耀眼的星给掩盖了。毕竟是“千呼万唤”的官方移动客户端,大家终于等到了。其实除了 Official Mobile App 外,大会上还公布了好几个提升使用体验,值得被注意的新特性。

GitHub Universe Day One Keynote
GitHub Universe Day One Keynote

重点还是在第一天的 Keynote,分为了三大主题:代码上云(Code to Cloud)、日常使用体验(Daily Experience)以及社区(Community)。其中第二部分最多干货,第三块有点惊喜。

TL;DR

主题 产品/特性
代码上云 CI/CD 产品 Actions 及包托管平台 Packages 全体用户免费可用
日常使用体验 官方移动客户端(GitHub for mobile)
桌面客户端迭代(GitHub Desktop)
代码导航(Code Navigation)
改进的搜索功能(Code Search)
代码审查智能派发(Code Review Assignment)
定时提醒器(Scheduled Reminders)
重新设计的通知功能(Notifications)
社区 Sponsors 迭代,支持团队/项目发起赞助
存档服务(Archive Program)及北极代码库(Arctic Code Vault)

代码上云

早前发布的两款重磅产品,GitHub Actions 和 GitHub Packages 正式发布,所有用户免费可用。

Actions and Packages
Actions and Packages

我实际试用了 Actions,用起来确实是非常赞的。可算是“一站式”解决你的 CI/CD 需求了。


日常使用体验

这一部分更新很多,都是些很能提升效率和体验的改进或者新特性。

1. 桌面客户端更新

GitHub Desktop
GitHub Desktop

这一部分没什么特别亮眼的,就是比较常规的更新介绍。平时有使用 GUI 的话用起来应该会更加得心应手。

用 GitHub Desktop 你能省去很多 git 指令
用 GitHub Desktop 你能省去很多 git 指令

2. 代码导航 & 改进的搜索功能

上来首先就介绍了两个很实用功能点。第一个是「代码导航」,这个功能其实早前已经有在测试了,可以让你在浏览仓库代码时,方便地在函数等定义之间跳转。现在还可以查看到函数被引用的位置。GitHub 内部用于分析代码找出定义的类库 semantic 也是开源的,不过代码导航这个功能目前只适用于少有的 Python、Ruby 以及 Go 几种语言(GitHub 上的头号热门语言 Javascript 没被支持,哈哈)。

Code Navigation
Code Navigation

搜索功能被进行了一次大改,新的搜索终于可以做到大小写敏感、完整精确的匹配了。可以看下面两张对比结果感受一下。

改版前
改版前
改版后
改版后

搜索这个功能是我经常经常经常用到的。就我个人而言,这个真的是一个“杀手级”的更新,太实用了。搜索功能不好做一直都是技术圈里普遍认同的,特别是中文,更是如此。我有过一段学习自然语言处理的时光,里面的学问真的很多。

新的搜索功能可以在这里进行申请。

3. 代码审查智能派发 & 定时提醒器

这两个都是针对团队/组织的功能点。团队管理通过指定不同的代码审查派发模式,可以做到自动安排不同成员进行 Code Review,责任平均分配。

定时提醒则是整合 Slack。设置定时器后,可以按既定时间发送代码审查等信息到 Slack 中,更好地帮助团队进行工作。

4. 重新设计的通知功能

这个也是一个大的改进,在没有看到这个新设计的通知功能前,我是真没明着抱怨过 GitHub 的通知有多难用。但看到这个新界面后,我才从心里发出感慨 —— 原来通知页还能改得更好用的。直接在通知功能里面就能处理所有消息,终于不用点一下铃铛点一个消息,处理一下,再点铃铛处理其他消息了。

更方便地处理你的通知消息
更方便地处理你的通知消息

5. 官方移动客户端

最后一个就是很多用户期待已久的移动 APP 了,这个是官方的、全功能、免费的、原生的客户端。

GitHub for mobile
GitHub for mobile

作为 iOS 用户,我一直在用的是付费版的 CodeHub,听说 Android 用户比较多用的第三方客户端是 FastHub。这次 GitHub 推出官方客户端功能齐全,颜值看上去还高,想必会吸引到许多人去使用的。

随时随地处理你的拉取请求
随时随地处理你的拉取请求

而且还有暗黑模式,以及针对 iPad 设备优化了的展示形式。加上全功能的 Photoshop,iPad 的生产力这下真的是提升了很多很多了,虽然我朋友还是认为用来在被窝里看剧是最好的用法。

GitHub for mobile 的 Beta 测试可以在这个地址申请。


社区

除了介绍万年 Beta 期的 Sponsors 赞助功能开始支持组织/团队外,作为压轴公布了两个充满社会意义的项目 —— 存档服务(Archive Program)与北极代码库 (Arctic Code Vault)。

GitHub Archive Program 将会与斯坦福图书馆、互联网档案馆(Internet Archive)、 Long Now 基金会等合作,共同尽最大可能地为后代保留全人类的共同财产 —— 开源软件。

存档服务计划
存档服务计划

同时 GitHub 还将会在 2020 年 2 月 2 日为所有活跃的公共存储库进行快照备份,利用寿命可达 1000 年的特制胶片存储这些备份,运送并保存到北极一个地下废弃煤矿中。著名的国际种子基因库,就坐落在这里 —— 斯瓦尔巴特群岛。

北极代码库计划
北极代码库计划

最后,以北极代码库的宣传片做个结尾。挺佩服和欣赏这些大公司,不再只有顾着在商业上发展,更有在为人类社会未来做出贡献。即使有发展公共关系的成分在里面,不论如何也都是值得被支持的。SpaceX、Microsoft(Minecraft、GitHub)、蚂蚁金服……

结合响应式图片实现更好的图片懒加载方案

2019-07-24 15:53:00

作为在网页性能调优方面常被使用的技巧之一,懒加载或者说延迟加载是在谈及「前端性能优化」话题时不得不说的一项技术。

延迟加载是一种在加载页面时,延迟加载非关键资源的方法, 而这些非关键资源则在需要时才进行加载。—— Google Deveplopers

其实说到懒加载,这项技术已经不局限于单一的图片懒加载。除了图片外,懒加载也早就应用到了视频、音频等其它媒体资源的优化上,同时随着组件化思维的不断深化,本着懒加载的核心观点 —— 用户暂时用不到的资源又能延迟去加载的,就大可做成懒加载,WebComponent 也已经纳入到懒加载优化当中,现代前端各种构建工具、类库,都有做相关的优化,进行组件资源的按需加载。本文主要把焦点聚集到“图片”的懒加载处理上,做一番探讨。


原理

图片懒加载的原理其实非常简单:使用手段让 <img> 元素默认只载入非常小的图片内容,再利用 JavaScript 检查元素是否在视口中。 如果元素在视口中,则往其 src(有时是srcset)属性中填充真正所需图像内容的地址,加载真正的图片,实现延迟加载。

最早的也是最为有名与流行的实现方式看起来大致如下:

<img src="blank.gif" data-src="normal.jpg" alt="IMG" />

或者使用 base64 内联:

<img src="data:image/gif;base64,R0lGODdhAQABAPAAAMPDwwAAACwAAAAAAQABAAACAkQBADs=" data-src="normal.jpg" />

更懒的方式甚至会像下面这样省掉占位用的小图,当然这种方式是不被推崇的,留空的 src 属性会带来多余的、有害的网络请求[1]。你可以完全不写 src 属性,这样虽然不符合规范,但是也能工作,但不要留空 src[2]

<img src="" data-src="normal.jpg" alt="IMG" />

简单直接是这种方式能成为主流的原因。一张宽高仅有 1px 的透明不可见的 blank.gif,作为默认载入的占位图最合适不过,只有非常小的体积,网页初始化后迅速载入,而真正所需图像的地址被放到了约定的 data-src 属性里。当 JavaScript 代码检测到(通过页面载入/ 滚动)图片进入视口时,便将 data-src 中的值赋给 src 属性,替换掉 blank.gif,触发原始所需图片的加载。但这种简单有力的方式,有些弊端。

问题

首先,其中一个非常直观可见的问题是「布局抖动」。在图片懒加载时,由于默认载入的 blank.gif 图片几乎没有大小,真正的图片还没有经过浏览器载入,浏览器就无法确定需要给原始图片预留多大的位置。当真正的图片加载完成后,网页就会出现肉眼可见的布局抖动问题。虽然该问题实际对内容本身没有影响,但影响的是用户体验。

布局抖动(图源:Davide Calignano)
布局抖动(图源:Davide Calignano

解决办法是给图片元素设置已知的宽高(元素属性或者 CSS 样式都行),这样浏览器就可以根据宽高预留位置。

<img src="blank.gif" data-src="normal.jpg" style="width:800px;height:600px;" />

当然固定宽高大小对响应式布局来说不友好,于是就出现了更好的「长宽比盒子」技巧,根据图片宽高计算出图片的长宽比,利用长宽比与 Padding 边距设定占位元素。

<div class="aspect-ratio-box">
  <img src="blank.gif" data-src="normal.jpg" />
</div>
.aspect-ratio-box {
  position: relative;
  /* 根据宽高比计算设置 box 的 padding-bottom */
  padding-bottom: 62.5%;
}

.aspect-ratio-box img {
  position: absolute;
  height: 100%;
  width: 100%;
}

利用长宽比盒子,就能在响应式布局页面中,较好地处理图片懒加载布局抖动的问题。其实更多的长宽比盒子实际用例是用在响应式 video 元素上,关于长宽比盒子技术,可以进一步阅读 CSS-TRICKS 的文章 —— Aspect Ratio Boxes。布局抖动解决后样子如下:

占位防抖动(图源:Davide Calignano)
占位防抖动(图源:Davide Calignano

第二个问题是,这个图片懒加载的最原始实现,没有考虑到响应式图片Responsive Images)技术的到来。

<img srcset="small.jpg 100w, middle.jpg 200w, large.jpg 300w" data-src="normal.jpg" />

不过好在如今的各种 LazyLoad 类库,都已经针对这个问题做了优化,对响应式图片做了兼容。只需要将原来单一图片的 data-src 换成多张图的 data-srcset 即可。

<img src="blank.gif" data-srcset="small.jpg 100w, middle.jpg 200w, large.jpg 300w" />

data-srcset 列出可供懒加载使用的响应式图片,类库就会自动根据终端判断加载哪一张。

第三个问题是一个比较严重的问题,问题的根源在于懒加载中 src 属性的 blank.gif 这张占位图本身。为了压缩体积,降低初始载入时间,选用了一张非常小的图。默认载入的这张图虽然在用户浏览站点自身网页时,没什么区别,因为有懒加载脚本加持。但对于站外来说,这张小图带来的影响却是致命的,因为站外访问内容时,没有图片懒加载。

这就产生了严重的弊端,blank.gif 懒加载的方式,对站外 SEO、社会化分享以及 RSS 等稍后阅读工具非常不利。除了站点自身,几乎所有站外的工具服务,都不会刻意在引用你的内容时,帮你处理懒加载。于是,Google 爬虫爬取你的网页内容时,只会爬到带有 blank.gif 的“空白” <img> 标签,Google 帮你缓存的网页快照,都会是“缺图”的;同时,通过社会化分享功能将内容分享到如 Twitter、Facebook 等站外时,你的内容也会是“无图”的;更惨的是,通过 RSS、Pocket 等稍后阅读工具订阅了你站点内容的读者,他们在阅读工具里无法看到你文章中所有懒加载的图片,阅读工具里有的,只是空空的 blank.gif。

RSS 阅读器无法读取懒加载的原图
RSS 阅读器无法读取懒加载的原图

于是,问题来了之后就触发了又一次的迭代。尝试修正如下:

<img src="small.jpg" data-src="normal.jpg" />

最直接的更改,选取一张由原图经过压缩后的 small.jpg 作为懒加载占位图,也就是我们常说的 thumbnail。比如原图的分辨率是 2000x1000,体积是 2MB,经过压缩后的小图是 200x100,体积是 20KB。这样,初始加载这张缩略图仍然会比加载原图快速,同时站外 SEO、RSS 阅读器等也能读取到缩略图勉强解决看不到图的问题。这里我们暂且不谈用压缩的小图替换 blank.gif 时带来的额外工作量问题 —— 需要对每一张原图生成缩略图。该方案仍然不是一个完美的方案。


改进

先抛出一个自我打击:如果一直从“先人”创造的最早的图片懒加载方法入手,打死我也不会想到下面这个改进的方案。这里我先直接亮出改进方案的实例:

<div class="aspect-ratio-box" style="padding-bottom:56.25%;">
  <img
    src="original.jpg"
    srcset="thumbnail.jpg"
    data-srcset="small.jpg 100w, middle.jpg 200w, large.jpg 300w"
  />
</div>

这个改进的图片懒加载方案有包括但不限于以下一些特性:

  • 支持响应式图片(Responsive Images)、支持 webp 格式
  • 对响应式布局友好、支持移动终端屏幕旋转时进行图片大小重绘
  • 使用了 srcset 的特性,在主流的现代浏览器上有完整的支持,同时覆盖率高[3]
    • 在无法支持 srcset 或者懒加载类库不能生效的旧浏览器上可完美回退(Fallback)
  • 对站外 SEO、社会化分享、RSS 稍后阅读等工具服务友好
  • 占位用的缩略图可配置性极强,支持多种 placeholder 展示形式

以下来说说这个改进方案的魔法之处。还记得最早的图片懒加载方法吗?没错,就是为了避免初始加载原图,刻意src 属性的图替换成了空占位图/缩略小图,把原图放到约定的 data-src 属性的旧方法:

<img src="blank.gif" data-src="normal.jpg" />

既然直接在 src 属性上动刀会产生那么多问题,那么有没有什么办法能在不改动 src 属性的基础上,通过其它办法避免浏览器初始加载 src 属性中的原图,实现懒加载呢?有!下面说一下改进方案的魔术原理。

  1. 首先,带有 .aspect-ratio-box 类选择器的外层 <div> 标签,是用来保证懒加载图片在未载入原图时的占位大小的。这个其实是前文说的「长宽比盒子」技巧,解决布局抖动问题,这里没什么特别点。

  2. 改进方案中 <img> 标签中的 src 属性携带的仍然是原始大小的图片 original.jpg,确保了站外 SEO、社会化分享、RSS 等不会读不到原图。src 中就是未改动的原图,该读取原图的还是原图。

  3. 改进方案中 <img> 标签,利用 srcset 属性存放了一张缩略图 thumbnail.jpg。正是这张缩略图,有着与 blank.gif 异曲同工的效果 —— 能够阻止 <img> 初始化时 src 原图被加载。这便是改进方案的关键点

  4. 最后也是不可或缺的一步,为保证懒加载能正常完成整个逻辑,同时保证对响应式图片的支持,改进方案中 <img> 标签将原本应该在 srcset 中的响应式图片列表,存放在了约定的 data-srcset 中。当图片进入视口时,懒加载类库就会用 data-srcset 中的响应式图片地址列表,替换掉实际在 srcset 中的缩略图。最后浏览器再根据终端计算判断,选择加载 src 属性与 srcset 属性中的图片。

srcset 兼容性(2019 年 7 月)
srcset 兼容性(2019 年 7 月)

正是通过这么一连串与早期方案截然不同的流程,完美地避开了动刀 src 属性产生各种问题的同时,保证了懒加载功能的正常进行。这一切,全得益于 srcset 这个神奇的 HTML5 属性,以及其与 src 属性以及浏览器三者之间的完好协作。

后记

这么一个突破固有思维的改进方案,当然不是我一下拍脑子能想得出来的,毕竟我还是太菜了,有些东西得继续学习。能了解到这个技巧,全赖「业务是驱动技术发展的主要力量」—— 自从博客更换了系统后,饱受原始图片懒加载方式带来的,布局抖动与 RSS 阅读器不友好的困扰。一直在寻找更好的方案处理博客文章图片的懒加载,直到最近才从谷歌中搜索到 Ivo Petkov 写的懒加载类库 —— responsively-lazy,并进一步阅读了其相关文章,于是才了解到这么个创新想法。

在目前能找得到的绝大多数 LazyLoad JS 类库中,基本上都是改 src 属性的方案,也就存在改 src 产生的问题。Ivo Petkov 的方案真让我耳目一新,只可惜他写的 responsively-lazy 类库已经很久没有维护了,甚至还有使用老式的 getBoundingClientRect() 办法检测元素的出现,而不是像 lozad 那样使用精简有力的 IntersectionObserver API。但这并不妨碍新方案本身,创新想法上的发光点。

如果你还在犹豫是否使用响应式图片、图片懒加载的话,读到这里,就不要犹豫了。快试试吧,还能同时用上呢。

参考

其它前文未曾列出过的参考文章:

  • Lazy load responsive images - Ivo Petkov[4]
  • 图片懒加载从简单到复杂 - OnionTalk[5]
  • Sizing Fluid Image Containers with a Little CSS Padding Hack - Andy Shora[6]

  1. https://csspod.com/frontend-performance-best-practices/#src ↩︎

  2. https://www.quora.com/Is-it-OK-to-omit-the-src-attribute-from-an-IMG-tag-in-HTML ↩︎

  3. https://caniuse.com/#feat=srcset ↩︎

  4. https://ivopetkov.com/b/lazy-load-responsive-images/ ↩︎

  5. https://hateonion.me/posts/19jan30/ ↩︎

  6. https://www.andyshora.com/css-image-container-padding-hack.html ↩︎