每天一秒的视频,365天,用Go语言写出你的人生浓缩书
- 汽车
- 2026-06-30 20:11:26
- 305
你有没有想过,如果每天只录一秒的视频,365天后,你会得到什么?
我算过,是6分5秒,不多不少,刚好一首歌的长度。
但这一秒,不是随便的一秒,是你人生切片里,最浓墨重彩的那一帧,就像把一整天的喜怒哀乐,蒸馏成一滴精华,再把这365滴精华,串成一条时间的项链。
这事听起来浪漫,做起来,却是个不折不扣的技术活,尤其是当你像我一样,是个程序员,还恰好会用Go语言。
我试过用手机剪,用各种App拼,最后都放弃了,因为我要的不是“能看”,而是自动化、不丢帧、不用手动对齐,我要的是,每天睡前,程序自己帮我记下这1秒,然后一年后,直接给我一个成品。
这东西,用Go写,出奇地合适。
为什么是Go,不是Python?
你可能在想:“录视频这种事,Python不是更常见吗?”
对,但Python是给数据科学家用的,Go是给造桥用的,你要在这座桥上,每天跑365趟,数据不能错,时序不能乱,内存不能崩。
Go的并发模型——goroutine,天然适合处理这种“每天一次、持续一年”的任务,而且它的编译产物是二进制文件,不依赖环境,你拿给不懂编程的朋友,双击就能跑,这不是黑魔法,这是工程上的贴心。
我写的第一版,用的是Python,跑了三个月,有一天发现第73天的视频丢了,原因是某次重装系统,依赖包版本冲突,脚本直接报错,而我压根没注意到。
痛定思痛,我用Go重写了全套。
核心架构:每天一秒,怎么存?
先说整体思路,再说代码,你不必照抄,但核心逻辑你大概率用得上:
目录结构
vault/
├── 2025-01-01.mp4
├── 2025-01-02.mp4
├── ...
└── 2025-12-31.mp4
每天一个1秒的原始视频文件,我这命名规则,用的是ISO 8601标准,按时间排序时非常自然。
元数据记录
光有文件不行,你得知道,这1秒里发生了什么,我用的是一份JSON文件:
{
"2025-01-01": {
"file": "vault/2025-01-01.mp4",
"desc": "早上下雪了,儿子在院子里画了个笑脸"
},
"2025-01-02": {
"file": "vault/2025-01-02.mp4",
"desc": "加班到11点,路灯下的影子拉得很长"
}
}
这个文件,是整个项目的“灵魂”,你将来回顾时,只看文件名,根本想不起那天的事,但看一眼描述,所有记忆就全回来了。
核心技术点:用FFmpeg截取1秒
Go本身不能直接操作视频,但可以通过exec包调用FFmpeg,这是最稳的方案,没有之一。
我写了一个函数,来实现“从视频文件里截取第N秒”的功能:
func extractOneSecond(inputFile, outputFile string, second int) error {
cmd := exec.Command("ffmpeg",
"-i", inputFile,
"-ss", strconv.Itoa(second),
"-t", "1",
"-c", "copy",
outputFile,
)
return cmd.Run()
}
参数解释:
-ss:开始时间(第N秒)-t:持续时长(1秒)-c copy:直接复制编码,不重新编码,速度极快,而且画质无损耗
我一般是在睡前,把白天拍的视频(可能三五分钟)拖进一个“待处理”文件夹,然后运行这个命令,自动截取今天的第1秒。
持续365天,你可能会遇到的实际坑
写代码容易,坚持一年难,这365天里,我踩了不少坑,有些特别值得提前说:
坑1:磁盘空间
1秒的视频,根据分辨率,大概在1-5MB之间,365天,就是365-1825MB,如果你还像我一样,保留了原始视频(以防截错了),那空间会翻几倍。
我的解决方案是:每天处理完,只保留那1秒的视频,原片删除,用个cron job或定时任务,每天凌晨自动清理。
func cleanupOriginalVideos(dir string) {
files, _ := filepath.Glob(filepath.Join(dir, "raw_*.mp4"))
for _, f := range files {
os.Remove(f)
}
}
坑2:视频文件编码不一致
你手机拍的可能是H.264,相机拍的是H.265,网上下的素材又是其他编码,用-c copy的前提是,所有输入的文件编码一致,否则会导致最终合成的视频无法播放。
我的解决方案是:强制转为H.264,在截取时就用-c:v libx264重新编码,虽然慢几秒,但换来一年的稳定性,值。
cmd := exec.Command("ffmpeg",
"-i", inputFile,
"-ss", strconv.Itoa(second),
"-t", "1",
"-c:v", "libx264",
"-preset", "ultrafast",
outputFile,
)
坑3:文件命名排序
这可能是最隐蔽的坑,你录的视频,文件名通常是IMG_001.mp4这样的,按字符串排序,IMG_001、IMG_010、IMG_100,顺序是对的,但如果你是用时间戳命名的(比如1735689600_001.mp4),排序就会出问题。
统一的解决方案:用YYYY-MM-DD格式命名,这样字符串排序和日期排序完全一致。
today := time.Now().Format("2006-01-02")
outputFile := filepath.Join("vault", today + ".mp4")
年终合成:把365个1秒变成6分钟
一年之后,你会有365个1秒视频,下一步,是把它们合成一个完整的视频。
这步其实最简单,因为FFmpeg自带concat功能,但有个前提:所有视频的分辨率、编码、帧率必须完全一致。
如果你按我之前说的,统一用了H.264,那这一步就不会有任何问题。
合成方式分两种:
方法A:直接拼接(适合所有场景)
先生成一个文件列表,然后让FFmpeg按列表顺序拼接:
func generateConcatList(output string) {
var lines []string
for i := 1; i <= 365; i++ {
date := fmt.Sprintf("2025-%02d-%02d", (i-1)/30+1, (i-1)%30+1)
lines = append(lines, "file 'vault/"+date+".mp4'")
}
ioutil.WriteFile("list.txt", []byte(strings.Join(lines, "\n")), 0644)
}
然后运行:
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
这个命令会直接把所有视频拼起来,不重新编码,速度极快,画质无损,我自己的2023年年终视频,就是这样合成的,整个过程不到10秒。
方法B:带转场效果(适合有追求的)
直接拼接,视觉上会比较“硬”,如果你希望视频切换时有点转场效果(比如淡入淡出),那就需要重新编码了。
ffmpeg -i output.mp4 -vf "fade=t=in:st=0:d=0.5,fade=t=out:st=10:d=0.5" -c:v libx264 output_fade.mp4
但说实话,对6分钟的短片,我试过加转场,反而觉得破坏了那种“一天跳一天”的抽帧感,后来我就一直用无转场的纯拼接,看着有种很奇妙的节奏感。
进阶玩法:让每一秒“活”起来
纯拼接的视频,看一遍可能就够了,但你可以用Go给每个片段加点“料”,让它变成真正可交互的东西。
玩法1:叠加日期水印
在每1秒视频的左下角,叠加上当天的日期,这样你一眼就知道,这段来自哪一天。
func addDateOverlay(input, output, date string) {
cmd := exec.Command("ffmpeg",
"-i", input,
"-vf", fmt.Sprintf("drawtext=text='%s':x=10:y=10:fontsize=24:fontcolor=white", date),
"-c:a", "copy",
output,
)
cmd.Run()
}
玩法2:生成HTML时间线
用Go生成一个HTML页面,把365个视频片段做成时间线,点击每个缩略图,就能播放对应的1秒。
func generateTimeline() {
html := "<html><body><table>"
for i := 1; i <= 365; i++ {
date := fmt.Sprintf("2025-%02d-%02d", (i-1)/30+1, (i-1)%30+1)
html += fmt.Sprintf(`
<tr>
<td><strong>%s</strong></td>
<td><iframe src='%s.mp4'></iframe></td>
</tr>`, date, date)
}
html += "</table></body></html>"
ioutil.WriteFile("timeline.html", []byte(html), 0644)
}
这个HTML文件,你可以存在本地,也可以挂到NAS上,随时翻看,我去年放了个平板在家里的走廊,循环播放这个时间线,每天走过都能瞥见几天的片段,感觉很棒。
玩法3:统计你的“高光时刻”
用Go把元数据文件里的描述,做个简单的词频统计,看看这一年,哪些词出现得最多。
| 出现次数 | |
|---|---|
| 工作 | 87 |
| 家人 | 64 |
| 吃饭 | 52 |
| 熬夜 | 38 |
| 旅行 | 12 |
你会很惊讶地发现,原来自己一年里,“工作”说了87次,“家人”只说了64次,这些数字比任何总结都直白。
代码之外的“工程良心”
写到最后,我想说点技术之外的东西。
设计这个系统时,我有几个原则,你可以参考:
-
不要依赖云服务,所有视频、代码、元数据,都放在本地,你永远不知道自己上传的视频,三年后会不会因为公司倒闭而被删。
-
元数据比视频更宝贵,视频丢了,你还有记忆;但如果你连那1秒里发生了什么都不知道,那视频就只是噪音,描述字段,别偷懒。
-
坚持比技术重要,这个项目最难的部分,不是用Go调用FFmpeg,不是用goroutine管理并发,而是365天每晚都能想起来“哦对,今天还没录那1秒”。
我解决这个问题的方法也很“程序员”:在手机设了个每晚10点的闹钟,闹钟名就叫“停下,录1秒”,365天,就算出差、旅游、生病,这个闹钟也雷打不动。
你能得到什么,如果真坚持完一年?
第一遍看,你可能会笑——那些前几天的事,你还记得清清楚楚;
第二遍看,你可能会沉默——中间有几个月的片段,你甚至需要点开描述才能记起那天发生了什么;
第三遍看,你可能会流泪——不是煽情,是当你看到365个1秒快速闪过,你才真正感受到时间的密度,有些日子重如千钧,有些日子轻如鸿毛,但它们都是你的。
这就是为什么我用Go写了这个项目,不是因为它能跑得更快,而是因为它让我能坚持跑完这365天。
生活不止有诗和远方,还有每天那1秒的准时交付。

下一篇:印象365,舞蹈视频的魅力与价值