用代码写视频脚本:声明场景和元素(文字、清单、数字、图片、代码、对话、外部画面、粒子、自绘)和声音(配音、旁白、录音),render 一次生成带音效和配乐的 MP4,并在旁边输出联系表。零依赖:gdip 画帧、av.audio 合成声音、系统自带 Media Foundation 编码。
at 是命中时刻:slam 是落地那一帧(提前 0.3 秒起跳),其余入场是开始出现。音效和节拍都对齐它;入场/退场进度按帧的中点取样,所以命中帧就能看到入场的开头(与音效同帧)。draw(function(g,t,e){}) 写原生 gdip(见「自绘」),淡入淡出和入场动作仍由引擎负责。e.box(元素) 拿到声明元素的位置。生成视频是耗时任务,请放在后台线程运行。工具层见 av.video,声音见 av.audio。
import av.film;
var film = av.film(1920,1080,{duration=8;theme="midnight";bpm=120;music="tech"});
//① 品牌名在 1 秒砸下(自动 impact + 镜头震动),副标题逐字浮现,两行自动垂直居中
var s1 = film.scene(0,3);
var brand = s1.text("aardio",{at=1;style="hero";enter="slam"});
film.sfx(brand.at,"riser",{dur=1}); //蓄力音效的结束处对齐落地
s1.text("小巧、快速、原生",{at=1.5;style="subtitle";split="char"});
//② 标题在上,清单逐条滑入;**强调** 用强调色
var s2 = film.scene(3,6,{transition="wipe"});
s2.text("一个语言,**桌面全能**",{at=3.3;style="title";pos="top"});
s2.list({"窗口界面";"网页界面";"自动化"},{at=3.8;stagger=0.5;icon="check"});
//③ 数字滚动(tick… + ding),结尾粒子
var s3 = film.scene(6,8,{transition="flash"});
var n = s3.counter(0,6.5,{at=6.2;dur=1;format="%.1f MB"});
s3.burst("sparkle",{on=n});
var report = film.render("/film-demo.mp4");
return report; //报告:路径、联系表、码率、响度、耗时、lint
渲染结束后 film-demo.sheet.png 是 16 格联系表(带时间标签)。迭代时先看 film.report() 和 film.lint(),再用 film.frame(t,路径) 看关键帧,最后才整片渲染。
film.scene(start,end,{transition;bg;transitionSfx}) 返回场景对象,元素加在场景上。转场以场景起点为中心;转场期间上一场景的元素保持最后姿态,随转场离开。transitionSfx=false 只关闭这一场景的转场音效,不影响元素音效。
| transition | 时长 | 效果 | 自动音效 |
|---|---|---|---|
| cut(默认) | 0 | 硬切 | 无 |
| fade | 0.4 | 交叉淡化 | 无 |
| flash | 0.3 | 白闪,峰值处切换 | impact |
| wipe | 0.45 | 从左到右擦除,边缘带强调色光条 | whoosh |
| push | 0.45 | 新场景把旧场景推出 | whoosh |
| zoom | 0.5 | 旧场景放大淡出、新场景缩放淡入 | whoosh |
| glitch | 0.3 | 撕裂:旧画面按细条逐条换成新画面,少量条带错位(约 15Hz 换一次状态),不做色散 | glitch |
bg:"dark"(默认)、"accent"(强调色背景,文字自动用反色)、颜色(0xRRGGBB 或 "#RRGGBB")、渐变表(字段同 av.video.kernel.background)、图片路径(cover 铺满)或 {image=路径;grain=3}。主题的渐变背景默认加静态粗颗粒 ±3(消除 8 位视频的圈状色带),av.film(w,h,{grain=false}) 可关闭。图片背景默认不加颗粒(真实照片自带噪声);AI 生成的深色、平滑背景图要写 grain=3,否则编码后容易出现色带。
全片层:直接加在 film 上的元素跨场景、不随转场移动(进度条、水印),颜色跟随当前场景的背景(强调色背景上自动变深)。
av.film(width,height,{audio={...}}) 把“连续配乐、分段配乐、只用音效、少量手动音效”写成显式策略,不要修改 av.film.transitions 共享表来绕过声音。常用字段:
musicMode="sectioned"(默认)或 "continuous";连续模式传给 av.audio.music 的 mode="continuous",短页面蒙太奇不按每个场景替换 groove。sfxMode="auto"(默认)、"manual"(只保留 film.sfx 与元素显式 sfx)或 "none"。autoAccents=false 关闭配乐按段落自动加重音;musicEnergy 固定连续床层级;musicGain、musicFadeOut 控制配乐总线。duck=false 关闭音乐对音效/配音的自动闪避;也可传 {amount=...;voiceAmount=...}。beatOffset 同时用于音乐拍点、film lint 与报告。scene(...,{transitionSfx=false}) 只关单个转场音效。渲染报告的 audio.filmEvents 是影片层音效事件,audio.premixEvents 是加入配乐/配音后的预混事件;audio.eventsBySource、audio.buses 与 audio.bars 用于声音预检。响度和峰值仍是编码前 PCM 指标。
| 方法 | 说明 |
|---|---|
text(内容,opt) |
内容可为字符串或 function(t)(计时器等);**强调** → 强调色;'\n' 分行(双引号里字面的 \n 也按换行处理,lint 记 info);`split="char"\ |
list(条目,opt) |
icon(图标名,如 check、circle-check、star、rocket,见 av.video.text.icons)、stagger(默认 0.3;给数组 = 每项自己的偏移秒);块内左对齐、整块居中。row=true 横排;step=true 各项同时出现(暗色),按 stagger 依次点亮(强调色 + 轻弹 + blip),项间画箭头;border/bg(true = 主题色,或给颜色)让每项成为一颗圆角小卡(标签、步骤),radius 改圆角 |
counter(from,to,opt) |
dur(滚动秒数,默认 1)、format(printf,含前后缀);数字向左长、后缀不动。默认 hero 样式(粗体、光晕、微呼吸);界面里的小号计数写 style="body",配 x + align="right" 右对齐 |
image(路径,opt) |
width/height(只给一个则等比);radius 圆角(投影随之变圆);占画面 40% 以上且没有运动属性时默认 Ken Burns 慢推;非全屏图片带柔和投影(shadow=false 关闭;源图四角透明时默认不投影);版面放不下时自动等比缩小。写了卡片词(title/caption/bg/border/zoom)就是卡片,见「卡片」 |
code(源码,opt) |
代码面板(卡片):自动语法着色、逐字打字、focus 高亮行、edits 局部替换、行号;返回表的 .done 是打完的成片秒。见「代码、对话、外部画面」 |
chat(内容,opt) |
字符串 = 提示词框(打字、光标、发送键);数组 {{说话人;消息},...} = 对话卡(逐字,行间分隔线);返回 .done |
clip(来源,opt) |
外部画面:PNG 序列 "/res/seq/frame-*.png" 或视频文件;from(素材起点秒)、speed(0 = 定格取帧)。只接入、不采集,静音 |
| `burst("sparkle"\ | "confetti",opt)` |
draw(function(g,t,e){},opt) |
原生 gdip 自绘,按不透明画:淡入淡出、入场/退场与 opacity/scale/rotate 由引擎作用在整个元素上。给 width/height 是方框元素(g 原点在框左上角,参与自动排版,默认 fade),不给是整屏画布。e 提供画刷、文字、图片等工具,见「自绘」 |
通用选项:
at 命中时刻(默认场景起点);out 完全消失的时刻(默认场景结束,随转场离开;早于场景结束则播退场,退场结束于 out)。style:hero / title / subtitle / body / caption(字号按 1080p 设计,按短边自动缩放)。enter 见下表,默认由样式决定。内联 size/bold/font 可直接写在 text、list、counter、code、chat 上,盖过样式(size 和 x/y/width 一样是画面像素,不再缩放);不写 style 的文字用 body(主前景色)。pos = top / bottom / center / left / right(可组合如 "top-left");x,y 是锚点(中心,align="left" 时 x 是左边)。自动排版分三个区域:pos 含 top 的贴安全区顶、含 bottom 的贴安全区底,其余没给 y 的在中间(在同时出现的上下沿元素之间居中)。同一区域里时间重叠且水平相交的元素按声明顺序从上到下堆叠,不会叠在一起;时间不重叠的各自占同一位置(依次替换的文字)。全片层的上下沿元素(水印、进度条)会让场景内容避开它们。给了 y 的元素不参与排版。{{t,v[,ease]}}(元素本地秒,缓动写在段末点)或 function(t)。opacity 用于所有类型;x/y 动画用于 text/list/counter/image/方框 draw;scale 用于 text/counter/image/draw;rotate(度)用于 text/image/draw。burst 是定点爆发;不要给不支持的类型套用通用变换参数。sfx:false 关闭自动音效;音效名或 {名字;参数} 覆盖;列表 {{秒,名字,参数...},...} 按相对 at 的秒数放多个音效(写了列表就只有列表里的,不再加自动音效),例如 sfx={{0.4,"pop"},{1.2,"success",gain=-8}}。color 覆盖文字颜色。.at(code/chat 还有 .done);共享时刻用 aardio 变量(var drop = 14)或元素引用(brand.at、code.done、on=logo)。直接修改元素/场景字段后调用 film.invalidate() 使准备缓存失效;追加元素、切换草稿会自动重建。通常修改脚本后重新运行即可。colour=、duration=、给 image 写 size=)lint 报 warn 并给出近似名,不再静默忽略。| enter | 时长 | 命中时刻 at | 动作 | 自动音效 |
|---|---|---|---|---|
| fade | 0.5 | 开始出现 | 淡入 | 无 |
| rise | 0.6 | 开始出现 | 淡入 + 上浮 + 轻微放大 | swish(轻) |
| slide | 0.5 | 开始出现 | 从左滑入(清单默认) | swish(轻) |
| pop | 0.5 | 开始出现 | 弹出并过冲(hero 默认) | pop |
| punch | 0.35 | 完全可见的那一帧 | 重音在开头:放大的字迅速收回(快剪用) | impact(轻) |
| slam | 0.3 起跳 + 0.3 收尾 | 落地 | 从大加速砸下 + 落地挤压 + 镜头震动 | impact |
| type | 每字 0.045 | 第一个字 | 打字 + 光标 | typing |
| none | 0 | 出现 | 无动画 | 无 |
手艺默认值已写进预设:缓动永不线性;入场同时动 2~3 个属性;退场比入场快;逐字时单字入场缩短到 0.3 秒、间隔 0.04 秒;hero 入场后微呼吸;快剪场景用 punch(slam 前面不够 0.3 秒起跳时自动改为 punch,lint 记 info)。同名音效 60ms 内去重,次要样式(subtitle/body/caption)的音效降 6dB。
import av.film;
var film = av.film(1920,1080,{duration=6});
var s = film.scene(0,6);
//真实截图套上窗口外框(标题栏)和说明文字;卡片不动,卡内画面在 0.8~2.6 秒推近到底部
s.image("/res/aa-main.png",{at=0.3;width=1180;title="AA";caption="真实界面";
zoom={{0.8;1;0.5;0.5};{2.6;1.8;0.55;0.9;"easeInOutCubic"}}});
return film.lint();
title="…":卡片内部顶端的窗口标题栏(三个小圆点 + 居中标题 + 细分隔线),画面放在标题栏下面;有 title 没写 bg 时自动 bg=true。caption="…":卡片外面下方左对齐的小字(caption 样式、场景调色板),参与自动排版的占位。bg:true = 主题卡面色(colors.panel),或给颜色;border:true = 主题细线色(colors.line),或给颜色。radius 圆角(画框、裁剪与投影同步,默认 18);shadow 柔和投影(只画在卡外,半透明卡面下不透出)。code/chat 默认有卡面、边框和投影;方框 draw 只在有 bg/title 时投影。width/height 是整张卡(含标题栏);只给一个时内容区跟着画面比例,两个都给时画面等比放进(contain),空出来的地方露出 bg。显式 y 是卡片中心(说明文字在卡片下面另占位置)。zoom:卡内画面推近,卡片不动。数值 = 静态倍数;关键帧 {{本地秒;倍数;中心x;中心y[;缓动]},...},中心是画面上的 0~1 坐标。写了 zoom 就不再做默认的 Ken Burns。整张卡变大用 scale。panel、line、bar(标题栏底)、shadow、code={keyword;string;comment;number} 可在 theme.colors 覆盖(值可为主题色名)。import av.film;
var film = av.film(1920,1080,{duration=10});
var s = film.scene(0,10);
//提示词框:卡片出现后自动开始打字,打完发送键由暗转亮(typing + click 音效自动)
var ask = s.chat("把这周的销售表整理成图表",{at=0.3;width=1100;y=200});
//对话卡:说话人一列,消息逐字;上一句发完下一句才开始
var talk = s.chat({{"你";"按地区汇总"};{"AA";"好。读表、汇总、**从高到低**排。"}},{at=ask.done;width=1300;speed=16;y=470});
//代码面板:打完高亮第 2 行,再在本地 3.5 秒把升序改成降序(选框 → 旧片段上浮、新片段回弹,click 音效)
var code = s.code('var sheet = excel.Open("/周报.xlsx").Sheets(1);\nsheet.Range("A2:C13").Sort(sheet.Range("C2"), 1/*升序*/);',
{at=talk.done;title="AA 正在写代码";speed=60;focus=2;edits={{3.5;"1/*升序*/";"2/*降序*/"}};y=800});
film.sfx(code.done,"success"); //.done 是成片秒,声明时就能算
return film.lint();
code
speed 字符/秒(默认 50),0 = 直接出现。行间自动停 0.1 秒,缩进不计打字时间。打字起点自动:卡片出现后稍停(无入场 0.2 秒,有入场约为入场时长的 0.6)。focus=行号或数组:打完后高亮这些行、其余行变暗(speed=0 时开场就高亮)。lineNumbers:默认不显示;true = 1..n;数组 = 每行显示的号码(只摘几行关键行时写原文件的行号,如 {1;3;17;25},也可以是字符串)。edits={{本地秒;"旧片段";"新片段"},...}:片段必须在当前版本的源码里唯一,否则 lint 报 error(不猜)。多个 edits 按时间顺序作用在改过的源码上。chat
speed 字/秒(默认 12,中文节奏;英文、代码写大一些);对话卡行与行之间停 0.25 秒。消息支持 **强调**;说话人第 1 位暗色、其余强调色(color 覆盖其余说话人的颜色)。clip
前缀-*.png,按文件名里的数字排序,按影片帧率播放)或视频文件(sys.mediaFoundation.reader,承诺 MP4/H.264;外来录屏先单独测,读不出 lint 报 error)。from + (t - at) × speed:speed 默认 1,0 = 定格取 from 那一帧(缩略图);from 为负 = 先定在第一帧;播完定格在最后一帧(lint info)。同一文件的多个 clip 共用一个读取器;顺序播放不 seek(约 3ms/帧),往回或大跳才 seek(约 0.1 秒)。s.audio(来源,opt) 放一段外部声音。来源可以是音频文件路径(MP3、WAV、M4A 等,任意采样率)、av.audio.clip,或带 path 字段的表(可带 text:这段声音说的话)。av.film 只认音频,不管它是怎么来的:TTS 生成的、真人录的、别人给的 mp3 都一样。织影技能包的配音助手 autos.skills.codingFilm.tts 返回的就是这种表。
import av.film;
var film = av.film(1920,1080,{duration=10;music="tech"});
var s = film.scene(0,10);
var q = s.audio({path="/res/voice/q.mp3";text="只用一句话,能做视频吗?"},{at=0.8});
s.chat(q.text,{at=q.at - 0.3;width=1000;speed=0}); //台词进对话卡
var a = s.audio("/res/voice/a.mp3",{at=q.done + 0.4;text="能。一句代码,一支短片。"});
s.text(a.text,{at=a.at;style="title"});
s.text("织影",{at=a.done + 0.6;style="hero";enter="slam"}); //说完再砸品牌名
.at 是开口时刻,.done 是说完时刻(都是成片秒,首尾静音已去掉),.duration 是说话时长,.text 是台词。下一句写 at=v.done+0.3;画面文字写 s.text(v.text,{at=v.at}),或者用 on=v 跟着声音出现。台词应当是画面的一部分(标题、对话卡、要点),不另做字幕条。gain 再微调(dB)。v.word("代码"[,n]) 是说到这个词(第 n 次出现)时的成片秒,直接写进 at:s.code(code,{at=a.word("代码")})。来源表或选项带 words({{text;at;dur},...},at 是相对开口的秒数;织影的配音助手自动带上)时精确到词;没有词时间时按声音里的停顿与字数估算(实测与真实词时间相差 0.06 秒以内),lint 记 info。台词里找不到的词直接报错。v.words 是来源给的词时间。at(默认场景起点)、on、gain、text、words、name。film.audio 放在全片层(用法相同,比如一整条旁白)。audio.voices 是段数。e.time 对照 v.word(...))、视频原声(clip 静音)。内置元素画不了的画面(示意图、游戏画面、特殊动效)写成 draw(function(g,t,e){},opt):g 是原生 gdip 画板,坐标是设计坐标(av.film(宽,高),草稿半分辨率也不用改)。
opacity、scale、rotate 由引擎作用在整个元素上;透明度小于 1 时先画到透明图层再整体合成,重叠部分不会互相透出。局部的渐显用 e.progress 自己控制。width、height 是方框元素——g 的原点在框左上角,和图片一样参与自动排版(pos,或用 x/y 指定中心),默认 fade 入场,on= 粒子也能跟随它;不给是整屏画布(原点在画面左上角,默认不做入场,适合整场的画面)。title/caption/bg/border/radius/shadow)。有 title 时 g 的原点和 e.width/e.height 是标题栏下面的内容区,画面按卡片圆角裁剪;卡面、标题栏、投影不用自己画。同一个画面放进两种尺寸的卡片,用两个小包装函数各自 translate/scale 取景即可。t 是元素本地秒(at 时为 0)。帧是时间的纯函数:只依赖 t 和固定种子(e.seed、av.video.random),不要累积状态。sfx 列表配音:把时刻写成一张表(如 beats = {hit = 1.2}),画面和音效共用这一份。e.box(元素) 返回该元素在当前自绘坐标系里的矩形 {x;y;width;height};e.box(code元素,"片段") 返回片段字形的矩形(先找当前版本,再找原文)。取排版位置与脚本里的 x/y 动画,不含该元素自己的入场动作(要贴着它一起出现,就让它用 fade 入场);调用者自己的入场/缩放会被换算掉。g.setClipRect(x,y,宽,高[,模式])、g.setClipPath(路径[,模式]),模式 0 替换(默认)、1 交集、2 并集、3 异或、4 排除(只画在路径外,例如投影只画在卡片外)、5 补集。先 var s = g.save(),画完 g.restore(s)。e.image 在轴对齐时走像素内核,只按裁剪区的外接矩形裁;要裁成圆形等路径时用 g.drawImage。av.video 工具画的:~/lib/av/film/_.aardio 里的 paintCard、paintCode、paintChat、drawList 等函数可以当自绘范例读。e 的成员(颜色都可写主题色名 fg/soft/dim/accent/emph、0xAARRGGBB 数值或 "#RRGGBB"):
| 成员 | 说明 |
|---|---|
brush(颜色[,不透明度])、pen(颜色[,宽度[,不透明度]]) |
画刷、画笔,回调结束自动释放(自己创建的其他 gdip 对象仍要 delete) |
text(文字,x,y[,样式名或选项]) |
主题字体与字号的路径文字(运动不抖,排版自动缓存)。x 是锚点(默认居中,align="left" 时是左边),y 是垂直中心。选项:style、size、color、emph(强调色)、font、bold、align、alpha、maxWidth、lineHeight、reveal(0~1 逐字显示)、cursor(光标)。返回排版(width、height) |
image(路径,x,y[,宽[,高[,选项]]]) |
x,y 是左上角;只给宽或高时按比例;选项 opacity、radius(圆角)。缓存;轴对齐时用像素内核重采样(运动不抖),遵守 setClipRect 矩形裁剪 |
glow(x,y,半径[,颜色[,不透明度]]) |
柔和光晕(halo) |
progress(a,b[,缓动]) |
本地时间在 [a,b] 内的进度 0~1,可选缓动名(如 "easeOutCubic") |
color(颜色[,不透明度]) |
转成 ARGB(给渐变画刷等用) |
box(元素[,片段]) |
声明元素(code 可给片段)在当前坐标系里的矩形 {x;y;width;height} |
t、time、width、height、theme、seed |
本地秒、全局秒、框(或内容区、画面)大小、当前背景的调色板、固定种子 |
import av.film;
var film = av.film(1920,1080,{duration=5});
var s = film.scene(0,5);
//声明元素负责内容:代码卡片(fade 入场,自绘才能贴着它),本地 2.2 秒把 12 改成 24(click 音效自动)
var beats = {edit = 2.2}; //一份时刻:代码、自绘、音效共用
var code = s.code('var radius = 12;\ng.fillCircle(brush, x, y, radius);',
{at=0.3;width=1100;size=48;speed=0;title="局部修改";enter="fade";edits={{beats.edit;"12";"24"}}});
//自绘负责这支片独有的画面:一道光环在命中前收拢到被改的数字上(辉光笔:宽的半透明笔垫在细线下面)
s.draw(function(g,t,e){
var b = e.box(code,"12"); //片段在当前坐标系里的矩形;改完后按原文位置找
var p = e.progress(beats.edit - 0.7,beats.edit,"easeInCubic");
if(p <= 0 || p >= 1) return;
var r = 230 - (230 - b.width) * p;
var cx,cy = b.x + b.width / 2,b.y + b.height / 2;
g.drawEllipse(e.pen("accent",14,0.16),cx - r,cy - r,r * 2,r * 2);
g.drawEllipse(e.pen("accent",2.5),cx - r,cy - r,r * 2,r * 2);
e.glow(cx,cy,r * 0.8,"accent",0.2 * p);
},{at=0.3;sfx={{beats.edit - 0.7,"swish",dur=0.7}}});
film.frame(2.2,"/check/draw.png");
return film.lint();
theme:"midnight"(深蓝 + 青,默认)、"punch"(黑 + 黄,快剪)、"light"(浅色 + 蓝),或在内置主题上覆盖:
import av.film;
var film = av.film(1920,1080,{duration=6;theme={
base = "midnight";
colors = {accent = 0xFF64F4DC}; //按键覆盖(颜色为 0xAARRGGBB)
styles = {hero = {size = 126;enter = "punch"};side = {size = 108;bold = true;enter = "rise"}} //逐个样式合并字段;可新增样式
}});
return film.theme.styles.hero.bold; //未覆盖的字段沿用 midnight
覆盖作用在副本上,不会改动 av.film.themes。bg 也按键覆盖(dark、accent 是背景规格表)。不写 base 时是完整主题表(结构同 av.film.themes.midnight:colors、bg、font、styles)。字体默认微软雅黑 UI。
配乐 music:风格名("tech"、"upbeat"、"ambient"、"lofi",由 av.audio.music 现场合成)、av.audio.clip 或音频文件路径(MP3、M4A、WAV 等,任意采样率,自动重采样;只取用得到的长度,响度自动对齐内置配乐,比影片短时 lint 提示)。音乐总线在音效处自动闪避,整体响度归一到 -16 LUFS。
用风格名时,配乐按时间线编排:
bpm,没给就用风格默认值。实际的段落强度和终止和弦时刻会写进报告 lint 的 info 行。
import av.film;
var film = av.film(1280,720,{duration=4});
var s = film.scene(0,4);
s.text("标题",{at=0.5;style="title"});
s.text("这是一行说明文字",{at=0.8;style="body"});
//渲染任意单帧(给路径则保存 PNG,否则返回 gdip.bitmap)
film.frame(1.5,"/check/f1.5.png");
//联系表:默认均匀 16 格,也可指定时刻
film.sheet("/check/sheet.png",{times={0.5;0.7;1.0;2.0}});
return {
timeline = film.report(); //场景、元素(at→out、入场、样式)、音效列表
lint = film.lint(); //{level;t;msg} 数组
}
lint 检查:场景重叠、转场不在拍上(指定 bpm 时)、at 不在场景内、零时长元素、文字超宽缩小、图片放不下缩小、找不到图片或背景图、配乐来源(找不到或读不出配乐文件、风格名写错,error 级;配乐文件比影片短,warn)、阅读时间不足(汉字 1、拉丁词 2 个单位,>8/秒)、音效过密(0.5 秒内 >6 个,数字滚动的 tick 不计)、整屏自绘给了 x/y、未知选项(warn,附近似名)、code 的行放不下(warn,右端淡出)、提示词/对话放不下、edits 片段不唯一或找不到(error)、找不到序列帧或视频(error);自动处理的也记一笔 info:slam 改为 punch、字面 \n 按换行、clip 播完定格。
film.render(路径,opt) 返回报告:path、sheet、frames、duration(按帧数量化后的秒数)、fileSize、mbps、seconds(总耗时)、timings(prepare/audio/draw/write/flicker 毫秒)、audio(lufs、truePeak、events)、flicker(闪烁检测,见下)、lint。opt:draft=true 半分辨率草稿;quality(同 sys.mediaFoundation.writer);sheet=false 不出联系表;flicker=false 不做闪烁检测。
有 error 级素材/配乐问题时停止渲染。视频帧、音频与 MP4 收尾错误会向上传播;先写同目录的 .rendering.mp4,全部编码成功后才替换目标文件,失败保留旧 MP4 与诊断文件。
报告边界:audio 测量编码前 PCM(有声混音目标 -16 LUFS、真峰值上限 -1.5 dBTP;静音标记 -200);AAC 编码后峰值可能升高。flicker 检查源帧,不是最终 MP4 的全面回读验收;不自动评判文案、审美或所有重叠。联系表、连续帧与用户看片反馈仍各有作用。
视觉模型看联系表只作补充:颜色、角度、对齐这类问题先用 film.frame(t) 返回的位图 getPixel 确定性地检查。
render 逐帧检查“单帧闪现”:某一帧在局部突然比前后两帧都强(或都弱),下一帧又退回去,例如入场首帧误用了完全不透明。这种问题只持续 33ms,联系表和单帧截图都看不出来,人眼却能察觉“闪了一下”或“原地抖一下”。
检测结果以 warn 级别写进 lint,写明帧号、方向和可疑元素(哪个元素的入场首帧或退场末帧),例如:
[warn] 7.50s 疑似单帧闪烁(第 225 帧突然变强,强度 1.9):list(第 2 条,slide 入场首帧);逐帧条带见 promo.flicker.png
同时在 MP4 旁输出 *.flicker.png 逐帧条带:每个事件一行,显示闪现帧及其前后各 2 帧,闪现帧描红边。报告的 flicker 字段为 {events;maxScore;strip}:maxScore ≥1 就会报告,用户验收过的成片最高约 0.8。强度是局部对比度变化超过阈值的倍数,不代表观感轻重:小字的闪现强度低,但同样要修。
有意的逐帧急变不检查:
function(t) 动态文字、预设声明的 fast 窗口(punch 命中后急缩、slam 起跳急缩)。快速运动不报:规则纹理(城市窗格、栅栏)快速平移、物体高速划过时,格子统计量会随相位起伏,检测器用平移复核把它们归为运动(报告不计)。单帧丢失、单帧错位(原地抖一下)前后帧不动,照样报。小物体边转边飞、每帧移动超过画面短边约 9% 时平移解释不了,可能误报,看逐帧条带确认即可。
自定义预设如果故意做单帧急变,给预设加 fast = {起,止,区域倍数}:起止是相对命中时刻的秒数,区域倍数乘在元素框上。检测每帧约 2~4ms。判据的原理和独立用法见 av.video 的“闪烁检测”一节。