365天一天一秒视频,用Go语言记录一整年的时光切片
- 健康
- 2026-07-12 21:48:16
- 314
你有没有想过,一年到底有多长?不是365天那个数字,而是那种“嗖一下过去”的感觉,去年春节吃的饺子味道还没散,今年又该包了,我试过用日记、照片、甚至朋友圈来抓时间,但都败给了懒惰。
直到有一天,看到有人用“一天一秒”的视频记录了一年,那种震撼——365秒,刚好6分钟多一点,却能看完整整一年的喜怒哀乐,我想,这玩意儿我能用Go写出来。
为什么非得用Go语言?
别急着学Python或FFmpeg命令行,Go的好处在于:编译快、跨平台、并发强,你要处理365个视频片段,每个1秒,拼接、转码、加字幕——用Go可以写成一个单文件工具,扔给谁都能用。
更关键的是,Go标准库里的os/exec配合FFmpeg,效率比你手动操作高十倍,而且写出来的程序能跑在Windows、macOS、Linux上,不用装Python环境。
视频素材怎么准备?先解决“每一天”的问题
你不可能每天想起拍视频,这很正常,我一开始也这样,后来定了三个原则:
- 固定时刻:比如每天午休后13:00,手机架好拍10秒
- 固定场景:办公桌、窗边、或者镜子前
- 固定动作:挥挥手、喝口水、或者只是眨眨眼
如果某天漏了怎么办?用前后几天的素材补一个模糊的“代表画面”,总比黑屏强。
素材格式尽量统一:1920×1080,30fps,横屏,竖屏也能拼,但上下黑边看着难受,用手机拍的话,建议开4K,最后统一缩放到1080p,画质损失小。
核心代码:用Go调用FFmpeg拼视频
这才是重头戏,我先用Go写一个文件扫描器,把365个视频按日期排好。
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
)
func main() {
// 假设素材都在 ./clips/ 目录下,命名格式:2024-01-01.mp4
dir := "./clips/"
files, _ := filepath.Glob(dir + "*.mp4")
sort.Strings(files) // 按文件名排序就是按日期
// 生成FFmpeg的concat列表
listFile, _ := os.Create("filelist.txt")
defer listFile.Close()
for _, f := range files {
listFile.WriteString(fmt.Sprintf("file '%s'\n", f))
}
// 拼合命令
cmd := exec.Command("ffmpeg",
"-f", "concat",
"-safe", "0",
"-i", "filelist.txt",
"-c", "copy",
"output.mp4")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
你看,核心就这几行,但真实情况比这复杂得多:
- 视频长宽不一:需要先统一缩放
- 码率不同:不转码可能导致音画不同步
- 有黑帧:某天没拍怎么办
所以改进版得加上预处理,我先用Go并发处理每个视频:
func processClip(path string) {
tmpPath := strings.Replace(path, ".mp4", "_tmp.mp4", 1)
cmd := exec.Command("ffmpeg",
"-i", path,
"-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2",
"-t", "1", // 强制1秒
"-y", tmpPath)
cmd.Run()
os.Rename(tmpPath, path)
}
然后用协程池同时处理8个视频:
sem := make(chan struct{}, 8)
for _, f := range files {
sem <- struct{}{}
go func(video string) {
defer func() { <-sem }()
processClip(video)
}(f)
}
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}
这样365个视频,几分钟就能处理完,要是单个跑,得半小时。
加上时间戳字幕:让每一秒有“重量”
光有画面不够,一年后的你,根本记不清哪天是哪天,所以得在画面右下角加上日期。
FFmpeg的drawtext滤镜可以搞定,但Go里得拼接复杂的滤镜链:
func addTimestamp(input, output, date string) {
// date格式: 2024-01-01
filter := fmt.Sprintf(
"drawtext=text='%s':x=w-tw-10:y=h-th-10:fontsize=24:fontcolor=white:shadowcolor=black:shadowx=2:shadowy=2",
date)
cmd := exec.Command("ffmpeg",
"-i", input,
"-vf", filter,
"-codec:a", "copy",
"-y", output)
cmd.Run()
}
注意:每次跑FFmpeg都会重新编码视频,这是最耗时的部分,所以最好在预处理阶段就把时间戳加上,别等最后拼接时再加。
我踩过这个坑:先拼合再统一加字幕,结果FFmpeg报“滤镜图太复杂”,因为365个视频,每个都要单独计算字幕位置,滤镜图会爆炸,所以得逐个加。
音乐和转场:让365秒不枯燥
纯视频拼接会有点干,加一段背景音乐会好很多,选无版权的轻音乐,长度6分钟左右,用Go的amix混合:
cmd := exec.Command("ffmpeg",
"-i", "output.mp4",
"-i", "background.mp3",
"-filter_complex", "[1:a]volume=0.3[a1];[0:a][a1]amix=inputs=2:duration=first",
"-c:v", "copy",
"-y", "final.mp4")
音量调低一点,30% 左右,别盖过原声,如果原视频有声音(比如环境音、说话声),保留一些会更有生活气息。
至于转场,简单点就用交叉淡化,FFmpeg的xfade滤镜能实现,但会增加几秒的总时长,如果你要严格365秒,就别加转场,我试过加0.5秒转场,总时长变成365+364×0.5=547秒,感觉节奏变了。
想了个折衷办法:只在月份切换时加0.5秒闪白,用Go判断文件名中的月份是否变化:
lastMonth := ""
for _, f := range files {
month := strings.Split(filepath.Base(f), "-")[1]
if month != lastMonth && lastMonth != "" {
// 这里插入转场逻辑
}
lastMonth = month
}
性能优化:别让CPU烧了
处理365个视频,CPU能跑满,这时候Go的并发优势就出来了,但有几个坑:
- FFmpeg本身就用多核,再开20个Go协程同时跑,CPU直接100%卡死,建议协程数≤CPU核心数
- 磁盘I/O是瓶颈:尽量用SSD,别用机械硬盘,我试过用HDD,写入速度只有30MB/s,预处理卡了2小时
- 临时文件清理:每个视频都生成临时文件,365个视频就是365个文件,用
os.Remove()及时清理
我写了个进度条,用sync.WaitGroup统计完成数:
var wg sync.WaitGroup
var mu sync.Mutex
completed := 0
for _, f := range files {
wg.Add(1)
go func(v string) {
defer wg.Done()
processClip(v)
mu.Lock()
completed++
fmt.Printf("\r处理进度: %d/%d", completed, len(files))
mu.Unlock()
}(f)
}
wg.Wait()
看着数字从1跳到365,那种成就感真不是盖的。
导出和分发:考虑不同平台
最终视频可能是6分钟,导出时注意:
- 高码率:建议20Mbps,保证细节,365个1秒片段,每个都可能有不同光照,低码率会出现色块
- H.264编码:兼容性最好,H.265虽然体积小一半,但很多播放器不支持
- 音频AAC:256kbps以上
如果要在抖音/小红书发,记得输出竖屏——但这需要重新裁剪,损失两边画面,我选择横屏发B站,加个“这一年的365秒
也可以用Go写一个多尺寸导出功能: 说真的,一开始觉得每天拍1秒没啥,但到第100天回头看,泪崩了。 1月1日,新年第一天,我在阳台拍日出,冻得直哆嗦,2月14日,和老婆吃饭,她给我夹菜,3月15日,加班到深夜,办公室只剩我一个,4月1日,愚人节被整,哭笑不得,5月,爸妈来住了一周,视频里他们笑得很开心,6月,搬家,镜头里全是纸箱,7月,第一次跑10公里,喘成狗,8月,海边,阳光刺眼,9月,项目上线,连续熬了三天,10月,国庆堵在路上,11月,感冒,镜头里我脸色惨白,12月31日,又一年结束,我在同一个阳台拍日落。 365秒,365个瞬间。 拼在一起,就是活生生的一年。 用Go写这个工具,不是为了炫技,而是因为Python打包太大,C++太难写,Go编译出来一个二进制文件,5MB,扔到任何电脑都能跑,配合FFmpeg,就是最实用的视频自动剪辑工具。 很多朋友看完我的视频也想要,我就直接把程序给他们,把素材扔进 如果你也想记录一年,别犹豫,今天就开始,每天花10秒拍一段素材,想想一年后你能看到昨天所有的今天,那个画面,值得。func exportForPlatform(input, output, resolution string) {
var scale string
switch resolution {
case "1080p": scale = "1920:1080"
case "720p": scale = "1280:720"
case "vertical": scale = "1080:1920" // 竖屏
}
cmd := exec.Command("ffmpeg",
"-i", input,
"-vf", fmt.Sprintf("scale=%s", scale),
"-c:a", "copy",
"-y", output)
cmd.Run()
}
真实案例:我那一年的365秒
clips文件夹,双击运行,等几分钟,final.mp4就出来了。就这么简单。