用Go语言把一年365天压缩成一段视频,这事儿到底怎么玩?
- 旅游
- 2026-08-13 16:32:10
- 39
你可能刷到过那种“一年365天时间视频”——就是每天拍一秒钟,最后把365秒拼在一起,几分钟看完一整年,我第一次看到这种视频的时候,心里那叫一个痒,心想这玩意儿我也得搞一个,但真上手才发现,每天用手机拍一秒容易,怎么把365个视频文件按顺序、无卡顿地拼成一个完整视频,这事儿没点技术还真办不了。
后来我琢磨着,用Go语言写个小工具来处理这事儿,简直不要太合适,为啥?因为Go处理文件、跑并发、调ffmpeg命令行,那都是老本行,今天我就把这套思路完完整整地写出来,你照着折腾,明年这时候你也有自己的年度时间切片了。
先别急着写代码,你得把素材准备好
打算用Go写,但不是让你从头去实现视频编解码——那玩意水太深,你把握不住,咱的思路是:Go负责“排兵布阵”,ffmpeg负责“干重活”。
你需要准备的东西其实挺简单:
- 一整年每天拍的一秒视频,文件名最好带上日期,
mp4、mp4,这样排序不会乱。 - 电脑上装好ffmpeg,并且能在命令行直接敲
ffmpeg -version出来东西,装的时候记得把bin目录加到环境变量里。 - Go环境,版本别太老,1.20以上就行。
有个小细节我得啰嗦一句:你拍的时候最好横屏固定机位,别手持一顿晃,最后拼出来你会后悔的,每天最好在同一个时间段拍,这样光线变化会特别明显,视频的“时间感”才够强。
Go代码的核心思路:其实就是三步走
很多人在这一步就开始纠结了——是不是得学OpenCV?是不是得搞图像识别?不用不用,咱这活儿用不着那么高大上,我把它拆成了三步:
- 扫描文件夹,把365个mp4文件按日期排好序。
- 统一素材格式,比如把每个视频都缩放成1280x720,帧率统一到30fps,这样拼接的时候不会出幺蛾子。
- 生成ffmpeg的concat列表,让ffmpeg一次性把视频串起来,再配上背景音乐(可选项)。
你看,逻辑清楚了吧?代码写起来其实不怎么长。
第一步:扫描文件,排序一定要稳
这一步的关键是排序,你不能用文件名排序了事,因为有时候手机导出来的文件名可能是 IMG_0001.MP4 这种,看不出来日期,我建议你从手机导出来之后,先重命名成带日期的格式,再丢进一个文件夹里。
Go代码里你用 ioutil.ReadDir 或者 os.ReadDir 读取目录,然后按文件名排序就行,但你要是跟我一样有强迫症,可以解析文件名里的日期部分,转成 time.Time 类型再排序,这样万无一失。
files, _ := filepath.Glob("clips/*.mp4")
sort.Strings(files) // 前提是你文件名前缀是 YYYYMMDD
要是你的文件名格式更复杂,2025-01-01_早上好.mp4,那就得写个简单的解析函数,提取前10个字符转成时间戳排序。
第二步:统一格式,这步偷懒不得
每个视频的编码、分辨率、帧率不一样,直接拼肯定出问题,所以得先过一遍ffmpeg,把每个视频都转成中间格式。
我在Go里用的是 exec.Command,直接调ffmpeg子进程,核心命令长这样:
cmd := exec.Command("ffmpeg", "-i", input,
"-vf", "scale=1280:720,fps=30",
"-c:v", "libx264", "-preset", "veryfast",
"-an", output)
这里我得说说我踩过的坑:-an 一定要加,因为很多手机视频自带录音,但咱们“一年视频”一般不要声音(要的话后面统一加音乐),不加 -an 的话,有些视频音轨是坏掉的,拼到一半直接闪退。
如果你觉得转码365个文件太慢,你可以用Go的goroutine开几个并发,同时转4个或者8个文件,但注意别开太多,CPU会直接拉满,电脑风扇转得跟直升机似的。
sem := make(chan struct{}, 4) // 控制并发数
var wg sync.WaitGroup
for _, f := range files {
wg.Add(1)
go func(f string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
// 转码逻辑
}(f)
}
wg.Wait()
第三步:生成concat列表,一段视频成了
转码完之后,所有视频都在 temp/ 目录下,文件名还是按日期排的,这时候要用ffmpeg的concat协议,但这玩意有个要求:所有视频的编码参数得完全一致,咱们第二步做的工作就是为了这里。
Go生成一个 list.txt格式特别简单:
file 'temp/20250101.mp4'
file 'temp/20250102.mp4'
...
然后一条命令搞定:
cmd := exec.Command("ffmpeg", "-f", "concat", "-safe", "0",
"-i", "list.txt", "-c", "copy", "year_2025.mp4")
这里用 -c copy 是因为转码后的格式已经统一了,直接复制流就行,不重新编码,几秒钟就合成完,要是你想加背景音乐,再用 -i music.mp3 混流进去,加个 -shortest 参数防止音乐比视频长。
进阶玩法:不止是拼接,还能加时间戳和进度条
拼接完就完事了?当然不是,你可以在视频上直接烧录当前日期,ffmpeg的 drawtext 滤镜能搞定,但中文字体路径得指定对。
Go代码里生成滤镜字符串的时候,注意转义特殊字符。
ffmpeg -i year_2025.mp4 -vf "drawtext=text='2025-01-01':fontfile=/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc:fontsize=48:fontcolor=white:x=w-tw-20:y=h-th-20" output.mp4
但问题是,你想让每段的日期都不同,那不能直接用concat了,得用ffmpeg的 filter_complex 做逐段处理,这事儿复杂度上去了,但我跟你说几个思路:
- 每段视频先加好日期水印,再转码,最后concat。
- 或者用Go写个循环,一段一段处理,最后合并。
我个人推荐第一种,因为filter_complex写起来容易乱,调试费时间,咱是过日子的人,怎么简单怎么来。
让生活轻松点:给脚本套个壳
代码写完之后,总不能每次都手动去改文件夹路径吧,你可以用Go的 flag 包写几个参数,-input 指定素材目录,-output 指定输出文件名,-music 指定背景音乐。
inputDir := flag.String("input", "./clips", "素材目录")
outputFile := flag.String("output", "year.mp4", "输出文件名")
musicFile := flag.String("music", "", "背景音乐路径")
flag.Parse()
这样你在电脑上双击个bat或者sh脚本,就能一键生成,甚至你可以用Go的 time.Tick 写个定时器,每天固定时间自动把你今天拍的视频转码重命名,存到对应文件夹里,这样全年都不用管,到年底跑一次就行。
我今年就是这么干的,每天早上出门前对着家门口拍一秒,晚上回来 go run makevideo.go -input ./2025 -output 2025.mp4 -music bgm.mp3 一跑,看着屏幕上进度条蹭蹭走,那感觉,还挺治愈的。
别光看不练,几个小坑我给你提前说
最后唠几个我栽过跟头的地方,你遇上了不至于干瞪眼:
- 文件名千万别有空格,ffmpeg处理起来麻烦,你写Go代码的时候也得小心拼接路径。
- date命令生成的文件名可能是小写,但手机导出的可能是大写,
sort.Strings是按字节排序的,大小写混着排会乱,你统一转成小写再用。 - 如果你用的是Windows系统,
filepath.Glob的路径分隔符是 ,但在list.txt里要写成 ,不然ffmpeg不认。 - 合成长视频的时候,记得看磁盘空间,365段一分钟的视频,转码后可能有几十个G,千万别放C盘。
这套工具我现在还在用,每年年底最期待的就是把一年的切片拼起来,看着屏幕上的自己从穿短袖到裹羽绒服,从阳台的绿植到窗外的大雪,真的有种“时光具象化”的感觉,你别光收藏,赶紧去试试,明年这时候你也能拿自己的视频跟朋友显摆,要是遇到啥奇怪的问题,回头再来读读这篇文章,保不齐你就找到答案了。
