说实话,最开始我想做这个“365天的四季视频”的时候,压根没想过会用到Go语言,我就是想在自己阳台的同一个位置,每天固定时间拍一张照片,坚持一整年,最后把365张照片拼成一个几十秒的视频——让你一眼看到窗外的树从枯枝变绿,再变黄,最后落光。
但真干起来才发现:一年365张照片,手动处理会死人的,每张都要命名、排序、检查曝光、剔除废片、再按时间顺序合成视频,第三周我就漏了两天的照片,文件名还写错了,这时候,我脑子里突然蹦出一个想法:写个Go程序来帮我管理这一切。
为什么是Go?不是Python?
我知道很多人会用Python干这事,但我选Go,原因特别简单:Go编译出来就是一个独立的可执行文件,扔到哪都能跑,我不用装Python环境,不用pip安装OpenCV或者PIL,更不用操心版本冲突,尤其当我打算把整个项目分享给朋友时,Go的“单文件交付”简直救命。
再说Go的并发模型,处理365天照片这件事,天然适合并发——我不需要等读完所有照片再处理,完全可以一边读照片、一边调整色调、一边生成视频帧,用Go的goroutine,写起来就像:“哎,这批照片好了,那批你也开始干吧。”自然又直接。
第一步:先给照片“排好队”
我先假设照片的命名规则是 YYYYMMDD_HHMMSS.jpg,这样排序就简单了:
package main
import (
"fmt"
"os"
"path/filepath"
"sort"
)
func main() {
dir := "./photos"
files, err := os.ReadDir(dir)
if err != nil {
fmt.Println("读目录失败:", err)
return
}
var names []string
for _, f := range files {
if !f.IsDir() && filepath.Ext(f.Name()) == ".jpg" {
names = append(names, f.Name())
}
}
sort.Strings(names)
fmt.Printf("一共找到了 %d 张照片\n", len(names))
// 按排序后的顺序处理
}
这代码很简单,但我当时写的时候出了个低级错误:忘了过滤隐藏文件,结果程序把 .DS_Store 也算进去了,排序出来365张里面混着一张系统文件。真实世界就是这样,总有些小问题等着你。
第二步:用FFmpeg合成视频——直接调命令
合成视频的部分,我最初想用Go的纯库去写帧,后来想通了:FFmpeg已经是行业标准了,我干嘛要重新发明轮子? 直接用 os/exec 调用它就行。

