尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python+moviepy:打造自动生成竖屏短视频的完整流水线

Python+moviepy:打造自动生成竖屏短视频的完整流水线 简介这是一份基于Python与moviepy模块的短视频制作设计源码适合熟悉Python基础、希望提升视频处理效率的开发者或内容创作者。资源将图片、音频、文字与视频剪辑整合为一套自动化流程覆盖封面生成、文本图片制作、背景音乐合成、成片输出等常见环节。压缩包共95个文件包体50.45MB其中以67张JPG图片素材为主另有6个Python源码文件、3个编译文件以及5个XML配置文件图片用于片头片尾与场景插图源码负责视频剪辑与特效逻辑XML便于调整参数。项目同时包含多段MP3/M4A音频和TTF字体可直接用于试听与样式复现。目前已有161人学习适合想快速上手moviepy、用代码批量生成短视频的用户。通过源码可了解从静音音频生成、文本图片渲染到最终视频合成的完整实现思路并结合注释与readme快速移植到自己的项目中。1. 不点剪辑按钮点运行这套 Python moviepy 短视频源码解决什么问题短视频制作最耗时间的从来不是拍而是剪。尤其做知识口播、语录、课程讲解这类“图文 配音”形态素材是现成的文案和图片真正的工作量全在剪辑软件里一帧帧对时间轴。这套基于 Python 与 moviepy 模块的短视频制作源码要解决的就是这个痛点把一段文案丢进去自动产出带文字卡片、语音旁白和背景音乐的竖屏成片。它不是一个剪辑软件而是一条生成流水线跑一次输出 short_video_demo.mp4 和配套封面图。适合两类人想批量做短视频又不想手动剪辑的内容从业者以及刚学 moviepy、想拆一个真实完整工程源码的开发者。2. 先把流水线理清楚六个 py 文件的分工与执行顺序拿到压缩包先别急着pip install第一步是搞明白这几个.py文件谁是上游谁是下游。这套工程的命名风格非常直白generate_前缀加产物名基本就是流水线本身。把文件按职责排开你会发现它跟一条手工剪辑线的流程完全对应。2.1 从文件名反推生产流程音频、文字图、视频三段式先看核心文件我整理成了一张职责表文件职责对应产物generate_audio.py把文案声源加工成全片旁白音轨final_audio.mp3、audio_without_cover_time.mp3generate_text_pics.py把每段文案渲染成带背景的文字卡片pics_for_video/ 下几十张 stationery_full_content_*.jpggenerate_cover_pic.py生成竖屏封面图可叠加角标short_video_cover_pic.jpggenerate_video.py用 moviepy 合成图片序列、旁白、BGMshort_video_demo.mp4app.py一键串联以上全部流程最终成品 中间产物test.py环境冒烟测试验证依赖和字体可读控制台输出音频类三个文件对应三个状态silent_audio_2s.mp3是封面时段的静音占位C400000lBOOF2N13oN.m4a是原始语音样例可以是你自己准备的旁白也可以是 TTS 批量合成后的结果bgm.mp3是背景音乐。结合这三个文件能还原出音频处理链路原始语音 → 插入封面静音段 → 叠加 BGM → 得到final_audio.mp3。而audio_without_cover_time.mp3则是去掉封面静音段后的纯净音轨专门给视频合成用。这个细节很重要后面踩坑环节我会再展开。图片链路同样清楚img/stationery.jpg是信纸风格的主背景generate_text_pics.py把文字压上去后输出到pics_for_video目录flag.png是角标或水印封面合成时会用到font/下三套中文字体保证在任何机器上渲染效果一致不依赖系统字库。工程目录里还有.idea、__pycache__、.iml这类文件它们是 IDEA 工程配置和 Python 编译缓存对运行没有影响直接忽略即可。真正要重点关注的是requirements.txt的依赖版本以及readme.txt里对输入文案格式的约定——不少下载源码后跑不动的人问题不在代码逻辑而在依赖版本不对。2.2 首次跑通装环境、按顺序执行别跳步我建议第一次跑不要直接用app.py一把梭而是按下面顺序手动执行。这样每一步失败了你都知道坏在哪一环。# 1. 建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt # 2. 先生成最终旁白音频 python generate_audio.py # 3. 再生成文字卡片 python generate_text_pics.py # 4. 生成封面图 python generate_cover_pic.py # 5. 最后合成视频 python generate_video.py这段顺序有讲究步骤 2 必须在步骤 3 前面。原因在于短视频的时间轴以音频为基准一句话读多久对应那张文字卡片就得展示多久。generate_audio.py先算出整段音频的时长和分段信息generate_text_pics.py才能决定每张卡片展示多少秒、要不要在结尾留停留时间。步骤 4 的封面时长会参考silent_audio_2s.mp3的 2 秒静音这也是为什么音轨文件里专门拆出一个audio_without_cover_time.mp3——它去掉了封面那 2 秒空白让正片在封面展示结束后再开口。你要是把顺序反过来图片先按固定时长算好再对音频最后就会到处返工。依赖方面requirements.txt里主要就是 moviepy 和 Pillow外加 numpy 做图像数组转换。这里有一个版本坑我直接说了moviepy 别装最新版固定在 1.0.3 到 1.x 的稳定版本区间能避开大量 imageio 兼容报错。如果你打开app.py看会发现它做的事情就是把上面几条命令在内存里按顺序调用一遍顺手把中间产物写到对应目录。手动跑一遍的另一个好处是你能看清final_audio.mp3、audio_without_cover_time.mp3、pics_for_video下几十张卡片这些中间产物长什么样后续改需求时就不会把流水线当黑匣子。提示首次执行建议先跑python test.py做环境自检。它不会生成大视频只验证 moviepy、PIL 和字体文件能否正常读取省得你在第 5 步才发现字体路径写死导致满屏方块字。3. 文字卡片为什么能看generate_text_pics.py 的版面参数与字体选型短视频的“图文”类内容成片质量七成取决于文字卡片好不好看。这套源码里generate_text_pics.py做的事就是把每段文案渲染到信纸背景上不是简单 print 一行文字而是有完整的版面控制。以下代码是我按这类工程的通用写法还原的核心逻辑你打开源码对照时会发现结构基本一致。3.1 背景、字体与文字渲染的合成逻辑# 核心逻辑文字压到信纸背景上 from PIL import Image, ImageDraw, ImageFont BG_PATH img/stationery.jpg FONT_BODY font/AlimamaFangYuanTiVF-Thin.ttf OUT_DIR pics_for_video def render_text_card(text: str, index: int, size(1080, 1920)): # 统一尺寸竖屏 1080x1920背景等比填满 bg Image.open(BG_PATH).convert(RGB).resize(size) draw ImageDraw.Draw(bg) # 正文用细体字号 64适配 1080 宽屏 font ImageFont.truetype(FONT_BODY, 64) margin_x, margin_y 120, 320 # 左右留白 120上下留白 320 chars_per_line 14 # 每行最多 14 个汉字 line_height 108 # 行距略大于字号防止笔画粘连 lines [ text[i:i chars_per_line] for i in range(0, len(text), chars_per_line) ] y margin_y for line in lines: draw.text((margin_x, y), line, fontfont, fill(45, 45, 45)) y line_height bg.save(f{OUT_DIR}/card_{index:03d}.jpg, quality92)这里每个参数都直接影响成片观感。chars_per_line 14是基于 64px 字号反推的1080 宽减去左右各 120 边距剩 840 像素可用宽度14 个汉字大约占 850 像素刚好不会顶到边。margin_y取 320 是因为信纸背景上部有装饰空间文字不能从屏幕顶部开始铺否则会压住背景花纹。fill用 (45, 45, 45) 这种接近黑的深灰而不是纯黑是因为纯黑在 JPEG 压缩后容易发脏深灰更耐看这个细节在不少改版代码里都被忽略了。line_height 108值得单独说。中文字符实际渲染时视觉高度小于字体的 em 框如果行距只取字号本身 64px两行文字之间几乎没有缝隙看起来糊成一团。108 相当于 1.68 倍行距属于中文正文的安全区间。如果你改文案时发现某段字特别多可以把chars_per_line调到 16 同时把字号降到 56但不要低于 52否则在手机小屏上会开始费眼。3.2 分镜怎么划一段文字对应一张卡片这套源码背后的分镜思路很朴素文案有多少个语义段落就生成多少张卡片每张卡片对应成片里固定长度的一个镜头。常见做法是先在文案里用空行把段落切好generate_text_pics.py读取时按空行切分输出card_000.jpg、card_001.jpg这样的连续序号。序号连续性很重要因为generate_video.py后面按序号顺序拼接中间缺一张号视频里就直接黑场。段落长度也有讲究。以 1080x1920 竖屏、正文 64 号字为例一屏舒服的容量是 4 到 8 行也就是 56 到 112 个字。超过这个量文字卡片读起来就像在屏幕上看 PDF观众通常没耐心。实际测试下来单张卡片 60 到 90 个字、配合 3 到 5 秒展示时间完播数据最好。这不是源码里写死的参数但决定了你要不要改换行逻辑和每屏字数。如果要生成封面卡generate_cover_pic.py走的是另一套逻辑标题字号拉到 96 到 120用粗体AlimamaShuHeiTi-Bold.ttf文字位置居中偏上底部叠加flag.png角标输出short_video_cover_pic.jpg。封面和正片卡片刻意保持同一种字体族、同一种留白风格这样视频列表页和正片视觉是连着的观众点进去不会有“货不对板”的落差。3.3 三套字体为什么要一起打包别赌系统字库font/目录里的三套字体是本项目最关键的一步自带字体不读系统字库。原因很现实开发者的电脑上大概率有微软雅黑或苹方但项目换到别的机器、或者部署到 Linux 服务器跑批量任务时系统里可能只有文泉驿之类的基础字体渲染出来的标题又细又没气势中文甚至直接变成方块。所以我在 3.1 的代码里用相对路径font/...而不是字体名。PIL 加载字体的规则是传绝对或相对路径就按路径找传字体名才会去翻系统字库。写路径时建议用os.path.join拼接别写死反斜杠Windows 和 Linux 的兼容性都能照顾到。如果以后在自己工程里复刻这套方案记住一个原则凡是用于出片的字体一律进仓库。我后来做任何模板类项目都会顺手检查字体授权阿里妈妈这两套字体可以免费商用这一点对做内容的人来说很省心。4. moviepy 合成出片时间轴对齐与输出参数前面生成的图片和音频都是素材真正把它们变成short_video_demo.mp4的是generate_video.py。moviepy 的 API 看着简单但它的时间体系和音频体系有一堆隐性规则不摸透就会翻车。这一章把合成逻辑和输出参数讲清楚避坑细节放到下一章。4.1 ImageClip 序列 双音轨混合的参考实现# generate_video.py 核心流程还原 from moviepy.editor import ImageClip, AudioFileClip, CompositeAudioClip, concatenate_videoclips FPS 24 IMG_DIR pics_for_video VOICE_PATH audio_without_cover_time.mp3 BGM_PATH bgm.mp3 OUT_PATH short_video_demo.mp4 # 1. 以语音时长为基础先算每张卡片展示多少秒 voice AudioFileClip(VOICE_PATH) img_count 20 # 按实际卡片数修改 base_duration voice.duration / img_count # 2. 逐张读图统一时长后拼接 clips [] for i in range(img_count): c ImageClip(f{IMG_DIR}/card_{i:03d}.jpg).set_duration(base_duration) clips.append(c) video concatenate_videoclips(clips, methodcompose) # 3. BGM 降音量后铺满全片和语音混音 bgm AudioFileClip(BGM_PATH).volumex(0.15).subclip(0, video.duration) final_track CompositeAudioClip([voice, bgm]) video video.set_audio(final_track) # 4. 输出 H.264 AAC video.write_videofile(OUT_PATH, fpsFPS, codeclibx264, audio_codecaac)这段代码的核心是“让视频迁就音频”voice.duration是旁白总时长img_count是卡片数两者相除得到每张卡片的展示时长。这个写法有一个隐含假设每段旁白的时间大致均匀。如果文案里某一段特别长、另一段特别短均匀分配就会造成长段落没读完就切图。更稳的做法是给每段语音单独计时再用set_start显式指定每张卡片的起始时间而不是依赖除法的平均值# 段落级时间轴每张卡片跟随对应语音段的实际时长 timeline [0.0] for seg in voice_segments: timeline.append(timeline[-1] seg.duration) for i, c in enumerate(clips): c c.set_start(timeline[i]).set_duration(voice_segments[i].duration) video CompositeVideoClip(clips, size(1080, 1920))参数上img_count和base_duration在源码里会作为变量暴露在generate_video.py顶部改文案后不需要动函数体只改这两个数字。这种方式对新手友好但有一个副作用图片目录里实际生成的卡片数和填的img_count不一致时脚本会直接报FileNotFoundError。这时优先去pics_for_video目录数一下实际有几张而不是怀疑 moviepy 装错了。使用concatenate_videoclips(methodcompose)的另一个注意点是它会自动统一所有 clip 的尺寸但这也意味着如果某张图片分辨率不一致合成时会悄悄缩放变形所以更建议在图片生成阶段就统一尺寸把脏活留在上游。4.2 出片参数表分辨率、码率与帧率的选择这套源码的输出编码参数整理成一张表替换需求时照着调参数推荐值说明分辨率1080x1920竖屏短视频标准避免黑边帧率 fps24静态卡片场景 24 即可30 会让文件更大视频编码 codeclibx264兼容性最好短视频平台通吃视频码率 bitrate4000-6000 kbps文字边缘锐利度不够时上调音频编码 audio_codecaac平台兼容性最佳音频码率192 kbps旁白内容足够清晰每卡时长3-6 秒与语音段落时长保持一致需要提醒的是moviepy 的write_videofile传码率参数用bitrate如果你把码率设到 8000 kbps 以上文件体积会迅速膨胀但文字卡片的画质提升非常有限因为静态图片本身的复杂度不高。一般 1080x1920 的图文类视频4000 到 5000 kbps 已经完全够用这个档位也是各短视频平台二次压缩后损失最小的范围。帧率方面静态卡片加简单位移的场景 24 帧就够了没必要上 60 帧纯静态画面用高帧率只会白白增加编码时间和文件体积。5. moviepy 合成避坑五个亲测翻车点与修正方法这一章是我实际跑这套源码、以及拿它改需求时遇到的真实问题按“现象 → 原因 → 解决”列出来。每一条都对应generate_video.py里某个具体环节你可以直接对照排查。5.1 坑 1 与坑 2首尾截断、音画错位坑 1成片开头几秒没声音最后一句话被截断。现象是播放视频前 2 秒画面在走但语音沉默播放到结尾处旁白最后两个字被硬生生切掉。原因是音轨文件选错了直接把final_audio.mp3接到视频上而这个文件包含了封面时段的 2 秒静音正片拼接时图片却从第 0 秒开始排开头空 2 秒结尾就溢出 2 秒。解决方法是音轨改用audio_without_cover_time.mp3图片序列的时长从封面结束之后开始计算而不是把封面段也算进正片。从根上讲就是我在第 2 章强调过的那个文件分工含封面静音的是给发布平台用的完整音频不含静音的是给合成器用的。从那以后我每次合成前都会先看一眼音轨开头是不是有一段静音确认后再往下走。坑 2音画错位越到后面越明显。现象是前面几句还挺准到第 15 句左右文字切换明显晚于语音。原因有两个一是每张卡片时长用了四舍五入后的浮点数20 张卡片每张差 0.03 秒累加到结尾就有半秒多的偏移二是concatenate_videoclips在methodcompose模式下会对每个 clip 重新布局引入额外的时间开销。解决方法是改用 4.1 里的set_start加累计时间轴每张卡片显式指定绝对起始时间累计误差只来源于最后一遍整体重排而不是每个片段逐级累加。这种写法还能顺便解决“某段文字特别长导致画面提前切走”的问题因为你给每段卡片分配的是实际语音时长而不是平均时长。5.2 坑 3 与坑 4BGM 盖人声、黑边变形坑 3背景音乐盖过人声或者 BGM 在某个点突然中断。现象是 bgm 声音比旁白还响人声听不清把 BGM 音量调低后又出现音乐在某一点硬切。原因首先是volumex(0.15)这个系数是拍脑袋定的不同 BGM 的平均响度差很多有的歌原响度就高0.15 还是压不住其次是bgm.subclip(0, video.duration)在 BGM 长度小于视频长度时会抛错而大于视频长度时又不会自动跟随视频截断结果就是音乐结尾处有爆音或突然消失。解决方法是先测响度再定系数# 查看 BGM 平均响度决定 volumex 系数 ffmpeg -i bgm.mp3 -af volumedetect -f null -如果mean_volume在 -20 dB 以下说明音乐本身安静volumex可以给 0.2 到 0.25如果mean_volume在 -10 dB 附近那 0.1 都得慎重。混音目标应该是 BGM 比旁白低 12 到 18 dB。subclip那行建议改成bgm.set_duration(video.duration)或者对短音乐做循环拼接避免边界处断音。这条排查对任何 moviepy 双音轨项目都适用不是只有这套源码会踩。坑 4输出视频有黑边或画面被拉伸变形。现象是背景明明做了 1080x1920 的竖版图但成片在手机上看是横版或者画面内容被压扁。原因是write_videofile默认输出尺寸由第一个 clip 决定如果目录里残留了上一轮不同分辨率的图片ImageClip读到的尺寸就不一致合成时被 compose 模式强行统一造成变形。解决方法是显式限定输出尺寸拼接前对背景图做中心裁剪后统一resize((1080, 1920))并在CompositeVideoClip里显式传size(1080, 1920)。图片预处理的具体写法见第 3 章核心原则是让进入 moviepy 的每张图片都已经是同一尺寸不要把缩放交给 moviepy 自动处理。5.3 坑 5moviepy 版本和 imageio 的兼容性翻车坑 5运行时报AttributeError: module imageio has no attribute imsave或类似的 import 错误。现象是代码逻辑完全没动换了一台新机器或重新装依赖后跑到write_videofile就崩。原因是 moviepy 对 imageio 的版本有隐性依赖关系新版 imageio 移除了某些旧接口旧版 moviepy 还在调用。解决方法是把 moviepy 固定到 1.0.3 这个稳定版本同时不要单独升级 imageio让它跟着 moviepy 的依赖解析走。越新的 moviepy 不一定是越好的 moviepy这类成熟多年、更新缓慢的库保守版本反而是最省心的。如果你在pip install -r requirements.txt时没锁版本号强烈建议在虚拟环境里跑一次把实际安装到的版本号写进 requirements避免过两个月再跑时环境已经变了。6. 把流程改成自己的生产线批处理、替换 BGM 与音画自检源码跑通只是起点多数人是拿它来批量产内容的。这一章把我实际操作中的三个关键动作讲清楚。6.1 一个文案目录对应一条成片我一般会把脚本改造成可传参的入口readme.txt里写明输入文案格式每篇文案放一个独立目录跑完自动把 mp4 和封面归到输出目录。批处理脚本通常长这样# batch_build.py遍历 articles 下所有文案目录逐条出片 import subprocess, os for folder in os.listdir(articles): os.chdir(farticles/{folder}) subprocess.run([python, generate_audio.py], checkTrue) subprocess.run([python, generate_text_pics.py], checkTrue) subprocess.run([python, generate_cover_pic.py], checkTrue) subprocess.run([python, generate_video.py], checkTrue) os.chdir(../..)注意subprocess.run的checkTrue任何一步失败都会立刻中断不会带着半成品继续往下跑。批量任务里最怕静默失败某条文案没生成音频但还是强行合成视频最后出一堆废片。中断后优先看是哪个目录挂的处理完重新跑那一个目录就行不用全部重来。6.2 替换 BGM 与最终自检清单替换 BGM 只需把新音乐改成bgm.mp3覆盖原文件但有两件事要注意一是音乐时长别比成片短太多短了会触发 5.2 里的中断坑建议准备 2 分钟以上的纯音乐二是正式发布前把响度检测跑一遍按mean_volume调整volumex系数。成片出来先别急着发在有线耳机上从头对一遍时间轴和音画确认没有断音、截断和黑边再走发布流程。这套源码我拿到手之后第一件事是按第 2 章的方式手动跑通再逐步替换成自己的 TTS 接口和背景乐。从那以后每次动到生成顺序、换字体或改 BGM我都会强制走一遍test.py加 20 秒小样输出的流程确认无异常才放行完整出片。希望帮到你。本文还有配套的精品资源点击获取
返回列表