用Go语言给365天的一周年纪念日视频加点硬核浪漫
- 科技
- 2026-07-15 17:02:32
- 42
为什么我决定用Go语言写一个纪念日视频生成器
老实说,一开始我也没想过要自己写代码来做纪念视频,那天翻相册,看到手机里存了365张照片,从去年今天到今年今天,一张不落,我就在想,能不能把这些照片串成一个视频,配上音乐,加点转场效果,做成一个像样的“365一周年纪念日视频”,搜了一圈市面上的App,要么有水印,要么压缩画质,要么就是模板太俗气。
然后我瞄了一眼电脑上刚装好的Go环境——那瞬间就决定了:自己写。
用Go做视频生成,听起来很离谱但其实很靠谱
很多人第一反应是:Go不是写后端、做微服务的吗?能搞视频?我一开始也怀疑,但深入挖了一圈,发现Go在视频处理这块其实有很扎实的基础库,尤其是能把FFmpeg包装得特别优雅,再加上Go天生擅长并发,处理365张图片的编码任务简直是“杀鸡用牛刀”。
核心思路其实就三步
- 读取365张图片,按日期排序
- 生成一个逐帧组合的视频流
- 叠加文字、音乐、转场效果
最关键的是,我希望这个视频不是单纯的照片幻灯片,而是能体现出“365天”这个概念的——比如每一帧都显示当天日期,甚至根据季节变化色调。
从零开始写代码:边写边踩坑的记录
第一步:照片读取得有点仪式感
我先把照片按YYYY-MM-DD.jpg重命名了,存到一个文件夹里,Go读取目录文件很简单:
files, _ := ioutil.ReadDir("./photos")
sort.Slice(files, func(i, j int) bool {
return files[i].Name() < files[j].Name()
})
但很快发现一个问题——Go的ioutil在1.16以后就废弃了,哎,旧习惯改不过来,赶紧换了os.ReadDir和filepath.Walk,写代码就是这样,版本一更新,老语法就得跟着改,挺折腾的,但也逼着你保持更新。
第二步:图片合成帧——ffmpeg才是幕后英雄
光靠Go标准库肯定拼不出视频,我最开始试着用github.com/giorgisio/goav这个库,直接调FFmpeg的C接口,但编译的时候环境变量配了半小时,各种链接错误,差点砸电脑。
后来换了个思路——直接用os/exec调用系统里装好的FFmpeg,这办法虽然不那么“Geek”,但胜在稳定,我写了一个函数:
func buildVideoFromImages(imageList []string, outputPath string) error {
args := []string{"-y", "-framerate", "30"}
for _, img := range imageList {
args = append(args, "-i", img)
}
// ... 拼接滤镜参数
cmd := exec.Command("ffmpeg", args...)
return cmd.Run()
}
真正复杂的是滤镜链,要让每一张图片显示1秒(30帧),还要做交叉淡入淡出转场,FFmpeg的filter_complex参数简直是个小语言,Go这边生成字符串的时候,拼接错一个冒号就得查半天。
这里有一条血的教训:转场部分的参数测试了12次才满意,第一次整个视频卡成PPT,第二次颜色偏差,第三次声音不同步……写到第五次我都有点想放弃了,但想到这个视频是要送给对象的一周年礼物,又硬着头皮改下去了。
第三步:加文字和音乐——让365天有温度
纯粹的图片滑动太干巴巴了,我想在每张图片右下角加上当天的日期,最好还根据季节变个色调——比如春天的照片偏绿,冬天的偏蓝,这需要逐帧处理,性能压力挺大。
我用了github.com/anthonynsimon/bild库来做图片叠加:
func addDateText(img image.Image, dateStr string) image.Image {
// 用bild绘制文字
textOverlay := bild.New(image.Rect(0, 0, img.Bounds().Dx(), img.Bounds().Dy()))
// ... 处理字体大小和位置
result := blend.Overlay(img, textOverlay)
return result
}
bild对中文支持有点问题,字体渲染出来是方块,我又去下了个思源黑体,设了个环境变量才解决,这个坑真是哭笑不得——代码没问题,是字体没装对。
音乐部分反而简单,直接让FFmpeg混流:
ffmpeg -i video.mp4 -i music.mp3 -c:v copy -c:a aac -shortest output.mp4
选了一首对我们有意义的曲子,音量压到30%,别盖过照片本身的故事感。
代码写完后,我第一次看视频哭了
说真的,当第一版完整的365一周年纪念日视频跑出来,我点开播放,从第一天到最后一天,365张照片一张张闪过——有些照片我都忘了是什么时候拍的,但视频里日期一显示,当时的场景就全回来了。
技术上说,这个视频并不是什么大片级别,像素没优化到4K,转场偶尔有顿挫感,字体位置在竖屏横屏混着的时候也不够统一。但它是自己写的代码跑出来的,每一个参数都是亲手调的,每一帧都是亲眼验证过的。
用Go写视频这件事,技术上其实有很多更好的工具链——Python有MoviePy,JavaScript有FFmpeg.wasm,专业软件直接拖拽就行,但我还是推荐你试试用Go写一次,原因不是性能,而是那种“从零到一”的掌控感。
如果你想复刻,这里有一份清单
| 步骤 | 工具/库 | 备注 |
|---|---|---|
| 图片读取排序 | os.ReadDir, sort.Slice |
文件名最好按日期命名 |
| 图片处理 | bild, golang.org/x/image |
注意字体路径和中文渲染 |
| 视频编码 | os/exec + FFmpeg |
重点在filter_complex写法 |
| 音频混流 | FFmpeg | 注意音频时长对齐 |
| 并发优化 | sync.WaitGroup |
如果图片多,可以分片处理 |
一些让你少走弯路的提醒
- FFmpeg版本别太老:4.0以下不支持某些滤镜,我都用的5.1
- 图片尺寸最好统一:我的照片有横有竖,最后统一缩放到1080×1920,竖屏效果最好
- 内存管理要注意:365张高清图片全部加载到内存,大概占2-3GB,后来我改成流式处理,读一张处理一张,内存降到300MB
这个项目写完后,我把源码和生成的视频一起压缩发给了女朋友,她看完说:“这个礼物比买的有意义太多了。” 其实打动人的从来不是技术本身,而是你愿意花365天记录、再花一整个周末编程,把那些日子重新组织成一种可播放的形态。
Go语言的语法确实不算最亲和的,但它那种“少即是多”的哲学,反而适合这种需要高度定制的个人项目,你不需要框架,不需要依赖注入,不需要容器化——只需要一个编译器,一个FFmpeg,和一堆值得被记住的照片。
视频最后10秒,我放了一张空白的照片,上面只写了一行字:“365天过去了,我们还在。”
代码也许有bug,转场也许不够丝滑,但那行字出现的时候,什么技术缺陷都无所谓了。

发表评论