这里有个小坑:照片数量多了以后,FFmpeg对输入文件名的顺序特别敏感,如果文件名乱序,视频里四季就会乱跳——春天突然闪过夏天,又回到冬天,像被剪辑师随机洗过牌,所以前面排序那一步,是根基。
我用的合成命令大概是这个样子:
cmd := exec.Command("ffmpeg",
"-framerate", "30",
"-pattern_type", "glob",
"-i", "./sorted/*.jpg",
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"year_in_365_seconds.mp4")
-framerate 30 的意思是每秒30张照片,365张照片大约12秒,但后来我发现12秒太短了,观众什么都看不清,改成 -framerate 8 ——每秒8张,视频变成45秒左右。这个调整我反复试了7遍,才找到“既能看清季节变化,又不至于拖沓”的感觉。
第三步:给每个季节“上色”——Go里的图像处理
这个想法是我半夜突发奇想来的:我可以给不同季节的照片加不同的色调,春天偏绿,夏天偏暖黄,秋天偏橙红,冬天偏冷蓝。让视频不只是时间流逝,还有情绪的温度。
我用Go标准库 image 和 image/color 手动写了个色调调整函数,原理特别土:遍历每个像素,把RGB值往某个方向“拉”一点。
func adjustSeasonColor(img image.Image, season string) image.Image {
bounds := img.Bounds()
result := image.NewRGBA(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, a := img.At(x, y).RGBA()
// 偷懒简写:实际用uint8转换
switch season {
case "spring":
g = uint32(float64(g) * 1.15) // 绿增加
case "summer":
r = uint32(float64(r) * 1.12) // 暖黄增加
case "autumn":
r = uint32(float64(r) * 1.25)
g = uint32(float64(g) * 0.85)
case "winter":
b = uint32(float64(b) * 1.20) // 蓝增加
}
result.Set(x, y, color.RGBA{uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8)})
}
}
return result
}
这段代码在性能上很烂——逐像素遍历,对于高清照片来说,一次处理就要一两秒,但如果只是365张,每个季节90张左右,还是能忍受的,我更在乎的是效果,第一版跑出来,我差点笑出声:夏天的照片被调得太黄了,像被泡在咖喱里,后来把系数拉低到1.06,才算正常。
第四步:自动识别季节——靠月份硬分
自动判断季节的逻辑我写得很“蛮力”:
| 月份 | 季节 |
|---|---|
| 3-5月 | 春季 |
| 6-8月 | 夏季 |
| 9-11月 | 秋季 |
| 12-2月 | 冬季 |
从文件名里提取月份:20250315_120000.jpg 的第三四位是 03,那就是三月,春天。
func getSeason(filename string) string {
month := filename[4:6]
switch month {
case "03", "04", "05":
return "spring"
case "06", "07", "08":
return "summer"
case "09", "10", "11":
return "autumn"
default:
return "winter"
}
}
这里我故意写了一个看起来很对的版本,实际上有bug:照片文件名年份可能是2024或2025,不同年份3月到5月的天数不一样,但我用一年数据,默认365天,直接按月份分了,这种方法简单粗暴,够用了。
第五步:光有视频还不够——加上“时间戳字幕”
我看了第一版视频之后,觉得少了点什么,画面在动,但观众根本不知道现在是几月份,所以我又加了一个功能:每张照片的右上角嵌入拍摄日期。
Go标准库 golang.org/x/image/font 可以用,但我嫌麻烦,直接用FFmpeg的 drawtext filter来做,因为Go程序已经能控制FFmpeg了,我就在生成命令时,动态传入日期字符串。
drawText := fmt.Sprintf("drawtext=text='%s':x=10:y=10:fontsize=24:fontcolor=white:box=1:boxcolor=black@0.5",
dateStr)
cmd := exec.Command("ffmpeg",
"-i", "framebuffer_%d.png",
"-vf", drawText,
"final_with_dates.mp4")
字幕一加上去,视频立刻就“活了”,你看着日期从1月1号跳到1月2号,再跳到1月3号……那种“一天天走过去”的感觉,比单纯看画面更有沉浸感。我甚至把最后一帧的日期加了个抖动特效——纯属觉得好玩。
第六步:处理“翻车”数据——每个项目都有
365天,我实际拍了358张,有7天因为出差、生病、相机没电漏掉了,面对缺失的照片,Go程序帮我做了两件事:
- 检测缺失:程序检查文件序号连续性,列出了一个缺失日期清单。
- 补帧:对缺失的部分,我用前后两天的照片做了简单的线性混合,生成一张过渡帧,效果不算完美,但至少视频不会突然闪一下。
混合代码很简单:取前两天像素值的平均值。
func blend(img1, img2 image.Image) image.Image {
bounds := img1.Bounds()
out := image.NewRGBA(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r1, g1, b1, _ := img1.At(x, y).RGBA()
r2, g2, b2, _ := img2.At(x, y).RGBA()
out.Set(x, y, color.RGBA{
uint8((r1+r2)>>9),
uint8((g1+g2)>>9),
uint8((b1+b2)>>9),
255,
})
}
}
return out
}
这函数性能极差,处理一张1920x1080的照片花了0.8秒,但一共就7张,加上我只在半夜跑一次,完全没问题。
我得到了什么?
最终生成的视频有52秒,358张真实照片+7张合成帧,按日期排列,从第一帧的枯枝积雪,慢慢过渡到嫩芽、繁花、烈日、落叶、霜冻,再回到雪景,看着它,我一句话都说不出来。
有时候技术不是为了高效,而是为了留住那些你本可能遗忘的瞬间,而Go语言,恰好帮我完成了这件事——不花哨,不复杂,但足够可靠。
哦对了,我还写了个小功能,可以随机在视频里插入一帧“彩蛋”:比如在我生日那天,照片上多画个生日帽,但这功能我还没完全写利索,有时候帽子画歪了,先这样吧。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://hljbesthome.com/keji/1792.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Go语言给365天的四季视频写个时间压缩器—我边想边写的真实记录》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:说实话,最开始我想做这个“365天的四季视频”的时候,压根没想过会用到Go语言,我就是想在自己阳台的同一个位置,每天固定时间拍一张照片,...