跳到正文
Hacker News· Vieltrinker·· 5 小时前精选AI 评分75

Claude Code 为 Petroly 宣传视频加入动画时序单元测试

My video has unit tests

AI 导读

Vieltrinker 用 Claude Code 为燃油价格应用 Petroly 制作了一段 15 秒宣传视频,并把通知与价格动画的先后关系写成单元测试。

推荐理由

展示了将视频动画拆成可测试代码的完整流程,借助 Claude Code 迭代修改、定位单帧并复用同一渲染器生成多语言版本。

正文 · AI 翻译

今天我从 AI 智能体那里看到的最出人意料的单元测试,是一项检查:确保视频中的推送通知不会在动态图表里的价格达到目标之前出现。

一次单元测试。为视频而写。 🤯

我需要为我的副业项目 Petroly——一款燃油价格应用——制作一段 15 秒宣传片。Claude Code 直接写出了一个复用我的应用设计的 JavaScript 渲染器。之后,我可以用通俗的语言描述改动、观看效果并继续完善。

我从未打开过视频编辑器。

图表、通知、排序动画和转场效果全都存在于源代码中。我可以修改它们、审查差异,然后重新渲染。我们越深入开发,看到的可能性就越多:可以测试时序、从应用中提取文案,甚至制作四角透明的视频。

一段视频。用代码写成。

亲自运行渲染器

在观看成片之前,先试着拖动浏览。

下面看到的不是视频文件。你的浏览器运行着与生成视频相同的渲染器核心。拖动滑块到每个位置,都会让它生成一个新的 SVG 帧。更改语言、跳转到某个场景,或打开 SVG,即可查看代码生成的内容。

不是视频文件

拖动时间轴

你的浏览器正在运行与渲染这则广告时相同的 frame(t) 函数。拖动滑块到每个位置,都会让它生成一个新的 SVG。

正在加载渲染器…

