凌晨两点十七分,我家闺女终于睡了,我盯着电脑屏幕上那堆按日期命名的文件夹——从“20240215_出生第一天”到“20250214_一周岁”,整整366个文件,加起来87个G,手机相册里还有两千多张照片没整理。
这活儿,用剪映手动拖拽得干三天,但我是一个写Go的程序员啊,咱得用代码解决问题。
为什么非得用Go?Python它不香吗?
坦白说,一开始我想用Python,但转念一想,视频处理要调FFmpeg,文件遍历要并发,最后还得生成一个HTML5时间轴预览——Go的goroutine处理起视频片段并行读取,简直不要太顺手,而且编译出来就是个单个可执行文件,放老婆电脑上不用装环境。
核心思路是这样的:
- 按日期扫描所有视频文件
- 用FFmpeg的
select过滤器提取每天最精彩的3秒 - 按时间顺序拼接,加上日期水印
- 生成一个XML时间轴,方便后期微调
第一步:先把366天的素材“喂”给程序
我的目录结构长这样:
/Volumes/宝宝成长/
├── 2024-02-15_出生/
│ ├── 0001.mp4
│ └── 0002.mov
├── 2024-02-16_第二天/
│ └── 0010.mp4
└── ...
Go的标准库path/filepath配合os.ReadDir,递归扫描所有子目录,这里有个小坑:手机拍的MOV和相机拍的MP4编码不一样,得用filepath.Ext判断扩展名,然后让FFmpeg自动探测。
func scanVideos(root string) []string {
var files []string
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil { return err }
ext := strings.ToLower(filepath.Ext(path))
if ext == ".mp4" || ext == ".mov" || ext == ".avi" {
files = append(files, path)
}
return nil
})
return files
}
第二步:每天挑3秒,用FFmpeg的“蒙太奇”手法
这是个艺术决策——每天的视频可能有几十条,哭的、笑的、爬的、吃的,全放进去肯定不行,但只取一条又太单调,我用了FFmpeg的thumbnail过滤器,它能在视频里自动找到信息量最大的帧,然后我以这个帧为中心,前后各取1.5秒。

命令行长这样:
ffmpeg -i input.mp4 -vf "thumbnail=50,trim=duration=3" -q:v 3 output.mp4
但在Go里,我得用os/exec来调用,还得处理各种引号转义,这时候就体现Go的优势了:exec.Command可以传参数切片,不用手动拼接字符串,避免了shell注入的麻烦。
cmd := exec.Command("ffmpeg", "-i", input, "-vf", "thumbnail=50,trim=duration=3", "-q:v", "3", output)
err := cmd.Run()
第三步:并发处理366个片段,别让你的CPU闲着
单个视频转码也就几秒钟,但366个串行得跑一个小时,Go的sync.WaitGroup加上errgroup(来自golang.org/x/sync)就能轻松实现并发控制。
我用semaphore限制同时跑6个FFmpeg进程,不然内存会爆,每个goroutine处理完就向channel发送结果,主协程收集。
这里有个真实经历:第一次跑的时候没限流,电脑直接卡死,风扇狂转,老婆以为我在挖矿。教训:并发虽好,但要节制。
第四步:拼接!但用concat协议还是filter_complex?
366个3秒的视频合起来是18分钟,太长了,我的目标是一周岁生日当天放的5分钟精华版,所以先得筛选——每天挑出最“有故事感”的片段,比如第一次翻身、第一次叫妈妈、第一次吃辅食。
筛选逻辑我写了个简单规则:
- 如果视频里检测到人脸(用的
ffprobe提取人脸框),优先保留 - 否则按文件大小排序,取最大的(一般大文件画质好)
- 如果当天视频超过10条,随机抽3条
拼接我试了两种方式。concat协议最简单,但要求所有视频编码参数一致——可我的素材有H.264、H.265、甚至还有手机录的VP9,这时候就得用filter_complex,让FFmpeg统一转码再拼接。
filter := fmt.Sprintf("[0:v]scale=1920:1080,setsar=1[v0];[1:v]scale=1920:1080,setsar=1[v1];...")
cmd := exec.Command("ffmpeg", "-i", "concat:part1.mp4|part2.mp4", "-filter_complex", filter, "output.mp4")
第五步:加上日期水印和片头片尾
水印用drawtext滤镜,字体选了个免费的中文字体NotoSansSC-Regular.otf,片头我用Go的image库动态生成了一张带有“小柚子一周岁”字样的PNG,然后让FFmpeg把它作为输入流叠加。
片尾本来想放个小彩蛋——把宝宝365天每天的照片做成快速闪回,但发现用Go生成视频帧开销太大,最后还是让FFmpeg的zoompan滤镜搞定缩放效果。
代码之外:一些真实的“屎山”时刻
说实话,写这个工具花了两个周末,中间崩溃过几次:
- 有一次发现所有输出视频都是绿屏——后来查了查是色彩空间没设置,加了个
-colorspace bt709解决 - 还有一次拼接出来的视频音画不同步,因为有些手机视频有B帧,而concat协议要求严格顺序
- 最惨的一次是生成到第300天的时候,硬盘满了,整个目录写串了,还好我用的是临时文件,最后统一rename
最后的最后:这工具值不值?
现在每次到宝宝生日,我只需要改几个路径,重新跑一遍程序,就能得到当年的回忆视频。用Go写一次性脚本,最大的好处是性能优势和类型安全——虽然FFmpeg才是真正干活的,但Go让我能管理这一堆杂乱的文件而不出错。
上周六我把成片放给全家人看,丈母娘看哭了,我老婆说这是最好的生日礼物,我闺女盯着屏幕里的自己,伸手想去摸,然后回头看看我,笑了。
值。用代码记录成长,比用记忆靠谱多了。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/jiankang/2572.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《一周岁365天视频剪辑,我用Go语言写了个工具,把366个日夜装进5分钟》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:凌晨两点十七分,我家闺女终于睡了,我盯着电脑屏幕上那堆按日期命名的文件夹——从“20240215_出生第一天”到“20250214_一周...