aardio 文档

av.film

用代码写视频脚本:声明场景和元素(文字、清单、数字、图片、代码、对话、外部画面、粒子、自绘)和声音(配音、旁白、录音),render 一次生成带音效和配乐的 MP4,并在旁边输出联系表。零依赖:gdip 画帧、av.audio 合成声音、系统自带 Media Foundation 编码。

生成视频是耗时任务,请放在后台线程运行。工具层见 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 共享表来绕过声音。常用字段:

渲染报告的 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 提供画刷、文字、图片等工具,见「自绘」

通用选项:

入场预设

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。

卡片(image / clip / 方框 draw / code / chat)

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();

代码、对话、外部画面

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

chat

clip

声音(配音、旁白、录音)

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"});  //说完再砸品牌名

自绘(draw)

内置元素画不了的画面(示意图、游戏画面、特殊动效)写成 draw(function(g,t,e){},opt):g 是原生 gdip 画板,坐标是设计坐标(av.film(宽,高),草稿半分辨率也不用改)。

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。

用风格名时,配乐按时间线编排:

实际的段落强度和终止和弦时刻会写进报告 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。强度是局部对比度变化超过阈值的倍数,不代表观感轻重:小字的闪现强度低,但同样要修。

有意的逐帧急变不检查:

快速运动不报:规则纹理(城市窗格、栅栏)快速平移、物体高速划过时,格子统计量会随相位起伏,检测器用平移复核把它们归为运动(报告不计)。单帧丢失、单帧错位(原地抖一下)前后帧不动,照样报。小物体边转边飞、每帧移动超过画面短边约 9% 时平移解释不了,可能误报,看逐帧条带确认即可。

自定义预设如果故意做单帧急变,给预设加 fast = {起,止,区域倍数}:起止是相对命中时刻的秒数,区域倍数乘在元素框上。检测每帧约 2~4ms。判据的原理和独立用法见 av.video 的“闪烁检测”一节。

性能(本机实测)

Markdown 格式