Loading frame…每秒 25 帧

    Show the SVG for this frame
    没有预先渲染的图像。无论你选择哪个时刻,图表、通知、排序列表和二维码都会针对该时刻计算。选择一种语言,文案、货币、数字格式和二维码目标地址也会随之改变。

    视频是时间的函数

    其核心想法很简单:

    frame(timeInSeconds) // → an SVG string for that exact moment

    在第 2 秒,函数绘制开场画面。之后,它绘制价格图表突破目标以及通知出现的过程。再之后,加油站列表自行完成排序。每个位置、淡出和弹跳效果,都是对 t 进行的一点点算术运算。

    要把它变成视频,渲染器会请求 375 帧,即每秒 25 帧、共 15 秒,并将这些帧流式传入 FFmpeg。当流程处理简化为 writeToFfmpeg 后,循环如下:

    for (let i = 0; i < DURATION * fps; i++) {
      const pixels = await sharp(Buffer.from(frame(i / fps)))
        .ensureAlpha()
        .raw()
        .toBuffer();
      await writeToFfmpeg(pixels);
    }

    时间 → SVG → RGBA 像素 → FFmpeg → 视频。 sharp 会将每个 SVG 转换为像素。FFmpeg 对帧进行编码并添加音轨。

    如果希望通知晚些出现,只需修改源代码中的一个数字。如果我想检查某个时刻,就可以渲染那一帧。输入相同时,渲染器会生成完全相同的一系列帧。

    创意部分是一场对话

    第一次渲染给了我可供反馈的内容。从那以后,这个流程感觉熟悉得出奇:查看结果,描述需要更改的地方,再审阅下一个版本。

    有些要求非常细微:让排序控件上的点击提示多显示一会儿,将其稍微向左移动,移除多余的边框。另一些要求则改变了整个场景的运作方式。

    清晰展示选择。 加油站对比比通用的最低价格时间图表更有用。街道名称对演示帮助不大,而且难以辨认。现在,各行使用应用自身的价格类别和配色,从“非常便宜”到“非常昂贵”。

    让动作有明确原因。 一次清晰的点击会把排序方式从距离改为价格。最便宜的加油站移到顶部时,其他行会淡出,随后各行稳定下来,勾选标记才会出现。你能看清发生了什么,以及为什么发生。

    每次请求都变成一次代码变更和一个新的预览。我可以并排比较各个版本,并保留自己喜欢的部分。Git 可以把渲染器及其预览内容一并保存下来。

    智能体负责实现。我仍然得盯着进度并作出判断。有几个技术上正确的版本却显得过于繁杂。这一部分仍然需要我的眼力和审美。

    我的智能体为一个视频编写了单元测试

    这是我始料未及的部分。

    在价格提醒场景中,图中价格达到目标之前,不得出现推送通知。智能体将这一顺序转化为一项测试。省略准备代码和导入语句后,这个思路只需几行即可表达:

    test('the alert follows the price reaching its target', () => {
      const scene = SCENES.find((scene) => scene.kind === 'alert');
      const targetReachedAt = scene.from + ALERT_AT;
    
      const before = frame(targetReachedAt - 0.1);
      const after = frame(targetReachedAt + 0.1);
    
      assert.doesNotMatch(before, /id="push-notification"/);
      assert.match(after, /id="push-notification"/);
    });

    渲染器根据图中价格和图表的动画时序计算出 ALERT_AT。再加上场景的开始时间,就得到了目标达成的时刻。就在它之前,不显示通知;就在它之后,显示通知。每次编辑后,自动化测试都会检查这一顺序是否仍然有效。在逐步打磨视频的过程中,我甚至不必重播整段视频来验证时序。

    其他测试会检查二维码的布局和静区、列表开头是否按距离排序、结尾是否按价格排序,以及 375 个 SVG 帧中没有任何一帧包含 NaN、Infinity 或 undefined。

    甚至还有一项检查,确保最便宜站点的高亮效果要等到所有行都完成移动后才会出现。这就是一项用断言表达的设计决策。我用来保护应用行为的同一套工具,也能用来保护其视频的行为。

    对渲染后的像素也会进行检查。其中一项会确保徽标能够栅格化,而不只是检查 SVG 中是否存在图像元素。

    当初要求制作宣传视频时,我不会想到还要提出这些要求。让我意外的是,智能体会把它当作可测试的软件来处理。

    测试保护运行机制,观看保护整体观感。两者我都想要。

    我的另一个发现:视频也可以是透明的

    我知道 PNG 可以带有透明像素,却从未想过视频也可以如此。

    我希望视频看起来像我的 iPhone 显示屏,包括圆角。Alpha 通道会让圆角之外的区域变得透明,使视频能够融入任何背景。

    试着更改这一帧后面的背景:

    正在加载渲染器…

    以英语实时渲染,价格采用欧元。SVG 的透明圆角会露出背景,无需使用 CSS 进行圆角裁剪。选择“手机边框”即可在 iPhone 风格的外框中查看。

    诀窍在于,在整个导出过程中始终保留这种透明度。剪辑母版为 MOV 容器中带 Alpha 通道的 ProRes 4444。仅以 .mov 作为文件名并不能保证透明度;编解码器和像素格式也必须支持透明度。

    上方这一帧是由渲染器实时生成的 SVG。下方浏览器中的 MP4 视频在底部合成了一层背景。其中显示的棋盘格属于画面本身;剪辑母版则保留了实际的 Alpha 通道。

    圆角矩形在 SVG 中本来就是一个图形。保留周围的透明像素后,它就变成了一段可以叠放在另一个场景上的视频。我没想到能用同一套代码做到这一点。

    十二个本地化版本,一段动画

    当我认可德语版本后,翻译便成了渲染器的另一项输入。这十二个版本涵盖十种语言,其中英语版本分别采用欧元和英镑,葡萄牙语则分为欧洲葡萄牙语和巴西葡萄牙语版本。所有版本共用相同的场景和时序:

    • 本地化格式。 英国版显示每升便士和以英里计的距离,丹麦版显示丹麦克朗,巴西版显示雷亚尔。示意价格保持原有顺序,并在同一时刻越过目标价。它们不是实时报价,也不是货币换算结果。
    • 应用自己的措辞。 “价格提醒”和五个价格类别直接取自应用的翻译文案,因此这则广告与它所宣传的应用使用相同的语言。
    • 本地化链接。 每个版本都会选择相应的二维码目标地址;如果某个语言没有对应的翻译落地页,就使用英语页面。这些二维码在动画中保持相同的大小和位置。
    • 文字适配。 每条文本都使用实际字体测量,并且只在必要时才会缩小。“Très bon marché”比“Very cheap”长得多。

    译文仍需人工审读,版式仍需视觉审查。但如果我改进了排序动画,每个版本都能在下一次渲染时继承这项改动。我不必维护十二条彼此同步的独立时间线。

    以下是渲染后的视频及其配乐。选择一种语言,然后点击播放:

    The result, in 12 localized versions

    同一份代码。不同的受众。

    一则 15 秒的 Petroly 广告,由 JavaScript、SVG 和配乐在本地渲染而成。

    640 × 1,080 像素
    每秒 25 帧
    375 帧,一个渲染器

    Read the video description

    Petroly 标志点出燃油更便宜这一卖点。柴油价格图表下降至目标价格,随后出现价格提醒通知。五个以颜色区分的加油站类别从“非常便宜”到“非常昂贵”,初始按距离排序。轻点一次会将排序方式改为按价格排序。最便宜的加油站移到顶部,并显示对勾。结束画面说明该应用免费,可在 iPhone 和 Android 上使用。二维码全程保持固定。唯一的音频是器乐音乐。

    无需视频编辑器。无需购买编辑软件订阅。

    渲染技术栈包括 JavaScript、SVG、sharp 和 FFmpeg。这些渲染工具都是开源的。配乐和音效也采用免费许可。

    正如我在关于智能体软件开发的文章中所描述的,我已经使用编程智能体来构建自己的应用。这个项目拓宽了我对自己能用代码打造什么的想象。我甚至不用购买视频编辑器,也无需再支付一笔订阅费用。

    现在,我可以像审查代码差异一样审查视频改动,让测试判断通知是否来得太早,渲染出指定的一帧来检查布局,并从同一段动画生成另一种语言版本。

    我可以描述想看到的内容,看着它逐渐成形,并通过代码持续改进。我仍然很兴奋,视频竟然可以以这种方式运作。

    来源:Hacker News · pertiller.tech