做独立开发这两年我一直在想一个问题一个人到底能做到什么程度上周我给出了自己的答案——我用 DeepSeek 定义需求 CodeBuddy 辅助编码一个人从零搞了一个 AI 口播视频生成平台取名智播坊。输入文案选形象和声音一键生成带字幕的口播视频。现在它开源了https://gitee.com/zhang-dongtao/zhibofang在线体验https://zhibofang.zhishujuzhen.com/演示视频输入文案选形象和声音30秒生成一条口播视频为什么做这个我之前做表单工具的时候想拍几个产品演示视频发抖音。结果发现• 真人出镜社恐而且拍摄剪辑要一整天• 用数字人平台月费几百起步生成一条视频还要排队• 自己用 FFmpeg 拼技术上可行但每次手动操作太麻烦我就想能不能把写文案→配音→合成视频这一整套流程自动化于是智播坊就诞生了。核心流程一条全自动的视频流水线整个视频生成过程拆成了 9 步用户只需要输入文案和选择形象剩下的全自动输入文案 → TTS语音合成 → 获取音频时长 → 生成字幕时间轴→ SRT字幕文件 → FFmpeg视频合成 → WebVTT字幕 → 上传COS → 清理临时文件核心代码都在video-pipeline.service.ts里大概 1600 行我挑几个关键的环节讲讲。第一个坎怎么把文案变成带时间轴的语音这步是整条流水线的起点也是最容易出问题的地方。短文案还好直接调 TTS 就完事。但长文案就麻烦了——火山方舟的 TTS 单次有字数限制超过就生成失败。我的方案是长文案自动拆分逐段生成再用 FFmpeg 拼接。// TTS 结果里带 segments每段都有 start 和 duration export interface TTSSegment { text: string start: number // 秒 duration: number } // 合并时在分句间插入静音间隔听起来更自然 async function concatenateAudioWithFFmpeg( segmentFiles: string[], silenceDuration: number, outputPath: string ): PromiseBuffer { // 用 FFmpeg concat demuxer 合并-c copy 直接复制不重新编码 const concatCmd ffmpeg -y -f concat -safe 0 -i ${concatListPath} -c copy ${outputPath} await execAsync(concatCmd) }segments 数据是关键——它不仅用于拼接音频后面生成字幕时间轴也全靠它。一次 TTS 调用音频和字幕数据同时拿到不用再做什么语音识别对齐。第二个坎FFmpeg 合成视频的滤镜链视频合成的核心逻辑形象图 背景 音频 字幕 最终视频。听起来简单FFmpeg 的 filter_complex 写起来能让人崩溃。async function generateVideoOneCommand( avatarPath: string, audioPath: string, subtitles: SubtitleLine[], audioDuration: number, hasNoBg: boolean, // 形象是否已抠图 config: { ... }, outputPath: string, srtPath?: string ): Promisevoid { // 构建滤镜链 const filterParts: string[] [] if (effectiveBackgroundType image) { // 背景图铺满 形象居中叠加 filterParts.push( [0:v]scale${VIDEO_WIDTH}:${VIDEO_HEIGHT}:force_original_aspect_ratiodecrease[scaled]; [1:v]scale${VIDEO_WIDTH}:${VIDEO_HEIGHT}:force_original_aspect_ratioincrease,crop${VIDEO_WIDTH}:${VIDEO_HEIGHT}[bg_img]; [bg_img][scaled]overlay(W-w)/2:(H-h)/2[body_base] ) } else { // 纯色/渐变背景 形象居中 filterParts.push( colorc${bgColor}:size${VIDEO_WIDTH}x${VIDEO_HEIGHT}:r25[bg]; [0:v]scale${VIDEO_WIDTH}:${VIDEO_HEIGHT}:force_original_aspect_ratiodecrease[scaled]; [bg][scaled]overlay(W-w)/2:(H-h)/2[body_base] ) } // 字幕烧录用 FFmpeg subtitles 滤镜比 drawtext 靠谱 if (srtPath) { filterParts.push( [body_base]subtitles${escapedSrtPath}:force_styleFontSize${config.subtitleFontSize},PrimaryColour${primaryColor},Outline2,Alignment2,MarginV20[outv] ) } }踩坑提醒字幕方案我前后换过三次。1. 第一次用drawtext滤镜——中文字符转义是个噩梦冒号、引号、反斜杠全是坑2. 第二次用ass格式——比 drawtext 好点但时间轴对齐容易偏移3. 最后用了subtitles滤镜 SRT 文件——最稳的方案SRT 格式简单不易出错force_style 参数还能控制字号颜色描边第三个坎中文字体跨平台这个坑我踩了整整一天。本地开发用 macOS 没问题部署到 Linux 服务器上字幕全是方框。原因很简单FFmpeg 渲染字幕需要指定字体文件路径不同系统的字体路径完全不一样。function getChineseFontPath(): string { const platform os.platform() // 优先用项目内的字体包最可靠 const projectFont path.join(process.cwd(), src, assets, fonts, HiraginoSansGB.ttc) if (fs.existsSync(projectFont)) return projectFont if (platform darwin) { // macOS: Hiragino Sans GB / STHeiti const macFonts [ /System/Library/Fonts/Hiragino Sans GB.ttc, /System/Library/Fonts/STHeiti Light.ttc, ] for (const font of macFonts) { if (fs.existsSync(font)) return font } } else if (platform win32) { return C:\\Windows\\Fonts\\msyh.ttc // 微软雅黑 } else { // Linux: 各种发行版路径都不一样... const linuxFonts [ /usr/share/fonts/truetype/wqy/wqy-microhei.ttc, /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc, /usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc, ] for (const font of linuxFonts) { if (fs.existsSync(font)) return font } } }最终方案把字体文件打包进项目里运行时优先读项目内字体找不到再按系统路径找。部署再也没出过问题。第四个坎形象抠图 透明背景叠加用户上传的形象图背景五花八门直接放到视频里很违和。我的处理流程上传形象 → 上传COS → 调用抠图API → 下载抠图结果 → 透明背景PNG叠加到视频// getAvatarPath 的核心流程 async function getAvatarPath(avatarId: number, userId: number) { // 1. 下载远程图片到本地 // 2. 上传到 COS获取 CDN URL // 3. 调用抠图 API传入 CDN URL const bgResult await removeBackground(cosCdnUrl) // 4. 下载抠图结果 if (bgResult.success bgResult.imageUrl) { const noBgLocalPath path.join(tempDir, avatar_nobg_${avatarId}.png) await execWithTimeout(curl -s -o ${noBgLocalPath} ${bgResult.imageUrl}) return { path: noBgLocalPath, hasNoBg: true } } }抠图成功后FFmpeg 合成时用 overlay 滤镜把透明背景的 PNG 叠到背景上效果自然很多。还有个加分项声音克隆除了 5 种预设音色智播坊还接入了火山引擎的声音克隆。用户上传一段音频样本就能用克隆的声音生成口播视频。// 克隆音色检测clone_ 或 S_ 开头的音色 ID const isClonedVoice voiceType (voiceType.startsWith(clone_) || voiceType.startsWith(S_)) if (isClonedVoice) { // 用声音复刻专用 TTS 接口 const resourceId seed-icl-2.0 // 不同于普通 TTS 的 seed-tts-2.0 // speaker 直接用原始音色 ID }普通音色用seed-tts-2.0克隆音色用seed-icl-2.0两个 resource ID 不能混用这个坑也踩了好久。技术栈一览层技术前端Vue 3 Vite TypeScript TailwindCSS Element Plus后端Express.js TypeScript SQLite JWTAI 服务火山方舟TTS 文案生成 Seedream 图片生成视频合成FFmpeg图片 TTS 字幕 → 口播视频存储腾讯云 COS实时通信WebSocket视频生成进度推送整个项目前后端加起来不到 1600 行核心代码一个人完全能 hold 住。为什么要开源说实话我本来是想靠卖源码赚钱的。但做了两个月发现个人开发者卖源码获客成本比开发成本还高。与其让代码在硬盘里吃灰不如开源出来• 让更多人能低成本搭建自己的口播视频工具• 收集社区反馈把产品做得更好• 用内容和技术实力吸引真正需要商业授权的客户开源版包含核心视频生成功能MIT 协议随便用。商业版多了套餐订阅、在线支付、订单管理这些运营模块适合想直接商业化部署的团队。怎么跑起来// bash # 克隆 git clone https://gitee.com/zhang-dongtao/zhibofang.git cd zhibofang # 安装依赖 cd frontend npm install cd ../backend npm install # 配置环境变量 cp .env.example .env # 填入你的火山方舟 API Key、腾讯云 COS 配置等 # 初始化数据库 npm run db:init # 启动 # 后端 npm run dev # 前端 cd ../frontend npm run dev详细的部署文档和一键部署脚本都在仓库 README 里支持 Nginx SSL PM2 全自动配置。写在最后做这个项目最大的感受AI 时代一个人的生产力真的可以被无限放大。我用 DeepSeek 定义需求和梳理逻辑CodeBuddy 帮我写代码我主要做架构决策和踩坑调试。整个项目从 v1.0 到 v2.5核心开发时间加起来也就两周左右。当然不是所有事都能靠 AI比如 FFmpeg 滤镜链的调试、跨平台字体的坑、TTS 流式响应的解析——这些还是得自己硬啃。但 AI 帮我省掉了大量重复性编码时间让我能把精力放在真正需要思考的地方。智播坊 Gitee 仓库https://gitee.com/zhang-dongtao/zhibofang如果觉得有用给个 Star ⭐ 就行这是对独立开发者最大的鼓励。你们做口播视频目前用的什么方案我是 FFmpeg TTS 的路子有人试过直接用数字人 API 吗效果和成本对比怎么样评论区聊聊