9355电影的放映工艺:从一段代码到一部片
三部片没有一帧是拍出来的。这一页写的是通用流程:画面怎么算、为什么是 20 帧、字幕时间码怎么保证对得上、文件为什么要控制在这个大小。
一、画面:一个函数,输入秒数输出一帧
每部片都是一个 draw(ctx, t, W, H) 函数:给定第几秒,就画出那一帧。没有时间轴软件,没有关键帧面板,所有动画都是把时间代进公式。好处很直接——片长、分辨率、帧率这三个数可以随便改,重跑一遍就有新版本;想知道第 41 秒画面上有什么,不用拖进度条,读代码就行。
渲染时把这个函数放进一个真实浏览器的画布里按帧调用,逐帧截图成 JPEG 序列。用真实浏览器是为了拿到正常的中文字体排版和抗锯齿。
二、随机也要可重复
《格线上的雨》里一百多扇窗的亮灯时刻、240 条雨丝的速度和位置都是随机的,但用的是固定种子的线性同余发生器。这意味着每次重新渲染,这座城市亮灯的顺序完全一样。对一部要配时间码字幕的片来说这是硬要求:如果重渲一遍第 27 秒的画面就变了,字幕立刻就对不上。
三、为什么是 20 帧
三部片都是 20 fps。这类画面里没有快速平移和真实运动模糊,24 或 30 帧在观感上几乎看不出差别,但帧数直接决定要截多少张图和最终文件多大。20 帧让一分钟的片只需要 1200 帧,整站三部片加起来控制在十几兆,在手机网络下点开就能播。帧率是恒定的,每部片页面的参数表里写的 fps 都是 ffprobe 对成品文件的读数。
四、编码参数
编码用 libx264,CRF 25,High profile,yuv420p 像素格式,关键帧间隔 40 帧(两秒),并且把索引放到文件头部,这样不用下完整个文件就能开始播、也能直接跳到中间某一秒——文字稿的时间码跳转依赖的就是这一点。
| 片名 | 编码 | 分辨率 | 帧率 | 视频码率 | 音轨 | 大小 |
|---|---|---|---|---|---|---|
| 《格线上的雨》 | H264 High | 1280×720 | 20 fps | 约 739 kbps | 无 | 5.3 MB |
| 《夜班读字的人》 | H264 High | 1280×720 | 20 fps | 约 287 kbps | AAC 44100Hz 2ch | 3.2 MB |
| 《四拍呼吸》 | H264 High | 960×960 | 20 fps | 约 225 kbps | 无 | 1.6 MB |
五、声音:合成,不是录的
只有《夜班读字的人》有音轨,由四层信号叠加而成:82 赫兹正弦波作底、327 赫兹极小音量补一点空气感、粉红噪声铺底,再加一个每两秒触发、指数衰减的 1180 赫兹短响。全部由 ffmpeg 的信号源生成,没有采样素材,没有人声,也没有旋律。另外两部片的文件里根本没有音频流——页面上写「无声」时,指的是这个意义上的无声。
六、字幕和文字稿共用一份时间轴
字幕不是看着成片打的。渲染脚本里每个阶段的起止秒数被直接拿来当字幕时间码,WebVTT 文件和文字稿页面是同一份数据的两种输出:字幕要短到两三秒能读完,文字稿可以写长一点并且分出「画面」和「声音」两行。这样做的结果是时间码天然对齐,不需要事后逐条校对。
每部片输出两条轨:完整轨(含画面说明与声音说明)和精简轨(只保留片中真实出现的文字)。为什么无声片也要配字幕,在观看与无障碍说明里有专门一节。
七、海报也是代码
片目和卡片上的预览图是 SVG,不是视频截图:用和影片同一套配色和构图规则生成,几 KB 一张,放大不糊,而且不需要额外的图片请求去拉一张大图。
接着看
每部片各自的具体做法在影片页的创作手记里,比如《格线上的雨》讲了熄灯为什么要比亮灯慢,《四拍呼吸》讲了为什么缓动只用在吸和呼两个阶段。想直接读全片内容,去《夜班读字的人》的文字稿。