十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python搭建AI视频自动化流水线:从脚本到成片

用Python搭建AI视频自动化流水线:从脚本到成片 做短视频或知识类视频时最消耗时间的往往不是剪辑而是“写脚本、找素材、配音、加字幕”这四个环节。很多人实际的工作流是先在文档里写文案再打开素材网站一页一页筛选下载然后用配音工具逐句合成语音最后在剪辑软件里手动对齐字幕。这样一期视频少则一两个小时多则半天如果文案中途改了一版配音、字幕、时间轴全部要重来。下面要搭建的是一条“输入主题输出成片”的 AI 流水线脚本由大模型生成视频素材由脚本自动搜索下载配音使用 TTS 合成字幕根据音频时长生成最后用 FFmpeg 完成视频拼接。这条流水线不是某个特定平台的功能而是一组 Python 脚本加一个 Shell 调度文件。读者可以把它复制到本地跑通也可以拆开替换其中的任意环节适应自己的业务。这篇文章适合三类读者需要批量制作内容视频的运营或开发者想了解大模型、TTS、字幕识别、FFmpeg 如何串成实际应用的工程师以及刚接触自动化想知道多环节任务应该如何拆分、如何排错的同学。读完以后你能搭出一个最小可运行版本并且知道每个环节最薄弱的点在什么地方。1. 先把“做视频”拆成五个环节再谈自动化1.1 五个环节各自解决什么问题自动化最难的不是写代码而是拆任务。一个完整视频生产流程可以拆成下面五个环节脚本生成根据主题生成标题和分段台词同时给出每个分段的画面描述。素材获取根据画面描述到素材库搜索并下载视频片段。配音合成把每段台词转换成一段音频。字幕生成生成带时间轴的 SRT 字幕文件。视频拼接把素材、配音、字幕合成最终视频。每个环节都有明确的输入和输出。脚本生成的输出是结构化 JSON素材获取的输出是一批本地视频文件配音合成的输出是每段音频字幕生成的输出是 SRT 文件视频拼接的输出是最终 MP4。这样做的好处是任何一步出错只需要重跑那一步不需要从头开始。比如配音生成失败脚本和素材都已经存在直接重新执行第三个脚本即可。1.2 为什么每个环节要独立成脚本如果把五个环节写进一个大文件代码会非常长而且很难排查。独立成脚本之后可以单独测试每个环节也可以单独替换某个实现。例如脚本生成环节今天用在线大模型 API明天想换成私有化部署的模型只需要改01_generate_script.py下游环节完全不受影响。素材获取环节也是如此Pexels 不支持某类素材时可以替换成 Pixabay 或其他图库 API上游脚本和下游合成脚本都不用动。每个脚本采用相同的命令行风格使用--script、--audio-dir、--output之类的参数传递路径。这样串联时非常直观阅读日志时也能快速定位是哪个环节出了问题。1.3 环节之间用文件和 JSON 传递数据环境之间不通过全局变量传递数据而是通过文件。最重要的中间文件是script.json它定义了整条流水线的内容结构。{ title: 人工智能如何改变内容生产, segments: [ { index: 1, scene: 城市夜景霓虹灯闪烁, content: 你可能没有注意到你刷到的很多视频已经不是纯人工做出来的。 }, { index: 2, scene: 电脑屏幕上代码快速滚动, content: 从脚本到配音一条 AI 流水线正在接管大量重复劳动。 } ] }segments列表是整条流水线的核心数据结构。index用于文件命名scene是素材搜索时的关键词来源content是配音和字幕的文本来源。各环节的数据交接关系可以用下面这张表概括。环节输入输出关键文件脚本生成主题script.jsonwork/script.json素材获取script.json视频片段work/materials/seg_01_0.mp4配音合成script.json音频片段work/audio/seg_01.mp3字幕生成script.json 音频SRTwork/subtitle.srt视频拼接素材 音频 SRTMP4work/output.mp4只要这几个文件路径稳定流水线就能稳定工作。2. 环境准备Python、FFmpeg、API Key 三者缺一不可2.1 依赖清单和安装方式本地跑通这条流水线需要准备三部分环境Python 3.9 以上、FFmpeg 4.x 以上以及一个可调用的大模型 API Key、一个素材库 API Key。Python 依赖尽量保持精简避免引入太重的东西。requests2.31.0 edge-tts6.1.12安装命令pip install -r requirements.txtFFmpeg 不在 pip 依赖里需要单独安装。macOS 可以用 Homebrewbrew install ffmpegUbuntu 或 Debiansudo apt update sudo apt install ffmpeg fonts-noto-cjk其中fonts-noto-cjk是中文语言包后面字幕渲染需要它否则中文会显示成方块。安装完成后检查版本ffmpeg -version ffprobe -version如果命令行提示无法识别ffmpeg说明 FFmpeg 没有安装或者没有加入 PATH 环境变量。这类报错在 Windows 上尤其常见安装后需要重启终端让环境变量生效。2.2 项目目录结构建议使用下面的目录结构把脚本和中间产物分开。video-pipeline/ ├── requirements.txt ├── config.py ├── pipeline.sh ├── scripts/ │ ├── 01_generate_script.py │ ├── 02_fetch_material.py │ ├── 03_generate_audio.py │ ├── 04_generate_subtitle.py │ └── 05_compose_video.py └── work/ ├── audio/ ├── materials/ └── output/work目录用于存放所有中间产物。audio和materials目录会自动创建。第一次运行时可以先手动创建work目录也可以由脚本用os.makedirs自动创建。2.3 公共配置通过环境变量注入API Key、模型名称、配音音色这些参数不适合写死在代码里。这里建议集中放在一个config.py中统一从环境变量读取。import os LLM_API_BASE os.getenv(LLM_API_BASE, http://localhost:8000/v1) LLM_API_KEY os.getenv(LLM_API_KEY, ) LLM_MODEL os.getenv(LLM_MODEL, qwen-plus) PEXELS_API_KEY os.getenv(PEXELS_API_KEY, ) PEXELS_PAGE_SIZE int(os.getenv(PEXELS_PAGE_SIZE, 10)) TTS_VOICE os.getenv(TTS_VOICE, zh-CN-YunxiNeural) TTS_RATE os.getenv(TTS_RATE, 0%) WORK_DIR os.getenv(WORK_DIR, work)运行脚本时通过环境变量注入配置export LLM_API_KEYyour-llm-key export PEXELS_API_KEYyour-pexels-key export TTS_VOICEzh-CN-YunxiNeural python scripts/01_generate_script.py --topic 人工智能如何改变内容生产本地开发可以放在.env文件里但不要把.env提交到 Git。生产环境建议放到配置中心或容器环境变量中。3. 第一步用大模型生成结构化脚本3.1 提示词决定后续链路的稳定性脚本生成这一步最关键的其实不是代码而是提示词。大模型如果自由发挥输出的可能是散文、表格、Markdown 标题后续解析就会很麻烦。所以提示词里需要明确三件事角色是短视频脚本编辑必须输出 JSONJSON 的结构必须固定。SYSTEM_PROMPT 你是一个短视频脚本编辑。请根据用户给出的主题生成一个适合制作短视频的脚本。 要求 1. 脚本分为 6 到 10 个段落。 2. 每个段落包含独立的画面描述 scene 和配音台词 content。 3. 只返回 JSON不要返回任何解释文字。 4. 不要使用 Markdown 代码块包裹 JSON。 5. JSON 结构示例 { title: 视频标题, segments: [ {index: 1, scene: 画面描述, content: 配音台词}, {index: 2, scene: 画面描述, content: 配音台词} ] } 这里注意提示词里强调“不要使用 Markdown 代码块包裹 JSON”是因为很多模型会习惯性输出代码块代码块外面再套一个json标记会干扰json.loads解析。3.2 调用兼容 OpenAI 的接口并解析 JSON现在很多大模型服务都提供 OpenAI 兼容的 API格式相同替换时只需要改地址和模型名。下面代码演示如何调用/chat/completions接口并把响应解析成字典。import argparse import json import os import requests def call_llm(topic: str, api_base: str, api_key: str, model: str) - str: url f{api_base}/chat/completions headers {Authorization: fBearer {api_key}} payload { model: model, messages: [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: f主题{topic}}, ], temperature: 0.7, } resp requests.post(url, jsonpayload, headersheaders, timeout60) resp.raise_for_status() data resp.json() return data[choices][0][message][content].strip()解析响应时要做一层容错把常见的 Markdown 代码块包裹去掉再交给json.loads。def parse_script(raw: str) - dict: raw raw.strip() if raw.startswith(): raw raw.strip() if raw.startswith(json): raw raw[len(json):].strip() data json.loads(raw) if segments not in data or not isinstance(data[segments], list): raise ValueError(script.json 缺少 segments 列表) return data主函数负责读取参数、调用模型、写文件。def main(): parser argparse.ArgumentParser() parser.add_argument(--topic, requiredTrue, help视频主题) parser.add_argument(--output, defaultwork/script.json, help输出 JSON 路径) args parser.parse_args() raw call_llm( args.topic, os.getenv(LLM_API_BASE, http://localhost:8000/v1), os.getenv(LLM_API_KEY, ), os.getenv(LLM_MODEL, qwen-plus), ) script parse_script(raw) os.makedirs(os.path.dirname(args.output) or ., exist_okTrue) with open(args.output, w, encodingutf-8) as f: json.dump(script, f, ensure_asciiFalse, indent2) print(json.dumps(script, ensure_asciiFalse, indent2)) if __name__ __main__: main()实际项目中建议把原始响应保存一份到日志文件便于模型返回非 JSON 时排查。3.3 脚本分段粒度对素材、配音、字幕的影响分段数量直接决定后续素材下载、TTS 合成和字幕生成的规模。如果分段太少比如只有 3 段每段台词会特别长一个素材画面持续几十秒视频看起来非常枯燥。如果分段太多比如 20 段每段素材下载、音频生成、字幕对齐都会成倍增加而且单段文案太短TTS 和字幕容易显得碎片化。比较稳妥的范围是 6 到 10 段。每段台词控制在 40 到 80 个中文字符这样单段视频时长在 10 到 20 秒之间最终视频约 1 到 3 分钟适合知识科普类内容。这里还有一个容易踩的坑index字段必须是整数后续文件命名依赖它。如果模型返回的是字符串1在处理前先统一转成整数避免文件名出现seg_01.mp3和seg_1.mp3混用的问题。4. 第二步按脚本自动搜索并下载视频素材4.1 素材源选型优先用开放 API找素材最常见的错误做法是直接写爬虫去抓网页上的视频这样既不稳定又有版权风险。更稳妥的方式是使用素材平台提供的开放 API。以 Pexels 为例它允许开发者通过 API 搜索免费可商用视频使用时需要在官网申请 API Key然后在请求头里带上Authorization: your_api_key搜索接口是curl -H Authorization: your_api_key \ https://api.pexels.com/videos/search?querycityper_page5orientationlandscape返回结果里包含videos数组每个视频对象中又有video_files数组里面是不同的清晰度和格式地址。代码中需要从video_files里筛选宽度大于等于 1280、高度大于等于 720 的横屏视频。4.2 从脚本里提取关键词并下载本项目的素材关键词来自script.json中的scene字段。下载器遍历每个段落用scene前几个字或整句去搜索然后下载第一个符合条件的结果。import json import time from pathlib import Path import requests def fetch_videos(keyword: str, api_key: str, per_page: int 5) - list: url https://api.pexels.com/videos/search headers {Authorization: api_key} params { query: keyword, per_page: per_page, orientation: landscape, } resp requests.get(url, headersheaders, paramsparams, timeout30) resp.raise_for_status() data resp.json() result [] for video in data.get(videos, []): for item in video.get(video_files, []): width item.get(width) or 0 height item.get(height) or 0 link item.get(link) if link and width 1280 and height 720: result.append({link: link, width: width, height: height}) break return result def download(url: str, target: Path): resp requests.get(url, timeout60) resp.raise_for_status() target.write_bytes(resp.content)主函数中文件名使用seg_{index:02d}_{序号}.mp4这样后续拼接时可以根据文件名排序。def main(): parser argparse.ArgumentParser() parser.add_argument(--script, defaultwork/script.json) parser.add_argument(--material-dir, defaultwork/materials) args parser.parse_args() material_dir Path(args.material_dir) material_dir.mkdir(parentsTrue, exist_okTrue) script json.loads(Path(args.script).read_text(encodingutf-8)) api_key __import__(config).PEXELS_API_KEY for seg in script[segments]: keyword seg[scene][:10] try: videos fetch_videos(keyword, api_key, per_page5) except Exception as exc: print(fseg {seg[index]} search failed: {exc}) continue if not videos: print(fseg {seg[index]} no material) continue for seq, item in enumerate(videos[:1]): out_file material_dir / fseg_{seg[index]:02d}_{seq}.mp4 if out_file.exists() and out_file.stat().st_size 1024: continue try: download(item[link], out_file) print(fseg {seg[index]} downloaded: {out_file}) except Exception as exc: print(fseg {seg[index]} download failed: {exc}) time.sleep(1)下载时强制间隔 1 秒避免触发素材接口的频率限制。4.3 素材文件命名和本地校验素材下载不可靠是常态尤其是不同地区网络差异较大时下载可能超时或返回空文件。因此下载完成后要做两项校验文件是否存在且大小大于 1KB。是否能被 ffprobe 正常识别文件头是否完整。如果素材文件只有几字节或者无法识别最好的策略是直接删除让下一次运行重新下载。另外scene作为搜索关键词并不总是理想。比如“电脑屏幕上代码快速滚动”这种描述素材平台很可能搜不到结果。工程上可以准备一份搜索词映射表scene包含“代码”就搜programming包含“城市”就搜city。这一步可以在扩展阶段补上。5. 第三步合成配音并生成字幕5.1 edge-tts 把台词转成语音配音环节使用edge-tts这个库。它调用微软 Edge 的在线 TTS 服务支持多种中文音色无需额外申请 API Key对本地跑通非常友好。import asyncio import json from pathlib import Path import edge_tts async def generate_one(text: str, voice: str, rate: str, out_file: Path): communicate edge_tts.Communicate(text, voicevoice, raterate) await communicate.save(str(out_file)) def main(): parser argparse.ArgumentParser() parser.add_argument(--script, defaultwork/script.json) parser.add_argument(--audio-dir, defaultwork/audio) args parser.parse_args() audio_dir Path(args.audio_dir) audio_dir.mkdir(parentsTrue, exist_okTrue) script json.loads(Path(args.script).read_text(encodingutf-8)) voice __import__(config).TTS_VOICE rate __import__(config).TTS_RATE for seg in script[segments]: out_file audio_dir / fseg_{seg[index]:02d}.mp3 if out_file.exists() and out_file.stat().st_size 0: continue asyncio.run(generate_one(seg[content], voice, rate, out_file)) print(fseg {seg[index]} audio done) if __name__ __main__: main()edge-tts的rate参数用于控制语速默认0%。如果想读得快一点可以设置成10%、20%但不要超过30%否则听感会比较仓促。这里要注意一个坑edge-tts输出的是 MP3 格式不同段落的音频采样率可能不一致。如果后续拼接出现音画不同步可以在合成视频时统一转成 44100Hz或者先统一转成 WAV 再使用。5.2 字幕生成的两条路线字幕生成有两条技术路线适用于不同精度要求。路线 A直接根据音频时长生成 SRT。每段字幕的显示时长等于对应音频的真实时长。优点是不需要安装额外的识别模型速度快缺点是不能做到逐句对齐。对于“一段画面配一段字幕”的短视频这种方式已经够用。路线 B用 Whisper 识别语音生成逐句字幕。先合成整条音频再交给faster-whisper识别拿到每个句子的开始时间和结束时间。优点是准确缺点是首次运行需要下载模型而且识别出的文字可能和脚本原文不完全一致。本文先实现路线 A。核心逻辑是用 ffprobe 读出每段音频的时长然后累计时间轴。import json import subprocess from pathlib import Path def get_duration(path: Path) - float: cmd [ ffprobe, -v, error, -show_entries, formatduration, -of, defaultnoprint_wrappers1:nokey1, str(path), ] out subprocess.check_output(cmd, textTrue).strip() return float(out) def srt_time(seconds: float) - str: hours int(seconds // 3600) minutes int((seconds % 3600) // 60) secs int(seconds % 60) millis int((seconds - int(seconds)) * 1000) return f{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d} def build_srt(segments, audio_dir, srt_path): lines [] cursor 0.0 for seq, seg in enumerate(segments, start1): audio_file audio_dir / fseg_{seg[index]:02d}.mp3 duration get_duration(audio_file) start cursor end cursor duration lines.append(str(seq)) lines.append(f{srt_time(start)} -- {srt_time(end)}) lines.append(seg[content]) lines.append() cursor end Path(srt_path).write_text(\n.join(lines), encodingutf-8)生成的 SRT 大致长这样1 00:00:00,000 -- 00:00:05,320 你可能没有注意到你刷到的很多视频已经不是纯人工做出来的。 2 00:00:05,320 -- 00:00:12,110 从脚本到配音一条 AI 流水线正在接管大量重复劳动。5.3 时间轴怎么对齐先有音频时长再写 SRT很多新手会犯一个错误根据台词字数估算时间。比如认为 20 个字就是 10 秒然后写死 SRT。但实际朗读速度、停顿、标点都会影响时长估算很容易偏离。正确做法是“先音频后字幕”。字幕脚本在生成前先用 ffprobe 获取每段音频的真实时长再累加出每段字幕的起止时间。这样字幕总时长和配音总时长一定是吻合的。如果要精度更高的逐句对齐可以在路线 B 中使用faster-whisperfrom faster_whisper import WhisperModel model WhisperModel(small, devicecpu, compute_typeint8) segments, info model.transcribe(output_audio.mp3, languagezh, vad_filterTrue) for segment in segments: print(segment.start, segment.end, segment.text)由于 Whisper 识别文本可能和原脚本有差异工程上常用的做法是以 Whisper 的时间戳为基准但文字尽量用脚本原文如果脚本台词和识别结果相差太大说明 TTS 朗读的内容和脚本不一致需要人工检查。6. 第四步用 FFmpeg 把素材、配音、字幕合成视频6.1 先统一分辨率、帧率和编码合成视频时最大的问题是素材不一致。有的素材是竖屏有的是横屏有的是 25 帧有的是 30 帧有的编码是 H.264有的是 H.265。这些不一致会导致拼接失败。解决思路是在每个段落合成时就统一参数。这里统一输出 1280x720 的横屏视频H.264 编码AAC 音频YUV420P 像素格式。scale和crop配合使用可以把任意比例的素材缩放并裁剪到 1280x720避免黑边。scale1280:720:force_original_aspect_ratioincrease,crop1280:720force_original_aspect_ratioincrease的含义是先把画面等比放大到能够覆盖目标尺寸再用crop居中裁剪掉多出来的部分。6.2 单段合成命令解析下面这段 Python 代码构造 FFmpeg 命令把一个素材文件、一个音频文件和一个字幕文件合成为一个段落视频。import os import subprocess from pathlib import Path def compose_segment(material, audio, srt, output, duration): cmd [ ffmpeg, -y, -stream_loop, -1, -i, str(material), -i, str(audio), -vf, scale1280:720:force_original_aspect_ratioincrease, crop1280:720, fsubtitles{srt.name}:force_styleFontNameNoto Sans CJK SC,FontSize20, -t, f{duration:.3f}, -c:v, libx264, -preset, veryfast, -c:a, aac, -b:a, 192k, -ar, 44100, -pix_fmt, yuv420p, str(output), ] subprocess.run(cmd, checkTrue)这里有几个关键点-stream_loop -1让素材循环播放当素材时间短于音频时长时补齐画面。-t duration截断输出保证每段视频时长等于配音时长。subtitles滤镜读取 SRT 文件。force_style中的字体名必须是系统里真实存在的中文字体。音频采样率统一指定为 44100避免后续拼接采样率不一致。subtitles滤镜对路径非常敏感。如果传入绝对路径Windows 下的盘符冒号会被 FFmpeg 误解析。稳妥做法是运行前先切换到 SRT 所在目录然后在命令中只使用文件名。os.chdir(srt.parent)6.3 多段拼接每个段落都合成成功后使用 concat demuxer 进行拼接。def concat_segments(seg_files, output): concat_file output.parent / concat.txt content \n.join(ffile {p} for p in seg_files) concat_file.write_text(content, encodingutf-8) cmd [ ffmpeg, -y, -f, concat, -safe, 0, -i, str(concat_file), -c, copy, str(output), ] subprocess.run(cmd, checkTrue)concat demuxer 的原理是把多个视频文件的编码流直接复制到输出文件不做重新编码速度非常快。前提是每个段的编码参数完全一致。所以单段合成时必须保证分辨率、帧率、像素格式、音频采样率、编码器都一样。6.4 用 ffprobe 验证输出合成完成后不能只看文件是否生成还要确认视频流和音频流是否正常。ffprobe -v error -show_entries streamcodec_type,codec_name,width,height -of compact output.mp4正常输出应包含stream|codec_typevideo|codec_nameh264|width1280|height720 stream|codec_typeaudio|codec_nameaac再看总时长和大文件大小ffprobe -v error -show_entries formatduration,size -of defaultnoprint_wrappers1 output.mp4如果总时长明显小于配音总时长说明某个环节的-t参数或 SRT 时间轴写错了。注意只验证“FFmpeg 命令返回 0”是不够的还要用 ffprobe 检查输出文件里的视频流、音频流、分辨率、时长是否和预期一致。7. 第五步用 pipeline.sh 串联整条流水线7.1 shell 脚本的职责各环节单独能跑通之后再用一个 Shell 脚本把它们串起来。pipeline.sh的职责是接收主题按固定顺序执行五个 Python 脚本并在日志中输出进度。#!/usr/bin/env bash set -euo pipefail TOPIC${1:-人工智能如何改变内容生产} WORK_DIRwork echo [pipeline] 01 generate script python scripts/01_generate_script.py --topic $TOPIC --output $WORK_DIR/script.json echo [pipeline] 02 fetch material python scripts/02_fetch_material.py --script $WORK_DIR/script.json --material-dir $WORK_DIR/materials echo [pipeline] 03 generate audio python scripts/03_generate_audio.py --script $WORK_DIR/script.json --audio-dir $WORK_DIR/audio echo [pipeline] 04 generate subtitle python scripts/04_generate_subtitle.py --script $WORK_DIR/script.json --audio-dir $WORK_DIR/audio --srt $WORK_DIR/subtitle.srt echo [pipeline] 05 compose video python scripts/05_compose_video.py \ --script $WORK_DIR/script.json \ --material-dir $WORK_DIR/materials \ --audio-dir $WORK_DIR/audio \ --srt $WORK_DIR/subtitle.srt \ --output $WORK_DIR/output.mp4 echo [pipeline] done: $WORK_DIR/output.mp4set -euo pipefail是 Shell 脚本里的安全开关含义是-e任何一个命令返回非 0 状态脚本立即退出。-u使用未定义的变量时报错。-o pipefail管道中任何一条命令失败整条管道失败。加上这套配置流水线里任意一步失败后续步骤都不会执行可以快速定位失败点。7.2 幂等性处理重复执行流水线时要避免重复下载素材和重复生成音频。各脚本应该在文件生成前检查“文件是否已经存在且大小大于 0”满足条件就跳过。例如03_generate_audio.py里的判断out_file audio_dir / fseg_{seg[index]:02d}.mp3 if out_file.exists() and out_file.stat().st_size 0: continue但幂等判断也有副作用如果脚本内容修改了旧文件会导致更新不生效。此时可以提供一个--clean参数执行前清空work目录中的中间产物。rm -rf work/audio work/materials work/subtitle.srt work/output.mp4生产环境还可以引入“根据文件生成时间和 script.json 修改时间判断是否重跑”的策略。7.3 日志与失败定位Shell 脚本中直接使用echo打印进度已经能满足最小版本的需要。如果要长时间运行建议把日志重定向到文件bash pipeline.sh 话题名称 run.log 21这样即使终端关闭也能查看执行记录。每个 Python 脚本内部已经用print打印了关键阶段比如“seg 3 downloaded”“seg 5 audio done”。日志里如果停止在某一行问题大概率就在那一个环节。8. 常见问题排查从现象倒推根因8.1 排查顺序多环节流水线的排错最好按照数据流向逐层检查。顺序是work/script.json是否存在segments是否为空。work/materials下的素材文件是否存在大小是否正常。work/audio下的音频文件是否生成时长是否合理。work/subtitle.srt时间轴是否连续有没有重叠或空行。FFmpeg 日志报了什么错误输出文件是否完整。最终输出能否被播放器正常打开。其中任何一层异常都优先修复上一层而不是反复重跑下一层。下面这张表整理了几类高频问题。问题现象常见原因检查方式处理建议大模型返回内容解析失败模型返回了 Markdown 或非 JSON打印原始响应并保存到日志提示词强调只返回 JSON部分接口支持response_format重试一次素材下载 403 或超时API Key 无效、链接过期、触发频率限制用curl -I检查链接状态码校验 Key下载之间加延时失败换下一个素材音频文件为空或时长异常台词为空、TTS 在线服务异常检查文件大小并用播放器试听跳过空文本单独执行一句 TTS 验证网络中文字幕显示为方块系统缺少中文字体执行fc-list :langzh安装fonts-noto-cjk并确认FontName与字体实际名称一致concat 拼接失败各段分辨率、编码、帧率不一致对比每一段的 ffprobe 信息单段合同时统一编码参数必要时改为重新编码拼接重复运行产生混乱文件缺少幂等判断查看work目录文件时间生成前判断文件存在且大于 0支持--clean清空中间产物8.2 三个最容易踩的坑第一个坑是把素材下载 URL 直接交给 FFmpeg。素材平台返回的视频地址通常带有临时签名和查询参数FFmpeg 解析这种地址容易失败。正确做法是先用requests下载到本地校验文件大小再交给 FFmpeg。第二个坑是字幕时间轴按字数估算。字数不能准确反映朗读时长尤其是标点、停顿、语气词都会影响实际时间。正确做法是先合成音频再用ffprobe读取音频真实时长最后累加生成 SRT。第三个坑是忽略subtitles滤镜的路径转义。FFmpeg 每个滤镜参数都有自己的一套转义规则路径里的冒号和反斜杠容易造成解析错误。最稳妥的方案是执行前os.chdir(srt.parent)命令中使用相对文件名。遇到 Windows 路径时尤其要注意这一点。注意不要把“日志最后一行出现done”当成流水线成功。真正的成功标准应该是输出视频能被播放器打开视频流和音频流都存在总时长和配音总时长一致中文字幕渲染正常。9. 学习环境与生产环境差异以及扩展方向9.1 本地跑通与生产部署的差异本地用 Shell 脚本串联五个 Python 文件适合学习和验证。进入生产环境后这套同步脚本会暴露出不少问题没有失败重试、没有任务队列、中间文件占据大量磁盘、没有监控告警。下面是本地环境和生产环境的主要差异。维度本地跑通生产部署调度方式pipeline.sh 手动执行消息队列或任务平台调度失败自动重试存储本地 work 目录对象存储加元数据数据库模型调用单次同步请求异步任务、限流、降级、超时控制素材下载逐个 requests 下载预下载、重试队列、文件完整性检测字幕精度按音频时长估算Whisper 识别加人工抽样审核日志print 输出到终端结构化 JSON 日志加监控告警内容审核无或人工观察关键词过滤、敏感信息识别、人工审核流程如果只在本地生成少量视频直接使用下面的方案没有问题。如果要支撑一个自动化视频平台生产环境至少要引入任务队列、对象存储和审核机制。9.2 可落地的扩展方向流水线跑通后扩展空间比较大。这里提供几个方向按投资回报率排序。第一个方向是增加封面生成。可以从最终视频中抽取关键帧作为封面也可以用标题调用图像生成接口制作封面图逻辑上是在05_compose_video.py之后加一个封面模块。第二个方向是批量生产。把“输入一个主题”改成“输入一个主题列表”由 Shell 脚本循环执行。批量场景下必须引入任务状态记录记录每个主题走到哪个环节、成功还是失败。第三个方向是引入 Agent 编排。当前脚本的关键词来自scene字段比较机械。如果使用 LangChain 或 Dify 这类平台搭建 Agent可以让模型根据段落内容自主判断搜索关键词、调整段落长度甚至判断素材是否匹配。第四个方向是沉淀知识库。历史脚本、爆款主题、素材来源、发布数据都可以整理进知识库。后续生成脚本时先检索知识库中同类内容的写法再生成新脚本内容质量会更稳定。9.3 内容安全和版权检查不能省自动化程度越高越要重视内容和版权检查。素材版权方面只使用素材平台明确标注可商用的资源并保留每个素材的授权信息和出处。AI 生成的文案在发布前必须经过敏感词过滤和人工审核不能直接把模型输出发布出去。配音方面本方案只使用公开 TTS 音色。不要使用未经授权的声音克隆技术生成他人音色否则会带来肖像权和声音权的合规风险。发布方面不同平台对 AI 生成内容的规则不一致。接入自动发布功能前要重新确认目标平台的开放接口、频率限制和内容规范。最后给一个建议第一次跑通时不要太早追求“全自动”。先用命令单独执行每个脚本把script.json、素材文件、音频文件、SRT、最终 MP4 逐个手工检查一遍理解每个中间文件是怎么来的。当你能用一条命令把主题变成成片之后再逐步加入批量生产、任务队列和审核环节。那时候这条流水线就不是一个示例而是你后续做内容工具的脚手架。
返回列表