一年365天视频怎么做?我拿Golang写了套方案,结果真能跑
- 科技
- 2026-07-20 22:22:50
- 164
说起来你可能不信,我第一次想搞“一年365天视频”这个事,是被逼的,朋友开了个民宿,说每天都要发一条短视频宣传,问我能不能自动化,我当时心想:365条视频,每一条都手动剪辑渲染?这不是要命么,后来我琢磨了一下,用Golang撸了一套方案,从脚本生成到视频合成,还真就全自动跑起来了。
今天我就把这套东西拆开讲,不说高大上的概念,就说我踩过的坑和实际能跑的代码逻辑。
为什么选Golang来做这件事?
跨平台编译太香了
我机器是Mac,但最后部署在Linux服务器上,Golang一条命令GOOS=linux GOARCH=amd64 go build,编译出来扔上去就能跑,Python倒是也能跨平台,但依赖装起来真能让你怀疑人生。
并发处理视频是刚需
365条视频,你要是顺序处理,电脑得烧起来,Golang的goroutine天然适合并行渲染,我一开始用sync.WaitGroup控制并发数,后来改成了带缓冲的channel限流,CPU利用率直接拉满。
视频素材从哪来?我用了三种方式
| 素材类型 | 来源 | 处理方式 |
|---|---|---|
| 图片 | Unsplash API | 每天自动下载一张 |
| 视频片段 | 本地录制或剪映导出 | 切片后循环使用 |
| 字幕文字 | 自己写的模板+变量替换 | 动态插入 |
图片这块我踩过大坑:有的API限制每天下载次数,后来我直接本地存了500张高清图,每天随机选,省心。
核心代码怎么组织的?
视频元数据管理
type VideoMeta struct {
Date string `json:"date"` string `json:"title"`
ImagePath string `json:"image_path"`
AudioPath string `json:"audio_path"`
OutputPath string `json:"output_path"`
}
每天跑任务前,先读一个JSON文件,里面存了365天的元数据,这文件我是一次性写好的,用Excel转JSON,省得每天手动填。
FFmpeg命令的封装
Golang本身不处理视频,它是调FFmpeg,我封装了一个函数:
func RenderVideo(meta VideoMeta) error {
cmd := exec.Command("ffmpeg",
"-loop", "1",
"-i", meta.ImagePath,
"-i", meta.AudioPath,
"-c:v", "libx264",
"-t", "60",
"-pix_fmt", "yuv420p",
meta.OutputPath,
)
return cmd.Run()
}
这段代码看着简单,但我调了至少三天参数。"-t"参数必须放在输入文件后面,否则FFmpeg不认,还有-shortest参数也经常搞混。
并发渲染的坑
第一次跑并发,200个goroutine同时调FFmpeg,电脑风扇直接起飞,然后死机了,后来改成信号量限流:
sem := make(chan struct{}, 4) // 同时最多4个渲染任务
for _, meta := range allMetas {
sem <- struct{}{}
go func(m VideoMeta) {
defer func() { <-sem }()
RenderVideo(m)
}(meta)
}
这样CPU负载控制在80%左右,能稳定跑完。
字幕和配音怎么自动化?
文字转语音
我用的是离线TTS引擎,本地部署了一个Mozilla TTS的镜像,Golang通过HTTP调用:
func GenerateTTS(text string) ([]byte, error) {
resp, err := http.PostForm("http://localhost:5002/api/tts",
url.Values{"text": {text}, "voice": {"zh-CN"}})
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
语音生成是整个流程最慢的一环,一条60秒的音频,TTS要跑5-8秒,但好在可以缓存:如果同样的文本生成过,直接读本地文件。
字幕硬编码
我试过两种方式:
- 软字幕:FFmpeg加
-vf "subtitles=sub.srt",但有的播放器不认 - 硬字幕:
-vf "drawtext=text='你好':fontfile=simhei.ttf:fontsize=24"
最后选了硬字幕,因为兼容性好,不过要注意字体文件必须放在同目录,否则报错让你找半天。
实际跑起来会遇到哪些问题?
磁盘空间爆炸
365条视频,每条60秒,1080p,MP4格式,算下来大概200GB,我一开始没算,跑到第100条的时候磁盘满了,解决方案是边渲染边上传到OSS,本地只保留最近7天的缓存。
文件名冲突
我最初用日期命名:2025-01-01.mp4,结果遇到时区问题,服务器UTC时间比北京时间差8小时,文件名都串了,后来改成时间戳+随机字符串:
outputName := fmt.Sprintf("%d_%s.mp4", time.Now().Unix(), randomString(6))
渲染质量不一致
同样的参数,有的视频清晰有的模糊,排查发现是图片分辨率不一致,我增加了一步预处理:所有图片统一缩放到1920x1080,用nano库处理:
func ResizeImage(inputPath string) error {
img, err := imaging.Open(inputPath)
if err != nil {
return err
}
resized := imaging.Fit(img, 1920, 1080, imaging.Lanczos)
return imaging.Save(resized, inputPath)
}
有没有更简单的方案?
说实话,如果你只有几十条视频,手动剪映就行,但到了365条这个量级,自动化是唯一出路,Golang的优势在于:
- 编译成单文件部署
- 天然支持高并发
- FFmpeg调用稳定
- 跨平台无依赖
我后来把整个项目放在了GitHub上,目录结构是这样的:
video365/
├── cmd/
│ └── main.go // 入口
├── internal/
│ ├── render.go // 渲染逻辑
│ ├── tts.go // 语音生成
│ └── utils.go // 工具函数
├── data/
│ ├── metas.json // 元数据
│ └── images/ // 图片素材
├── output/ // 输出目录
└── go.mod
这一套跑下来,每天自动生成一条视频,传到自媒体平台,朋友用了三个月,粉丝涨了8000,虽然不能说全是视频的功劳,但至少断更问题彻底解决了。
关于性能的几个实测数据
| 操作 | 耗时(单条) | 365条总耗时 | 备注 |
|---|---|---|---|
| 图片下载 | 5-2秒 | 约10分钟 | 网络波动影响大 |
| TTS生成 | 5-8秒 | 约40分钟 | 可缓存重复文本 |
| 视频渲染 | 15-30秒 | 约2小时 | 看视频长度和分辨率 |
| 上传OSS | 3-5秒 | 约30分钟 | 百兆宽带 |
如果你用我的方案,建议先跑10条测试,确认没问题再全量跑,我吃过一次亏:跑了一半发现字体文件路径写错了,所有字幕都没显示,只能删掉重来。
写这篇文章的时候,我又看了眼那个项目的Star数,居然有47个,可能大家都有“一年365天视频”这个需求吧,说到底,技术就是拿来解决问题的,Golang干这事,挺顺手的。
