fuheshun-auto-clip
Skill1 starsChanged 48 days agoMITView on GitHub
福和顺出品。把一条竖屏口播原片剪成抖音风成片的 Python+PIL+ffmpeg 流水线:词级对齐花字、短句分页字幕、模版卡片、量化数据条、章节进度条、圆形头像、按场景虚化、语义化音效、混音提速一条龙,不依赖剪映 GUI。覆盖数字人换声重剪、网上抓配素材(含相关性闸门与水印清理)、素材合规目检、图片素材升级视频素材。含全套渲前自检与交付前三查脚本。触发词:自动剪辑、口播剪辑、视频剪辑流水线、加花字、字幕分页、模版卡片、数据条、去水印、数字人换声、抖音风成片、素材抓配、成片核验。
- Installs packages
Files
福和顺·自动剪辑
把一条竖屏口播原片剪成抖音风成片:词级对齐花字 → 素材卡/字幕条编排 → 圆形头像 → 背景按场景虚化 → 语义化音效 → 混音提速。全程 Python+PIL+ffmpeg,不依赖剪映 GUI。
福和顺主体口径(默认账号)
用户没有另行指定时,默认服务对象是福和顺——企业 AI 增长体系共建与陪跑服务商,内容面向传统企业老板与管理者。
剪辑时默认遵守三条硬线(与福和顺视频号口径一致):
- 不点客户名——成片里出现客户一律改行业描述:一家连锁餐饮 / 一家药房连锁 / 一家制造业集团 / 一所民办学校。原片里如果有客户名口播,需在字幕层做替换提示。
- 不提度量型数值——规模、增长、账号数、曝光量、门店数、年限这类数字,默认不做数据条;「三步」「三类」这种结构性数字可以保留。
- 不做自曝——不揭自家内部账与投入产出;涉及自家做法的表述改成一线观察口径。
用户明确要求做数据条、或素材本身就是对外可公开的数据时,以用户指令为准,但要在交付时提醒这三条。
运行环境要求
本技能纯本地执行、无需 Key、不向任何服务端回传。以下依赖需自行准备(不随包分发):
| 依赖 | 用途 | 说明 |
| Python 3.10+ | 全部脚本 | — |
Pillow numpy | 图像合成 | pip install pillow numpy |
ffmpeg / ffprobe | 解码、抽帧、编码、混音 | 需在 PATH 上 |
faster-whisper | 词级转写 | 模型约 460MB,首次运行自动下载;下载受限时需自行放置权重 |
| 系统中文字体 | 字幕与花字渲染 | 渲染前确认字体存在,否则会缺字 |
详细部署与目录说明见 references/SETUP.md。
开工前先做的两件事
ffprobe源视频时长 vs 音轨时长。 不等长就直接上「素材与配音不等长」一节的重定时,不要等用户报"结尾没说完"再补。实测 30.00s 源 + 31.79s 配音,漏判会导致两轮返工。- 确认是"保留原声"还是"换文案"。 两者互斥:换文案后数字人原动作说的还是旧稿,嘴型不会自动跟着换。要换文案就先讲清这一点,并给出"要不要上 Wav2Lip 重驱动"的选项,不要默默交付。
标准开工口令
推荐用户按这个模板下指令,一次到位:
用 <数字人/口播视频路径> 剪一条视频,
文案音轨用 <音频路径>(原视频的声音直接换掉),
按自动剪辑的流水线剪,配图素材你从网上找并匹配,
只要成品。这句话里 5 个约束缺一不可,收到后依次落实:
| 口令片段 | 落实动作 |
| 数字人视频当画面源 | 只取画面;源音轨整条丢弃 |
| 文案音轨用 X(原声换掉) | 音轨 = X;DUR = ffprobe(X),不要用源视频时长 |
| 按流水线剪 | 走六步:转写→三层视觉→人物→音效→混音→核验 |
| 素材从网上找 | 见「素材自动抓配」一节 |
| 只要成品 | 中间过程不汇报,直接交付成片 mp4 |
迭代口令对照表
用户常常说得极简、或直接发截图代替描述。下表是「这么说 → 真实意图 → 改哪里」的对照,按它执行可以省掉大量返工。
| 用户说 | 真实意图 | 改哪里 |
| 「这样全景的就不用虚化了」+ 截图 | 只针对截图那一类画面,不是全片 | 先定位该帧属于哪一段(金句条?纯字幕?钩子?),只改那一段 |
| 「所有全景人像画面都不用虚化」 | 仍可能是"分场景"而非"全局关闭" | 按场景开关实现,别全撤 |
| 「上一版圈有头像的统一保留」 | 圆形头像是定稿审美,不要删 | 有金句条 = 虚化 + 头像,必须恢复 |
| 「结尾没说完」 | 成片时长 < 配音时长,最后一句被砍 | 查 duration 与 -shortest,上重定时 |
| 「结尾不要定格,要自然」 | 拒绝定格末帧 | 不要复用末帧 |
| 「最后一秒卡住 / 衔接不自然」 | 画面整体变速,不要拼接、不要补帧、不要倒放 | setpts=PTS/rate 全片重定时 |
| 「字幕需要排版断句短一点」 | 结构问题(整段一次性铺满),不是字号问题 | 改短句分页 |
| 「排版模式可以参考 <参考片> 这样分成排版」 | 要结构化版式(章节条/主标题/编号/进度条) | 抄参考片的信息层级,但必须保留数字人头像,别做成纯文字页 |
| 「头像出现时间长一点,需要符合口播语境」 | 头像窗口从"金句条那一瞬"扩到整个数字人段 | 头像窗口覆盖整段;上升沿 0.18s |
| 「这个字需要不模糊」+ 截图 | 该文字元素的渲染缺陷,不是字体不好看 | 先单独把这个元素渲染出图定位;数据条见对应三坑 |
| 「视频剪辑得很清晰,素材动画也很流畅继续保持」 | 基线达标确认,不是新需求 | 别改已达标的部分,只动他点名的 |
| 「音频换成这个」 | 换文案重建,不是改音频轨 | 走「换文案重建检查清单」逐条打勾 |
| 「素材一样云端搜索配置」 | 沿用抓配方式,但类别按新文案重列 | 别复用上一版的图,语义对不上 |
| 「素材有水印的要去掉」 | 全量清理水印 | 先物理预裁切,再考虑去水印 |
三个高危歧义的处理规则
1. "全都不要虚化" → 实际是"分场景",不是全局开关。
撤掉全部虚化后,用户通常会立刻纠正:「上一版圈有头像的统一保留,只有需要人物全景出现的画面才不用虚化」。 规则:收到"全都不要…"类的视觉偏好,先按"分场景"实现(找一个自然的开关变量),再让用户确认,比全量撤掉再返工便宜。定稿开关如下:
| 场景 | 背景 | 头像 |
| 有金句条压屏 | 重度虚化(人像糊成色块) | 圆形头像(0.80 倍,(540,380)) |
| 纯字幕段 / 开场钩子 | 全景原图直出,不虚化 | 不叠(否则"头上再叠一个头") |
| 素材卡 / 结尾 CTA | 与人物无关,照旧 | — |
2. "结尾不要定格" ≠ "接缝平滑就行"。
做过"片头帧循环 + 0.28s 交叉溶解",接缝肉眼完全看不出来,用户下一轮还是报「最后一秒卡住」——因为数字人片头那批帧本来就还没开口说话,补出来的 1.5s 运动量 0.00–0.17(真定格)。 教训:接缝平滑 ≠ 有运动。判"卡没卡住"只能量运动量,不能看接缝。
3. "不要虚化" 的边界是"这一帧有没有金句条",但绝不能做成硬开关。
布尔开关会让背景在窗口到点时"闪一下",两条之间的空档再闪一次。必须走连续权重曲线(见「人物处理」一节)。
流水线六步
1. 词级转写 → 时间锚点
- 用 faster-whisper 本地转写出词级时间码。
- 所有花字/卡片/音效的进出点都锚定到关键词(
word_time(phrase)取词级区间),不要手拍时间。 - 模型下载 401/断流时用 HTTP 断点续传本地加载。
- 中文 whisper 是逐字切分的,错别字修正(如
长粉→涨粉、同胞→通报、红灾→洪灾)如果写在单字上永远匹配不到,必须在累积串上做:逐词累加成acc后clean(acc)再比对。 写错的表现是:3~4 个锚点全部 miss、那些卡片/花字全堆到片头,而不是报错。 - 锚点期与显示期可以分开(锚点用转写原始词、显示层单独映射)。更好的做法:直接改词表,让锚点和显示共用一份干净文本,不用维护两张表。
- 改完锚点必须打印命中表逐条确认(
词 → 起止时间),有 miss 就先修再渲染。
纠错实现的两条硬规则
word_time只在同一个 segment 内累积。跨段累积会让长串把锚点锚到偏早的时间(实测偏早 3s)。第一轮精确等值、第二轮才在同段内做包含匹配。- 改词短语:等长时逐字替换,不等长才合并。整段合并会把锚点词吞掉:把
在说前路透和蓬勃整体换成再说路透和彭博,会让路透不再是独立词,锚点漂移 0.76s。拆成在说前→再说+蓬勃→彭博或走逐字路径后就精确了。 判据:len(原文)==len(替换文)且逐词都是单字 → 一字换一字,保词边界。 逐字替换时记得把原词尾部的标点带上(证,→证+,),否则字幕丢标点。
2. 视觉体系(三层)
- 金句卡:渐变大字 + 描边阴影,多种入场动画轮换(弹性/左滑/上滑/翻转),显示期间隐藏底部字幕防撞车。
- 素材卡:卡片风,黑底霓虹描边圆角卡 + 年份角标 + 画中画 Ken Burns。
- 字幕条:胸口弹出 → 上移到眼镜位 → 素材卡跟进 → 随口播一起上飘淡出。
- 三卡排队编排:第一张独占中央 → 第二张进场时前一张缓动上移让位 → 三卡同屏错位倾斜堆叠(倾角 -5/+3.5/-3°)→ 随口播统一上飘消失。消失点 = 下一个金句锚点前 0.06s。
- 接力规则:同位置两条字幕撞车时,旧条在上移淡出(y-=240,0.45s),新条照常进场。
3. 人物处理(关键审美)
- 纯字幕时刻:人物收成圆形头像(完整头部入圆:发顶到下巴都可见、脸占圆约 45% 宽、白色细环 + 投影)。
- 裁切框必须实测,禁止目测:肤色连通域会被暖色背景污染(bbox 撑到全帧);正确做法是多帧累计的密度轮廓——按行/列统计严格肤色占比,从峰值向两侧扩到累计 55%,找高密度核心区。目测估算会得出整圆只有额头的结果。
- 虚化与否由"这一帧有没有金句条压屏"决定,不是全片统一:
- 有金句条 → 重度虚化背景(人像变成纯色块)+ 圆形头像出镜,注意力全给文字;
- 纯字幕段 → 人像全景原图直出,不虚化、也不叠头像(构图本来完整,再糊一层是过度处理,叠头像会出现"头上再叠一个头");
- 开场钩子段(人物大特写推近 + 大字)也归"全景直出",保持清晰。
但绝对不能做成硬开关——必须用连续权重曲线
bw[t] ∈ [0,1]: 背景 =Image.blend(srcf, blur_full(srcf), bw),头像alpha=bw。 否则会在金句条窗口到点时背景"闪一下",两条之间的空档还会再闪一次。 权重要渐入 0.35s / 渐出 0.30s 双向平滑,且对每条按"存在强度包络"取 max 后再平滑:def bar_env(t, b, fin=0.40, fout=0.35): # 单条金句条的存在强度 if t < b['s'] or t >= b['e']: return 0.0 a = min(1.0, (t - b['s']) / fin * 2.2) if b.get('handoff') and t >= b['handoff']: a *= 1.0 - min(1.0, (t - b['handoff']) / 0.45) a *= min(1.0, (b['e'] - t) / 0.10) # 接力条末尾兜底归零 else: a *= min(1.0, (b['e'] - t) / fout) # 无接力条渐隐,别硬消失 return max(0.0, min(1.0, a))同一
bw还要复用到:素材卡的"退出淡回人像"、素材卡随条让位的上移量(cy = CARD_Y - 200*bw)。- 结尾 CTA 的"条让位"不要用循环里 filter,要在建表后 clamp:
b['e'] = min(b['e'], DUR-3.55),并清掉跨过该点的handoff。否则条会在 CTA 起手处硬消失(filter 是突变的,clamp 才能被上面的渐隐包络接住)。 - 头像的静态件(投影/圆形蒙版/白环)必须提到模块级只算一次:每帧重建大半径圆形 + GaussianBlur 会在 30fps 全片白烧几十秒。
- 结尾 CTA:背景按
bw走(结尾段 bw 已归 0 → 全景清晰),字条居中,下集预告 + 点赞关注在下。
4. 语义化音效(不要轮换随机配)
| 事件 | 音效 | 音量 |
| 卡片进场 | whoosh | 0.45 |
| 寒冬/危机(红卡) | boom(+0.08s) | 0.55 |
| 高光时刻(金卡) | ding+sparkle | 0.42/0.32 |
| 字幕条弹出/组合卡跟进 | pop | 0.40 |
| 接力让位 | swish2 | 0.38 |
| 三卡集齐 | sparkle | 0.40 |
| 三卡统一消失 | whoosh+boom | 0.50/0.45 |
| 结尾前 | riser→boom→sparkle | — |
- 混音:
amix normalize=0+alimiter=limit=0.95(不是 level=)。BGM volume=0.12(克制)。 - 外部音效库长样本的两个坑:
atrim必须放在adelay之前(atrim=duration=X,adelay=ms|ms)。放后面时 atrim 按流 PTS 裁——裁掉的全是 delay 产生的静音头,真实声音全没,验收表现是所有音效点位 RMS 比值 x1.00(音效整体消失而非报错)。- 外库 impact 类音效自带渐强铺垫(实测某 impact 音效 7.93s、峰值滞后 2.1s)。按"目标帧 − 峰值滞后帧数"钉帧(先量音源 0.1s 窗 RMS 包络找峰),截断给到峰值 + 衰减。不补的话窄窗口验收 x1.00,观众听到的是"晚了 2 秒的一声闷响"。
5. 提速出片
- 全片
setpts=PTS/1.18+atempo=1.18。 - 音画分开渲染后
-c:v copy -c:a copy合流,最后统一提速重编码 crf=19。
6. 验证(先看图,再用数值兜底)
看图是第一手段——本模型可直接读 PNG/JPG。 一张 22 帧接触表(每 4s 抽一帧 / 6 列)就能一眼扫完版式一致性、字幕同步、素材是否跑偏,比任何数值指标都直接。不要用"读不了像素图"当借口跳过目检。
标准目检三件套:
- 接触表:每 4s 抽一帧 → PIL 拼接 → 读图。看全片节奏与版式是否统一。
- 开场细帧:0 / 0.1 / 0.2 / 0.33 / 0.5 / 0.7 / 1.0,配合每帧平均亮度量化。 亮度 0 → 0.2s 内升到 60 属正常淡入;0.5s 后仍 <30 才是黑屏 bug。
- 收尾细帧:确认整段版式统一、末尾无定格、末句字幕完整。 缩略图里"全屏深色背景"极易被误判成"圆头像",怀疑版式不一致时以细帧为准。
数值核验(补目检看不到的维度):
- 边缘能量(FIND_EDGES 直方图均值)量清晰度、肤色框(YCbCr)定人脸位置、按时序抽样卡位亮度验编排。
- ffmpeg 8.x 抽单帧必须
-update 1,且要用正斜杠路径。 - 量虚化必须只测"纯背景 ROI":整帧边缘能量会被圆形头像 / 金句条 / 字幕条这些本身就锐利的叠加元素盖住,糊和清差不到 10%,根本区分不出来。
正确做法取左侧竖条如
ROI=(0,250,150,1750)——避开头像、条、字幕——糊态与清态信噪比才够。沿时间轴扫一遍输出曲线,能直接看出过渡是斜坡还是硬切。 - 硬切阈值:素材切换点本身就是设计内硬切(Δ 约 4)。判定阈值取 0.8,报警点再回比对素材段边界,落在边界上的直接忽略——否则每个换图点都会被误报。
- 判"是硬切还是内容变化"要回到帧上看。
素材自动抓配
可达与不可达的图源
- 可达:360 图搜
image.so.com。解析 JSON 字段"img":"http:\/\/p0.so.qhimg.com\/...", 注意 URL 里的斜杠是转义的,正则r'"img":"(https?:\\?/\\?/[^"]+?)"'拿到后.replace('\\/','/')。 - 360 会被限流,别按类连打:实测连续跑 15 类时,前 2 类正常,第 3 类起候选骤降到固定 8 条且下载全失败(被降级)。
对策:每类之间
sleep 2–2.5s、每类拆 3 个查询词,一次别超过 8~10 类,跑完一批隔一会儿再来第二批。 受限时不要重试同一批,换查询词重来(换词能拿到新候选,重试拿不到)。 - 单类至少要 6 张候选:像"股票/中美会谈/刹车"这类词命中率低,要用更口语的近义词再搜一轮("证券交易大厅""外交谈判""汽车刹车")。
- 不可达(别浪费时间重试):
pexels.com403、upload.wikimedia.org超时、pixabay/freepik/rawpixel/stocksnap403、api.openverse.org502、pic.sogou.com403、image.baidu.com/search/acjson返回 0 条。 - 360 彻底限流时的备用图源:Bing
https://cn.bing.com/images/async?q=<词>&first=0&count=35&mmasync=1,Referer: https://cn.bing.com/,原图 URL 在murl":"(https?://[^&]+?)"里(记得.replace('&','&'))。 同一时刻 360 只回 2 条候选、下载全失败时,Bing 可回 25–35 条且下载正常,单类 2 个词就能凑够 8 张,词间sleep 1.6s就够。
Bing 抓图的三个硬条件
取 Bing 返回前 N 张、标题一个字都不看的做法,实测 20 类里 12 张完全文不对题(说明书、黑板报、红灯笼、绿色青蛙、企业 logo)。三条缺一不可:
- 查询词必须是短 token,不要长中文短语。
实测同一时刻:
Meta35/35 命中、OpenAI24/25、腾讯35/35、数据中心35/35;而Meta 公司 总部 大楼只回 1 条、扎克伯格 演讲 发布会回的全是连衣裙、刹车回的是儿童感冒科普、虚拟卡回的是客家围龙屋。 中文多词短语会被 Bing 返回一份完全无关的结果,而且同一 query 反复请求结果一致,不是抖动,是稳定地错 → 别重试,换词。 - 必须按标题做相关性闸门。 走
https://www.bing.com/images/search?q=<词>&first=0&count=35&qft=+filterui:imagesize-large(不是images/async,async 拿不到title),解析class="iusc"上的m="{...}"JSON(html.unescape后json.loads),拿murl / t(title) / purl。 标题必须命中该类的 MUST 词才算相关;再给白名单站点加分。 另外每个查询先算一遍标题命中率,<35% 就整条查询弃用(长短语的典型特征)。 - 水印站直接拉黑,不要指望事后修。 实测带水印来源:
smzdm(什么值得买)、123rf、xhscdn(小红书)、zhimg/zhihu、699pic(摄图网)、nipic(昵图网)、duitang(堆糖)、baidu.com/baike、以及iMore这类科技站。命中即丢,比事后去水印便宜得多。
4. 标题里点了名的东西,配图必须真的是它。(比"不美观"严重,属硬错)
分镜标题写「设计师是某某」时,配图必须是那个人;曾出现抓到的图背景板对、人不是本人(另一位高管)的情况。标题点名了具体人物 / 品牌 / 产品,配错等于片子说错话,观众里只要有一个人认得就翻车。 所以「标题含专名」的镜头必须单独过一遍:把该镜头配图放大,问一句"图上这个人/这个东西,和标题写的是同一个吗"。别信文件名。
同理还要看两条审美硬伤:
- 不要选"背景是大号英文单词但被裁掉一半"的图。预裁比例 1.3926 是横构图,竖排/超宽的大字标题图基本都会被腰斩。
- 不要选一眼看不出是什么的抽象特写。抽象概念宁可用机房 / 报警灯 / 数据屏这类能指清楚的实拍图。
素材必须按「语义 + 合规」两道目检
光看美观会翻车:搜「红色警报 屏幕」返回的 8 张全是红色政治宣传图(党旗/红色标语),整类报废;搜「安全 警示」返回的是「注意安全」施工牌,跟"系统被入侵"完全文不对题。
对策:
- 敏感色系词(红/国旗/警报)抓回来先拼联系表看一遍再入库;
- 判据是"这图配上这句文案,观众会不会误读",不是"好不好看";
- 不合规的整类
shutil.move到_rejected_assets/隔离,不要删,以防下次又抓同一批词再翻一次车。
抓图脚本里不要用 os.remove(),会触发安全删除保护把整个脚本 exit 掉;写新文件名即可。定稿用 override 文件指到具体图,别靠打分排序碰运气。
水印:先"物理切掉",再考虑去水印
素材卡是固定比例 908×652(1.3926),渲染时 cover_crop 只按比例裁,底部角标会留在画面里。所以定稿阶段先做一次预裁,把图裁成卡片比例且不保留底部 14%:
AR = 908 / 652.0
TRIM_BOTTOM = 0.14
h_avail = int(h * (1 - TRIM_BOTTOM)); w_need = int(h_avail * AR)
if w_need <= w: box = ((w - w_need)//2, 0, ..., h_avail) # 横向够:切掉底部
else: ch = int(w/AR); y0 = int((h-ch)*0.12); ... # 竖向裁:切掉底部实测一次清掉全部角标(小红书 / 什么值得买 / 123RF / iMore 等全在底部)。副作用是好的:预裁后 cover_crop 变成纯缩放,Ken Burns 不再漂移。
只有水印落在画面中部时才需要专门的去水印处理(云端图像处理,单张 <4MB,慢)。
验收方式(别靠缩略图):截每张定稿图的底部条带、按条带原始像素宽拼联系表再读图。缩略图上看不出水印,条带法一眼就能看到角标。
视频素材流水线
Mixkit 是实测能直下的视频源(同一时段 Pexels/Pixabay/Videvo 全 403,Wikimedia Commons SSL 超时,archive.org 超时,Bing 视频页 murl 拿不到稳定文件)。
- 站内
?q=搜索是假的——不管传什么都返回同一份热门流。分类页是服务端渲染、结果准确:mixkit.co/free-stock-video/<cat>/。 - 分类探测:批量 GET 候选 slug,有效的存成本地目录索引,检索就在本地标题库里做关键词打分,不用反复抓站。
- 直链规律:预览
assets.mixkit.co/videos/<id>/<id>-360.mp4,把 360 换成 720 即可(1080 403)。720p=1280×720,卡片 908×652 够用。 - 竖屏素材也有(如 720×1280),
cover_crop通吃,不用单独处理。 - 品牌/人物类镜头素材站不可能有,用主题相近 B-roll 表意(安全→黑客攻击失败、7×24→深夜机房),标题条承载具体信息。
渲染:帧缓存,别逐帧 spawn ffmpeg。
- 每段先解码成帧序列缓存(
ffmpeg -vf fps=30 -q:v 4),缓存时长要 > 最长镜头;长片跳前 20% 取中段。 - ffmpeg
%05d帧号从 1 起,不是 0——取帧函数记得% n + 1,否则会出空卡。 - 素材段去掉 Ken Burns 推近(视频自带运动,再推就晃),底和卡都只做固定倍率
cover_crop。 - 性能与图片版持平:每帧一次 JPEG 解码 ≈ 5–10ms。
版式对齐对标模板:视频圆角窗 + 同窗虚化整屏底 + 白粗体无衬底字幕(重字重、带 stroke、去掉圆角 pill、去掉逐字变色,全句统一纯白)。
字符串 patch 的教训:s.replace(old, new, 1) 打的是第一处出现,同名代码在渲染循环和版式函数里各有一行时容易改错位置、正主没改。规矩:patch 必须带上下文锚定(多带上下两行),patch 完 grep 目标行确认只出现在该出现的地方。
全屏变体:用户要求"素材部分统一改为全屏"时,圆角卡/虚化底/卡边描光全去,base = cover_crop(im, W, H, 1.10) 直接当整屏画面,入出场改为 0.34s 黑场淡入 + 0.26s 压暗;章节 chip 挪到屏幕左上;标题条/数据条/字幕原样叠加(条自带深色底,压得住画面)。
fork 项目脚本第一件事:核对 --voice / --src 默认值指向本项目素材。
曾发生 fork 上一项目的脚本时忘了改默认 --voice,渲染时又没传参 → 前段有配音、后段只剩 BGM,交付后才发现。
排查判据:ffmpeg -ss T -t 6 -i 成片 -af highpass=f=120 -f s16le - 算高通后能量包络,逐 15s 打表——正确音源全程平稳,错音源在后半段断崖。别用相关系数:成片混了 BGM,低频 sine 把人声相关性压到 0,正反两条音源都接近 0,判不出来。
横屏素材进竖屏:不要单画面硬拉,用上下双画面拼贴。
横屏源 cover_crop 到竖屏等于放大 2.67x、只保留源画面中间 31% 宽度——又糊又切主体。解法(按首帧尺寸判定 + 分格):
- 横屏源 → 上下两格,格高
(H-16)//2,每格cover_crop(im, W, gh, 1.06):只放大 1.32x、保留 64% 画面宽度,清晰度约为单格方案 2 倍;上格与下格取同一素材错开约 2.6s(同素材错开时间 → 多机位感);中间播主题色分隔条 + 黑边。 - 竖屏源 → 仍单画面整屏
cover_crop(im, W, H, 1.10)。 - 选素材时若主题允许,优先挑竖屏/方形源,根本避免裁切问题。
综艺感文字动画:
- 字幕逐字错峰弹跳:每字延迟约 0.032s,用阻尼振荡缩放(起跳约 0.65x、过冲 1.09x、回落 1.0x),同时上弹 + 淡入。 实现:逐字各画一张小图(含 stroke)→ resize → 贴进紧凑块;稳态才回落到整块一次性绘制(省时间)。注意紧凑块 PAD 要大,否则弹跳放大时被裁边。
- 关键词高亮:token 命中数字/英文/百分号 → 金色,其余纯白。
- 标题条/数据条 Q 弹入场:用阻尼振荡缩放,下限 0.80,用现成的
paste_center贴,别手写 resize。
字幕句首动画:句首约 0.28s 内文字块从 1.14x / 左偏 / 带两道水平残影,ease_out 收敛到 1.0x / 居中 / 无残影;淡入快于位移。切句重新触发。 实现要点:文字先画进紧凑块,动画期整块 resize + 平移 + 残影副本合成,别在整屏 layer 上做缩放(贵)。 拆解方法:对标片动画一闪而过,必须全帧抽出再逐帧目检,隔 0.1s 抽帧会错过整个入场。
开头主标题只挂 1 秒:开场钩子卡 alpha 包络改为 0.18s 淡入、0.75s 起淡出、1.0s 全消,之后只留底部逐句字幕。
文案直出「文字动画卡片」短片(无配音 / 无源片)
用户只给文案、要「根据文案生成符合的画面」(画面可为文字动画卡片 + 实拍 B-roll)时走这条。
- 结构:每段文案 = 一个「拍」。每拍 = 一张 B-roll 底图 + 一句综艺感逐字弹跳大标题;拍间背景按权重交叉溶解。文字窗不重叠(首尾相接),背景窗重叠约 0.7s(最多两拍按权重混合)。
背景「要看得清」的正确参数("背景也要清晰"的反馈后定的,别再往死里压):
| 参数 | 错误做法(被否) | 正确做法 | 说明 |
| GaussianBlur | 15 | 4 | 15 把画面糊成色块,等于没有背景 |
| Brightness | 0.42 | 0.92 | 0.42 全片发灰发黑 |
| Color / Contrast | 0.90 / — | 1.02 / 1.06 | 稍提一点通透感 |
| 暗角 composite 底 | 0.45 | 0.78 | 0.45 把边缘再压一遍=三层叠加压死 |
「文字要看清」不靠压暗背景,靠给文字加衬底——两个需求不冲突,别用同一个旋钮。
- 文字可读性三件套(缺一个就会"看不清"):
- 整块半透明衬底板:圆角矩形覆盖所有文字外扩,透明度跟着首字同时进场。背景再亮也能读。
- 白字黑描边;高亮字绝对不许加描边(见下方硬 bug)。
- 行距 = 字号 × 1.44(1.34 时两行贴在一起,衬底板也托不开)。
- 大字弹跳:用阻尼缓动(回弹系数约 2.4)做缩放 + 上移 + 淡入;逐字各画一张含
stroke_width的小图 → resize → 贴到该字中心。字号按句长自适应。 - 关键词三档角色:
hl= 金色高亮条 + 深色字(不描边)("划重点");strike= 红色淡条 + 白字(黑描边)+ 红线(用于被否定的说法);white= 纯白 + 黑描边。
三个把字弄糊的硬 bug(别再犯):
- 高亮字加了描边:想"只加粗字形",实际把汉字笔画间的空隙全部填死,大字号的高亮词在金色块里糊成一坨黑饼。高亮字一律
stroke_width=0,连textbbox也要按stroke_width=0算(否则字模尺寸偏大、缩放动画会跳)。 - 高亮块的计时锚在 run 的「最后一个字」上:块要等最后一个字才开始长,于是前几个黑字先落在深色背景上——黑字黑底,等于没显示。计时必须锚在 run 的「第一个字」,先出块、再落字。
- 衬底板边界只按字模算,没算高亮块:高亮块会高出衬底板约 10px,露出一个方块角。
pady取足够大(如 50)即可盖住。
换行必须按段分组,不能逐字断(否则会把词拆成两行):
# 每段拆成「段首标点(可挂上一行的小单元)」+「余下整体(不可拆单元)」;
# 整组放不下就整组换行;只有该组自身宽 > max_w 时才退回逐字换行。
# max_w = W - 120(比逐字版宽,因为高亮块/衬底要往外扩)顺带解决了「以逗号开头的行」——段首标点单独成单元后会自动挂到上一行行尾。
- B-roll 用 AI 生成竖版素材比素材站检索稳、主题更准;生成图右下角常带「AI生成」水印,先
im.crop((0,0,w,h-100))裁掉再处理。cover_crop的zoom别超过 1.1,否则主体被裁掉。 - 无音频时:用
ffmpeg -f rawvideo管道直灌 h264(-preset medium -crf 18 -pix_fmt yuv420p -movflags +faststart),不写音轨;交付时提示用户可后期叠配音/BGM。首尾各做淡入/淡出 + 底部进度条。 - 核验方式按「能不能预览图片」二选一,别写死:
- 能预览 → 必须真抽帧目检。黑字被描边糊死、块与底板错位这类问题只能看出来,程序化统计是绿的(白像素数、金像素数全正常)。
- 不能预览 → 退化为程序化核验:抽帧统计白/金像素数、
ffprobe校验时长尺寸,并明确告诉用户"我没目检,请你过一遍",别声称"目检通过"。
素材与配音不等长
源视频(数字人拍摄/生成)常常和配音不等长。实测 30.00s 素材 + 31.79s 配音:帧读完就 break → 画面先结束、音频被 -shortest 砍掉,最后一句直接没了("结尾没说完")。
正确解法:把画面整体重定时到配音长度,全片一个速度跑到底。
rate = src_dur / DUR # 30.00/31.79 = 0.9437
-vf scale=...,crop=1080:1920,setpts=PTS/{rate:.6f},fps=30setpts=PTS/rate(rate<1 → 时间戳放大 → 变慢)再 fps=30,ffmpeg 自动补/丢帧。实测输出帧数与 int(DUR*FPS) 完全吻合,渲染循环一帧都不用补。5.6% 的减速肉眼不可察,且顺带把口型与配音对齐。延迟很小,不需要 minterpolate(1080p 全片跑不起)。
已被否掉的三条路(都会在那 1~2 秒露馅):
- 定格末帧:用户明确拒绝("不能用定格卡住结束")。
- 倒放 / boomerang:说到结尾那句时嘴部反着动,最好认的破绽。
- 片头帧循环 + 交叉溶解:接缝确实看不出来,但补出来的 1.5s 运动量 0.00–0.17(真定格)。 教训:接缝平滑 ≠ 有运动。判断"有没有卡住"要量运动量,不能只看接缝。
混音时长别交给 -shortest:amix 默认取最长输入(BGM 比配音长),-shortest 会被最短的那个音效文件牵着走,长度不可控。改成用 -t 显式截断。
源素材"远短于 + 内容不同"于配音:改用「露脸 + 素材段」双源结构。
重定时只在差距小(实测 30.00s vs 31.79s,放慢 5.6%)时成立。一旦差到 2 倍以上,重定时就变成慢动作,必须换结构。 实测数字人源 30.10s(内容是「老板做IP」demo)、配音 85.20s(「AI圈五件事」),差 2.83 倍且内容毫不相干。这种输入不能靠剪辑补齐,正解是:
把画面拆成两类段,数字人只负责"露脸",事件展开交给素材画面。
| 段 | 用途 | 画面来源 |
| DN(数字人段) | 开场钩子 / 每件事的引入 / 收尾 CTA | 源视频按顺序消耗 |
| MAT(素材段) | 事件展开、数据、新闻画面 | 抓来的图 + 文字面板 |
做法与实测数据:
- DN 段总长 ≤ 源片时长 → 段内只放慢极小的比例(无感)。
- MAT 段通常占 65% 左右 —— 这才是观众看知识口播的常态比例。
- 取帧:DN 段顺序读源;MAT 段用图片做 Ken Burns。两类段共用同一条时间轴与同一套字幕/音效。
DN 需求帧数 > 源片帧数时不用补:源片先过
setpts=PTS/(src_dur/DN_TOTAL)重采样再读,不要改用循环/补末帧。 - 每个 DN 段安排 ≤1 条金句条(锚点自动 clamp 到该段内)。
- 素材段版式:整屏实拍 Ken Burns + 上下压暗 + 文字面板(左色条/标签/主标题/英文)+ 字幕条。
金句条窗口必须 clamp 到所属 DN 段内:s = max(seg.t0+0.15, anchor-0.10)、e = min(seg.t1-0.06, s+hold);算出长度 < 0.55s 就退化成"整段";< 0.45s 直接跳过。
原因:条一出现就要"虚化 + 圆形头像",而那需要人像画面;条跨到素材段上会很怪。
排片时先看锚点落在哪个段——否则大量锚点实际落在素材段,会被全部丢弃。
两类的配比逻辑不一样:
- 新闻清单型:按事件条数均分,节奏快。
- 观点型:先数第一人称句和反问句。"我"和"你"就是可信度本身,数字人直接对镜头说话的价值 > 任何素材画面。 配比照源片长度倒推:DN_TOTAL 只能 ≈ 源时长,所以先把必须露脸的段排出来占满,剩下的全部给 MAT,不要反过来先排素材。 哪些必须给 DN:开场钩子 / 第一人称观点 / 反问观众 / 转折句 / 结论金句 / 收尾 CTA。 哪些给 MAT:事实陈述、数字、第三方说法、案例。 单个 MAT 段别超过约 7s:观点型节奏比清单型慢,超了就把其中一句交给 DN(一句 1.5–2s 的反问插在中间,节奏立刻起来)。
- 开场钩子段的长度 = 第一句的句号,钩子卡结束点直接等于第一个 MAT 段起点,别留硬编码。
- 章节条按文案自身结构切,标签文本做进配置里(不要在渲染函数里拼接固定文案)。
换文案重建检查清单
用户说「视频剪辑得很清晰继续保持,音频换成这个,素材一样云端搜索配置」时走这条路径。 逐条打勾,别凭记忆 —— 下面每一条都是实践中踩过的。
① 转写
- 新音频 → 转写 → 词级 JSON;
DUR = ffprobe(配音) - 通读转写稿,逐条列出 ASR 错字写进
FIX_LIST(如:登陆评证→登录凭证、亿美元进现金→亿美元净现金流、车已经标起来→车已经飙起来、危言怂听→危言耸听) - ⚠️ 每条改词都要确认它在字幕原文里真的出现 —— 逐句纠错分支只在命中时才走
- ⚠️
FIX字典必须显式构造(历次 fork 里最容易漏的一条):python FIX_LIST = [('登陆评证', '登录凭证'), ('危言怂听', '危言耸听'), ...] FIX = dict(sorted(FIX_LIST, key=lambda x: -len(x[0]))) # 按原词长度降序,短词不遮蔽长词漏了会NameError,而且只在某条改词命中时才暴露。反过来,等长改词用逐字替换、不等长才整段合并,否则会破坏词边界。 - 采样率:本地 ffmpeg 混音 40kHz 也照跑;只有特定云端服务要求 44.1/48k
② 时间轴
- 判结构(长度比 / 内容是否相关 / 是否观点型)
DN_TOTAL≈ 源片时长,rate落在 0.95~1.05;超出就是能看出来的慢放/快进- 时间轴无空洞、末端 == DUR(段长不匹配会告警,别忽略)
- 金句条锚点必须落在 DN 段内,落 MAT 段的会被丢弃
HOOK_END= 第一句的句末,别留上一版的硬编码CHAPS按新文案结构切;标签文本写进配置
③ 素材
- 按新文案重列类别 → 云端抓(360,限流就换 Bing)
- 全部拼联系表目检:文不对题 / 政治敏感 / 水印 / 别人的截图,一张都不许留
- 不合规的整类隔离到
_rejected_assets/,别留在正式素材目录里混着
④ 渲前自检(省一次 20 分钟白跑)
python scripts/static_names.py build.pypython scripts/dryrun_layout.py build.py—— 顺带读逐句字幕,看有没有把数字/词劈开、某段时间字幕为空
⑤ 渲染与核验
- 先渲 10s 试片(覆盖开场 + 第一张数据卡 + 第一条金句条),抽帧看一眼
- 全片 →
scripts/verify_full.py+scripts/contact_sheet.py目检 → 交付
⑥ 检查藏在函数体里的上一版文案
只改头部的配置常量、函数体里写死的字符串一条都改不到。曾发生 fork 完 render_hook 里还是上一版的开场白,渲完抽帧才发现,等于开场白全错。fork 后必须 grep 一遍这些位置:
render_hook 里的主标题 / 副标题 / 标签 render_endcard 里的 CTA 文案
render_hook 里的 HOOK_END 退场窗口 chap_card 里的事件标签
make_data_bar 里的单位小字 BARS 的英文副标题⑦ 金句条窗口三查(渲前用 10 行脚本过一遍,别等渲完)
T=m.build_timeline(); segs=m.build_segs()
bs=[b for b in m.resolve_bars(T,segs) if b['s']<m.DUR-3.60] # 提前退场点之后的本来就会被砍
for b in sorted(bs,key=lambda x:x['s']):
print('%-16s %6.2f-%6.2f %.2fs'%(b['cn'],b['s'],b['e'],b['e']-b['s']))- 锚点必须在同一个 whisper 段内连续。词级时间限段累积,跨句的短语永远命中不到(会显示
anchor miss)。改锚点时要挑段内连续、语义更对的那句。 注意锚点文本 ≠ 条上显示的字。 - 任何两条窗口不许重叠(重叠 = 两张大字叠在同一屏)。改锚点后一定要重算一遍。
- 单条短于 0.8s 读不完。锚点落在段尾会被 clamp 砍短,改锚到更靠前的词即可。
- 脚本
scripts/bar_windows.py可一次查完这三类问题。
⑧ 修正字幕的坑:改词会被应用两次(不等长规则才会暴露)
词表在构建时已经过一遍改词;渲染时再跑一遍清理,不等长规则就会被应用两次:
raw「往 前 挪 一」→ 第一遍 → 「往前挪一步」→ 第二遍 → 「往前挪一步步」等长规则看不出问题,所以这个坑一直藏着,换文案才暴露。 对策二选一:
- 推荐:渲染函数里直接用已改好的词,不再纠错;
- 或者把不等长规则拆出去,只留等长规则。
⚠️ scripts/dryrun_layout.py 以前也照抄了这段二次纠错,会报假问题(干跑字幕错、成片其实是对的)。脚本已同步修掉;若又见到"干跑字幕和成片不一致",先查这条。
⑨ 钩子卡退场要和第一条金句条交叉,不能硬叠
render_hook 的 alpha 包络若让钩子卡到金句条入场时还是全不透明,两段大字会直接叠在一起。改成提前退场窗口,与金句条入场形成交叉溶解。
渲染与交付纪律
渲染进程只能有一个
绝对不能让两个 build 进程写同一个输出路径。
曾发生:全片渲染 A 已跑完并写出成片;以为它被中断,又起了进程 B 指向同一路径。B 从头覆盖写,A 的尾部还在——两个 ffmpeg 同时往一个 mp4 里写 NAL。表面看 ffprobe 一切正常(时长/帧数/体积都对),但中间连续 40 秒运动量 = 0.00(画面冻结),解码时刷屏 Invalid NAL unit size。交付前才发现,27 分钟白跑。
三条规定:
- 起渲染前先看有没有同名进程在跑(输出路径相同就算冲突)。有就等它,不要另起。
- 每次渲染换独立输出名(
_v1 / _v2 / _r),确认无误再改成交付名。不要直接把输出指向交付路径跑第二次。 - 中断恢复的正确姿势:先
ffprobe看已有文件是不是完整(duration ≈ DUR且nb_frames ≈ round(DUR*FPS)),完整就用它,不完整才重渲。别凭"日志是空的"判断——Python 重定向到文件时 stdout 是块缓冲,日志 0 字节不代表没在跑。
识破这种假成片的方法(ffprobe 骗得过,这个骗不过):
# 抽若干帧比字节数:坏文件的冻结区每一帧 PNG 大小完全相同
for t in [58, 65, 75, 85, 95, 100]:
ffmpeg -ss <t> -i 成片.mp4 -frames:v 1 f<t>.png
# 6 个文件 size 全是同一个数 → 同一帧被复制了 40 秒verify_full.py 里"连续若干 5s 区间运动量恒为 0.00"也是同一个信号——正常片子再静也不会精确等于 0。
交付前三查(不过不许交付)
# 1. 时长 ≥ 配音时长
ffprobe -v error -show_entries format=duration -of default=nw=1 成片.mp4
# 2. 结尾 2s 运动量不能塌到 0(定格 <1.0)——唯一能抓住"卡住"的指标
python scripts/verify_full.py 成片.mp4
# 3. 尾部无长静音(有 = 音频被截)
ffmpeg -i 成片.mp4 -af silencedetect=n=-42dB:d=0.25 -f null -渲前自检与核验脚本(技能自带,直接用):
| 脚本 | 用途 | 时机 |
scripts/static_names.py <build.py> | 静态扫未定义全局名 | 渲染前 |
scripts/dryrun_layout.py <build.py> | 干跑全时间轴排版 + 打印逐句字幕 | 渲染前 |
scripts/bar_windows.py <build.py> | 金句条窗口/锚点自检(10 秒) | 渲染前 |
scripts/debug_component.py <build.py> <fn> | 单元素出图诊断(秒级) | 改组件时 |
scripts/assets_sheet.py <assets根> | 素材联系表(每类一段、标文件名) | 抓完素材、定稿前 |
scripts/wm_strip_audit.py <assets_dir> | 水印条带终检 | 定稿后 |
scripts/prep_card_aspect.py | 素材预裁(卡片比例 + 切水印带) | 定稿时 |
scripts/verify_full.py <成片> | 全片核验:运动量 + 虚化连续性 + 跳变归类 | 交付前 |
scripts/contact_sheet.py <成片> out.png | 接触表 / 首尾细帧 | 交付前目检 |
渲染前第一道自检的真实价值:fork 出来的 build 脚本里渲染函数用了某个字典,而它从来没被定义过(只有对应的列表)。上一版之所以没崩,只因为那版改词表一条都没命中字幕原文、else 分支从没被执行到;换文案后第一次命中,跑到 50s 才抛 NameError,11 分钟白费。
教训:fork 管线换文案后,凡是"只在特定输入下才会走到的分支"都必须主动触发一次。 静态扫一遍是成本最低的兜底。
全片核验的实现要点:一次抽完帧再算差分,别循环 -ss(逐次抽帧在范围大时必被 120s 超时杀掉,输出为空,看着像"脚本坏了")。
# 一次抽完再算差分
subprocess.run([FF, '-y', '-v', 'error', '-i', V,
'-vf', f'fps={FPS},scale=270:480', os.path.join(tmp, 'f%04d.png')], check=True)
ims = [Image.open(...).convert('L') for ...]
d = ImageStat.Stat(ImageChops.difference(ims[i], ims[i-1])).mean[0]- 全片运动量阈值:<0.8 判定格。
- 虚化连续性阈值:相邻帧 ROI 细节 Δ/低值 >0.8 才报警;但素材切换点本身 Δ≈4,是设计内硬切,不要误判——报警点必须拿时间码去比对素材段边界,落在边界上的直接忽略。
⚠️
--mat要传全部切点(数字人段边界 + 素材段边界 + 素材段内部的镜头边界),只传段边界会把设计内的镜头硬切误报成"非边界异常"。 - 抽单帧时
-ss放在-i之后是精确 seek(慢);放前面是就近关键帧(快但不准)。小窗口宁可慢也要准。
Windows 环境注意事项
- Git Bash 内联
python -c带 Windows 反斜杠路径会被 MSYS 转换炸掉 → 写成 .py 文件再执行。 - Bash 可能没有 coreutils(ls/head/grep/dirname 全没有)→ 文件操作用 Python 更稳。
- 多处并行编辑同一文件会互相覆盖 → 改完 grep 确认定义还在,或用带 assert 的批量 patch 脚本。
- 内联
python -c带 f-string 中文括号引号容易出错 → 一律写 .py 文件。 - 图片素材批量下载时注意反爬;部分站点的端点需要特殊处理。
- 路径一律用正斜杠传给 ffmpeg,避免转义问题。
常见返工原因速查
- 花字看不见 → 渐变合成顺序反了(文字层要在上)
- 字幕贴底太小 → 上移胸口位、放大
- 钩子挡脸 → 下调 + 背景虚化
- 金句卡撞字幕 → 版面解析函数写死 y 忽略了配置(grep 检查配置是否真被用)
- 头像切头顶 / 背景见脸 → 裁切框要放大 + 重度虚化;目测脸位估错几百像素 → 必须用密度轮廓实测
- 音效单一枯燥 → 语义化映射表
- 把虚化做成布尔开关 → 背景在几处硬闪 → 改连续权重曲线
- 结尾 CTA 的条让位用循环 filter → 条硬消失 → 改建表后 clamp
- 源片短于配音 → 帧读完
break,最后一句被砍 → 用户连报三轮("结尾没说完" → "不要定格" → "最后一秒卡住")。根因是长度不等,正确解法在开工前就该上重定时,而不是逐轮打补丁 - 兜底分支的打印写在循环之后无条件执行 → 一帧没补也打印"已补齐",据此误判方案生效。教训:兜底分支的日志必须挂在"真的走了这条路"里,否则日志会骗自己。
- 字幕「太长」→ 根因是结构(整段一次性铺满多行),不是字号。改短句分页。
切句时按字符数硬断会把词劈开(
OpenAI→OpenA|I、2026→202|6),改「标点优先 + 不可拆单元边界」;千分位逗号也要排除(否则2,780被切成两页)。 - 「两个头」→ 全屏人脸额头上又叠了个圆头像。根因是虚化不够重(人形剪影仍可辨),加码虚化 + 下半身压暗底衬才彻底退成色彩氛围。
- 「这个字需要不模糊」→ 数据条三个写死值叠加,其中漏加
pad偏移最隐蔽(只在汉字上暴露,因为汉字 bbox 更高)。 - 换文案后跑到一半才崩(某个字典从未定义,只在某条改词命中时才走到那个分支)→ 渲染白费。此后渲染前必跑静态扫名 + 干跑全时间轴。
- 素材:360 限流换 Bing;「红色警报」搜出来全是政治宣传图,整类报废并隔离。语义契合与合规 >> 审美分,必须联系表目检。
- 字幕从逐字变色 pill 换成对标模板的白粗体无衬底;章节编号 chip 宽度写死会裁字 → 文本长度自适应。
- 抓图取前 N 张不看标题 = 整轮返工。
- 水印先"物理切掉"再谈去水印。
- 函数体里写死的上一版文案 fork 改不到。
- 改词被应用两次:等长规则看不出问题,所以一直藏着,换文案才暴露。
- 金句条三条硬伤:跨句短语永远命中不到;两条窗口重叠 = 两张大字叠一屏;锚点落段尾被 clamp 到 0.8s 以下读不完。
- 钩子卡退场要交叉不能硬叠。
数字人换脸工作流
把「真人出镜口播成片」的出镜人换成「数字人」,并套原卡片版爆款结构重剪。
- 先转写核对数字人视频内容是否匹配原脚本——否则会把错误 demo 当 source(曾把一段看似数字人的视频当作素材,转写发现内容是另一篇 demo,与目标文案不符)。
- 本地转写数字人音频:faster-whisper 走本地缓存(
huggingface.co受限时不能联网下),用已装该包的环境跑。 - 中位数帧背景差分定位头部(禁目测):多帧 median 当背景 → 前景差 > 阈值 → 中央列掩码取主体 → 头 = 主体顶部一定比例。全身数字人头在画面上方,与真人版的中心位置差别很大,务必用实测值。
- fork 主渲染脚本后要同步改:词表/段表改读数字人转写文件;头像裁切框改数字人实测头;结尾 CTA 段补上字幕渲染——否则结尾 CTA 起手后最后一句收尾金句会被盖掉不出字幕。
- 锚点必须对照实际转写 token:whisper 常把专有名词误识(同音/近音)。直接改锚点词,不要动显示文案里的正确中文。改完跑锚点自检确认全部命中再渲染,否则 KeyError/整段错位。
- 渲染 + 交付前三查。
- 字幕「显示层」也要修误识:锚点修了还不够,字幕显示的是转写原文,画面仍会露出错字。加一个字符映射做呈现层修正——只改呈现、不动锚点时间码。先 grep 确认该字只出现在误识 token 里再全局套,避免误伤正常字。
- 用 Python 脚本插入全局变量,别用文本编辑器工具:某些编辑器对大段全局定义会「报成功但不落盘」(行尾匹配问题),导致运行时报 NameError。稳妥做法是用 Python 读文件、精确 replace 后写回,改完 grep 确认落盘再渲染。
数字人换脸 · 三条硬经验
1. 音色同源判定 —— 决定要不要重做 TTS 拿到"数字人素材"和"目标文案音轨"时,先判定两者是否同一音色,避免无谓的 TTS 重合成:
- 两条音频都转 16k mono,算平均对数幅度谱的余弦相似度
- 判定阈值:>0.90 基本可认定为同一音色(实测同一 TTS 两条不同文案 ≈0.93,同一内容提速版 ≈0.988)
- 同源则直接沿用目标文案音轨:文案 100% 准确 + 音色就是数字人的,比重新合成质量更高
2. 数字人素材比旁白短 —— 用"帧混合慢放",不要循环也不要跳帧 数字人源帧数 < 成片总帧数时:
ratio = (N_SRC - 1) / (TOTAL - 1),输出帧 i 对应源位置p = i * ratiok = int(p); frac = p - k,用Image.blend(F[k], F[k+1], frac)插值- 比"循环拼接"(有接缝)和"逐帧复制"(约 18% 重复帧,肉眼可见微卡顿)都好
- 内存:只缓存相邻 2 帧,边读边写,恒定占用
- 注意:闭包里修改外层索引变量要加
nonlocal
3. 时间码缩放
若成片对原始音轨做了 atempo=S,则所有词级时间码统一 * (1/S) 即可(atempo 是均匀时间缩放,线性精确),不必重新转录。
4. 词级锚点的两个陷阱
- 多义词锚点会命中"最先出现"的位置:如锚点"数字人"命中开场白而非正文。改用更长的唯一短语,或给
word_time传兜底值。 - 兜底表不接进
word_time就是死代码:必须word_time(p[0], FALLBACK.get(p[0]))这样传。
5. 卡片别超宽 文字卡要自适应缩字号:文本宽超上限就逐步减小字号,否则大字号下超过一定字数就会被裁掉。
6. 验收脚本的步进坑
像素差函数若固定按全画布高度步进,对裁切出来的子区域会 IndexError。必须按 a.size / b.size 取 min 后步进。
数字人成片补素材 + 嘴型
输入对齐:先拉长、后 Wav2Lip,成片帧与嘴型 1:1 数字人源比旁白短时,不要"渲染时再慢放"——先用帧混合把源拉长到旁白长度(可先 atempo 对齐),让 Wav2Lip 直接吃"旁白长度"的视频和音频,成片比例 1.0 逐帧直取,嘴型零漂移。
wave 读立体声 = 时间轴翻倍(静音检测全错的元凶)
wave.open 读立体声 wav 时得到的是交错帧,按单声道解释后:
- 时长算出是真实的 2 倍
- 所有"语音峰值/静音点"的时间戳全错一半,目检会得出"静音时嘴张开"的假阴性
- 修法:先用 ffmpeg 转 mono 再读;或按步长拆声道
素材版式:史料不用全屏切镜,用「卡片浮层」 背景人像持续重度虚化,中尺寸卡浮在上面:
- 三卡排队(讲人物/事件起源时):多张拍立得卡(白描边黑卡 + 图 + 年份行),错位微倾堆叠,第一张独占中央 → 后卡进场前卡缓动上移让位 → 词尾统一上飘淡出。每张进场/出场配对应音效。锚点用词级时间逐卡定位。
- 单条史料:标题条素材卡——顶部色条(中文粗体 + 英文小字)、图区 Ken Burns、左下年份角标(白字黑描边压图)。卡的骨架(描边/标题条)模块级预渲染一次,渲染循环只贴图 + 画年份。
- 金句条:紧凑窄条(自适应宽度),非全宽大卡;金字配金色描边、其余白字;英文副标小号灰蓝。
- 分析参考片的方法:用 ffmpeg 场景检测拿切点 → 镜头时长序列 → 按切点中段抽帧拼联系表目检。参考片的节奏往往比想象中慢,卡段要留足阅读时间。
嘴部真实感:GFPGAN 不是万能的 用户圈红框「嘴部模糊、衔接不真实」→ 根因是整脸重绘的塑料感,不是 Wav2Lip。
- 判据:原素材本身清晰时,整脸重绘 = 肤质变塑料 + 融合边界色差翻倍。GFPGAN 只用于"救真糊"的素材;数字人原片清晰时弃用。
- 正解:嘴部小 ROI 融合:底片用原拉长帧,只从 Wav2Lip 输出取嘴部小 ROI(含羽化)贴回,patch 加轻锐化。脸颊/下巴/脖子 100% 原片像素。
- 接缝量化验收:融合边界两侧窄色带的平均色差(越小越无缝),必须对比新旧两版出数字。
- 嘴部 ROI 实测方法:抽发声帧/静音帧各一张裁切目检(发声张嘴定下界、静音闭嘴定上界)。
「模板没植入」自查清单(用户会拿这个验收)
fork 管线时「人物处理」这一步最容易漏,交付前对单:
- 金句条压屏 = 重度虚化 + 圆形头像,不是轻虚化。实测参数:先降采样再放大 + 大半径高斯模糊;只做轻虚化时人形剪影仍可辨 → 视觉上"两个头"。
- 排版窗口内必须压掉下半身剪影:圆头像盖住了头,但底层的肩/身体剪影还在,用户照样报"两个头"。加一层柔化地板(下 60% 渐深)+ 降饱和,底层退成纯色彩氛围。
- 纯字幕段 = 全景直出,不虚化、不叠头像
- 头像裁切框实测(暗像素找发顶:头发亮度低分位 bbox,多帧取并集),静态件(圆蒙版/白环/投影)模块级只算一次
- 金句条压屏时底部字幕减透明(
alpha *= 1 - k*bw)。 k 不能照抄固定值 —— 那是"条与字幕会撞车"的版式下的值,会把字幕压到几乎看不见。 判断依据是"条和字幕的实际 y 是否重叠",不是习惯性照抄系数。 - 连续权重
bw[t]贯穿:虚化、头像 alpha、字幕透明、素材卡让位共用同一条曲线 - 头像窗口应该覆盖整个 DN 段,不是只覆盖金句条那一瞬。改成"该 DN 段只要有 bar 就整段虚化 + 头像"后,头像出现时长可提升 60% 以上。无 bar 的 DN 段仍全景直出。 注意上升沿要快(0.18s):过渡期的"半虚化"正是"两个头"最明显的时候。
- 顶部章节进度条:
事件 NN/05 · 事件名+ 进度条,只在排版窗口内显示。注意头像中心要下移给它让位。
字幕:必须是「短句分页」,不是整段铺满
根因不是字号,是结构:整段词一次性铺满(一个 27 字的段落 → 5 行小字堆在底部)。 改成短句分页:按标点切成短句,一次只渲染当前句(≤2 行),说完切下一句。
切句三条硬规则:
- 切页主要靠标点,行内换行才用单元边界。 累计字数达下限且句末是标点才断;上限只作兜底(超长无标点句)。下限小一点,让「先说某家,」这类超短句自动并入下一句。
- 断点必须落在「不可拆单元」边界,否则会切开词。
按字符数硬断的实测惨状:
OpenAI→OpenA|I、2026→202|6、2780→278|0、承认→承|认。 正解:连续 ASCII(数字/英文/小数点)合并成一个单元,中文 2 字一组,标点并到前一单元末尾(这样句末标点才判得准)。 注意小数点和千分位逗号都不能当句末标点:前后都是数字 → 跳过。 只认 ASCII 逗号,不要把全角「,」也放进去(全角逗号在中文里几乎总是真断句)。 - 不要在「已合并成 2 字 token」的流上切句。 标点会被合并进下一个 token 的开头,句末标点判断全部失效,整段连排、短句分页形同虚设。
字号与行高:58 号 + max_w = W-110 + 行高 78(最长 31 字的句子能压进 2 行 = 16 字/行)。
素材段:用「模版卡片排版」,不要大图铺满
原实现是素材图全屏铺满 + 一个标签面板。改成模版卡片:
- 素材图进圆角卡,不再全屏;卡外是同一张图的重度模糊版做氛围底
- 卡左上角章节编号色块(01/02…,跟随章节配置)
- 标题条:色块标签 + 中文主标题 + 英文小字
- 数据条:大字数值 + 分隔竖线 + 单位/说明小字 —— 这是最抓眼的部分。数值走渐变色,左侧还有一条强调色条。数据从文案里抽真实数字,不要编
- 卡片斜切滑入,不是淡入
素材段和 DN 段共用同一套顶部章节条,章节按时间区间切。
数据条「字模糊」的三个坑
用户截了张图说黄字糊成一团。根因不是字体,是三个写死值叠加,逐个查出来的:
- 忘了加
pad偏移(真凶,最隐蔽) —— 发光卡片函数返回的图层是(w+2·pad, h+2·pad),卡片实际画在[pad, pad, pad+w, pad+h]。而大字用y0=(h-bh)//2画、没加 pad,后半段的单位/说明却加了。结果字形按"图层顶"对齐、而不是"卡片顶"对齐。 为什么只在汉字上暴露:数字 bbox 较矮,偏移被(行高-bbox)//2吃掉了;汉字 bbox 更高,字形上半截直接压出卡片上边框、叠在发光边上,看起来就是"发虚、糊"。凡是发光卡片的产物,所有坐标都要 +pad,一个都不能漏。 - 字号写死 —— 窄数字刚好,两个汉字直接顶到分隔线。改成按可用宽度自适应,逐步降字号直到装得下。
- 描边写死 —— 汉字笔画本就粗,再加描边,笔画互相糊连、闭合腔被填死,认不出字。改成:汉字禁描边(sw=0),数字保留
sw = round(size*0.04)(保留数字那种厚重感)。
汉字还要逐字画、加 12px 字距,否则两个汉字会粘在一起;字号上限也单独降档:≤2 字用 64、3 字用 52。
复验方式(不要靠看整片):用 scripts/debug_component.py 单独特化某个组件出图,一次把「2 汉字 / 3 汉字 / 负数 / 小数」四种拼一张联系表看,秒级迭代。整片渲一遍 20 分钟,不值当。
来源与改编说明
- 本技能由深圳市福和顺科技传媒有限公司自主开发并持有。
- 技能内容为福和顺在竖屏口播视频自动剪辑上的自有工程实践沉淀,包含完整的管线方法、参数基线与踩坑经验。
- 随包分发的
scripts/为福和顺自研工具脚本,用于渲前自检、素材目检与交付核验。 - 运行所需第三方依赖(ffmpeg / faster-whisper / Pillow 等)由使用者自行按各自许可证获取,不随本包分发。
In this repository60 other files it gives its agents.
- accessibility-auditSkill
- agent-browser-coreSkill
- ai-explainer-videoSkill
- ai-short-video-makerSkill
- ai-video-shortSkill
- AI 工具箱Skill
- antislopSkill
- browser-useSkill
- cangjie-skillSkill
- delivery-no-pseudoblockSkill
- design-reviewSkill
- design-routerSkill
- edge-tts-readSkill
- frontend-designSkill
- frontend-devSkill
- fullstack-devSkill
- githubSkill
- git-shipSkill
- grillingSkill
- hfut-inspectorSkill
- humanizerSkill
- image-to-editable-pptxSkill
- kdocsSkill
- local-video-pipelineSkill
- markitdown-skillSkill
- pdfSkill
- pptxSkill
- qf-english-polishSkill
- qq-emailSkill
- responsive-designSkill
- self-improving-agentSkill
- sherpa-onnx-ttsSkill
- short-video-script-to-finished-videoSkill
- skill-creatorSkill
- skill-integratorSkill
- tastemakerSkill
- techdebtSkill
- Typography FoundationsSkill
- ui-designSkill
- video-scriptSkill
- web-accessSkill
- web-design-guidelinesSkill
- web-design-layoutSkill
- wechat-article-proSkill
- wechat-article-searchSkill
- weekly-reviewSkill
- word-toolkitSkill
- WorkBuddy 工作原则Skill
- xlsxSkill
- yuketang-harvesterSkill
- 学术研究工具箱Skill
- 安全审查工具箱Skill
- 工程方法论工具箱Skill
- 开发工具箱Skill
- 微信小程序开发套件Skill
- 技能包跨机器打包Skill
- 文档工具箱Skill
- 知识工具箱Skill
- 联网工具箱Skill
- 评审委员会Skill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

