产物:1080p/30fps MP4(分镜逐格运镜 + 中文配音 + 黄字大字幕 + BGM)+ 发布包(封面/章节轴/双平台文案)。工作目录建议 video_<主题>/{slides,pngs,aud,seg,subs}。
script.json:{"<页码>": [["角色","台词"],…]}。每页 1~3 条、单条 ≤90 字(约 20s 内)。风格三选一:标准播报 / 评书腔(结论先行+自问自答+职场隐喻+口头禅)/ 分角色。时长硬规则(火宝短剧那套 4.5 字/秒、500 字/分钟是它自家口径,别照搬:本机 edge-tts 云健 +12% 实测 5.05 字/秒 = 303 字/分钟,套 500 字/分钟会把目标时长算短一半、套 4.5 字/秒会让每页都误报「台词不够」)。正确算法:先估 成片时长 ≈ 总字数 ÷ 5.05 + Σ每页(0.5 前置 + 0.8 尾 + 0.45×(句数-1)),再据此定写词字数(10 页 × 每页 2 条约 = 500 字 ≈ 138s);超了砍台词不砍画面。注意音轨长度本来就是 TTS 实测出来的,「台词下限」只在画面时长要独立于配音设定(如给 AI 生视频定 8-15s 段)时才是硬约束;分格必须落在【开场】【触发】【高潮】【收尾】节拍边界,同一因果链不许切散;口播稿禁抽象形容词(”很伤心”“紧张”),一律转可见描写(”低下头、攥紧杯沿”)——评书腔同理。<header class="cover"> 与各 <section class="panel">,逐页包进 1920×1080 舞台(.stage{width:860px;transform:translate(-50%,-50%) scale(2.02)},px 定位的气泡会随 SVG 等比缩放,勿改布局)。Chrome 无头截图:--headless --window-size=1920,1080 --hide-scrollbars --screenshot。say 自然一个次元):python -m edge_tts --voice zh-CN-YunjianNeural --rate=+12% --proxy socks5h://127.0.0.1:7890 --text ... --write-media x.mp3,国内直连报 NoAudioReceived 必须走代理;mp3→ffmpeg -ar 44100 -ac 2 转 wav。定版音色:云健 zh-CN-YunjianNeural rate+12%(评书腔,用户拍板),edge-tts 音量偏轻,成片混音时人声轨加 volume=2.2 再限幅。备选 say -v <音色> -r 235~255。say 音色必须用完整显示名(见坑①)。每句生成后断言时长 >1s,防整段静音。可仿口吻,不克隆真人声音(声纹授权红线,平台判违规)。volume=1.75,alimiter=limit=0.92。[,。!?;:] 切 ≤20 字条,句内按字数比例分摊实测时长。渲染成透明底 PNG(HTML + Chrome --default-background-color=00000000),样式:PingFang 52px 900 #ffd94a + 四向 text-shadow 描边。横屏 bottom 必须 ≤30px 且把舞台 scale 降到 1.86、top 改 47%:scale 2.02 时面板几乎铺满 1080 高,漫画自带的那条黑底白字旁白条正好压在 y≈890-990,字幕放 bottom:168 会和它叠成一片糊字(实测踩过,见坑⑨);缩小上移后底部才腾出一条米色净空带。竖屏 bottom:420px 本来就落在面板下方的空白区,不受影响。scale=3840:2160 减抖,d=时长×30)。可选黑底大字开场/收场卡(同 Chrome 截图,配静默音轨)。concat=n=N:v=1:a=1(滤镜!见坑③)→ 逐条 overlay=enable='between(t,a,b)' → amix BGM(volume≈0.32)→ alimiter + 尾部 afade。qwenwork_music_generate(见坑④,常失败);兜底本地合成:Python wave 写五声音阶八分拨弦(指数衰减正弦+泛音)+ 每拍贝斯 + 反拍噪声沙锤,73s 循环,峰值归一 ~0.22。ffprobe 视频流与音频流时长差 <0.5s(坑③同类漂移);抽 2 帧看字幕对齐与遮挡;volumedetect mean ≈ -19~-22dB。sips -Z 1280 出 YouTube jpg;章节轴 = 逐段实测时长累加;文案包 = B站(分区/标题/简介/标签)+ YouTube(双语 description/章节/Category),必含 AI 语音申报提醒;仿写口吻注明”与任何创作者无关”。不是加黑边,是重排版式:1080×1920 舞台,顶部常驻系列徽章+大标题(70~330px 区),中段漫画格 .stage{left:40px;top:640px;width:860px;transform-origin:top left} 缩放 1.1628(左右各留 40px 安全边距),底部字幕区。运镜改纵向缓推缓拉:输入 scale=2160:3840,zoompan z=1.06(>1.1 会裁掉格内边缘文字!实测 1.16 切掉”第二站”标题),y 在 0..(ih-ih/zoom) 逐帧线性移动、奇偶页换方向;字幕 PNG 重出 1080×1920 版,bottom:420px。音轨与横屏共用,事件时间轴不变。
面板 PNG、字幕 PNG 全部复用:①新音色重跑逐句 TTS;②重拼每页音轨;③重算字幕事件时间轴(断言切条数不变,变了才需重截字幕 PNG);④分段重渲染(时长变了);⑤若只是响度不够,跳过 ①-④,仅在最终混音给 [ca]volume=2.2 重跑合成。视频号已发布视频换不了视频文件,只能重发新帖+引导用户手机长按删旧。
say 新音色裸名(Eddy/Grandpa/Shelley/Rocko)读中文 = 纯静音 0.016s,必须 "Eddy (中文(中国大陆))";Tingting 例外。-loop 1 的段用 concat demuxer -c copy 拼接,视频流会比音频长出一截(帧溢出,实测 134s 变 155s);必须用 concat 滤镜重编码收尾。qwenwork_music_generate 纯 BGM 会报 lyrics_prompt 校验错(auto_lyrics=false 也救不了),失败标记 resumable=false 时直接转本地合成,别死磕。subtitles 滤镜不存在)→ 透明 PNG + overlay 是通用替代;subtitles= 语法也要写成 subtitles=filename=。-y,否则静默跳过覆盖,产出的还是旧文件。loudnorm 的 TP 取值范围 [-9,0],传 2 直接报错;简单场景 volume+alimiter 足够。&& 链后半静默不跑,表现为”文件不存在”假象)——多 glob 清理逐条执行或 setopt null_glob。mv 到 ~/.Trash/),否则下次脚本秒发;rm 会被 Bash 安全层拦截且连带中断 && 链。[20:a] 指到第一张字幕 PNG 上,ffmpeg 只吐一句 Error binding filtergraph inputs/outputs: Invalid argument。拼接 inputs 后立刻 assert len(inputs) == 2*(段数*2+1+字幕张数)。\,(反斜杠会原样进表达式解析器);z='min(...)' 与 y='(...)' 的收尾单引号一个都不能漏,漏了 ffmpeg 同样只给一句笼统的 Invalid argument。优先用 Python 列表传参并 assert 引号成对。uv tool install edge-tts(装到 ~/.local/bin/edge-tts),国内直连必挂 NoAudioReceived,每条都要带 --proxy socks5h://127.0.0.1:7890。.stage 之外:.stage 自己是 position:absolute 的包含块,徽章塞进去后 top:70px 是相对 stage 算的,会掉到漫画格上面跟封面标题叠在一起。封面页本身已带大标题,竖屏不要再叠徽章。[0:v][0:a][1:v][1:a]…,写成”全部视频在前、全部音频在后”会报 Stream specifier ':v' matches no streams(concat 按位置取流,不是按名字);2 段能过 8 段报错的假象来自测试样本太小。-ss 60 写在第一个输出文件名之后会被当成 output 选项静默忽略,两帧抽到同一时刻;要么分开多条命令,要么用 select='eq(n,1800)' 多输出写法。[标签] 进 labels,两者严格分开;定义串混进 concat 标签序列会报 Trailing garbage after a filter。最终合成两处易错:BGM 输入索引 = 段数(0起)差一位就 Error binding filtergraph;输入计数断言数 -i 旗标个数。成片与封面、文案包复制到 outputs 并 present_files;若仓库化发布,视频放 videos/(<100MB 免 LFS),push 前先 git pull --rebase(多会话并发仓库)。