去年这时候,我正蹲在电脑前,对着一个20秒的视频片段发愣,女朋友生日快到了,我想送个特别的礼物,最后咬牙决定:用Go语言,每天拍一段视频,连续365天,最后自动合成一个求婚视频,说出来你可能不信,这个想法差点让我疯掉——但最终它成了我写过最浪漫的代码。
为什么非要用Go语言做这件事?
你可能想问:视频剪辑软件不香吗?Premiere不香吗?香,但我想让这个过程自动化、可复现、甚至有点程序员的浪漫,我需要的功能其实很简单:
- 每天拍摄一小段(手机拍就行)
- 自动标记时间戳
- 按天归档整理
- 365天后合成一部连续视频
- 最后配上音乐+文字求婚
用Python?脚本是方便,但Go的并发处理和跨平台编译能力太适合这种长期运行不崩的场景了,你不想拍到第300天时程序突然挂掉吧?
第一阶段:从“瞎拍”到“结构化”
刚开始我天真地想:随便丢个目录就行,结果第三天就乱了——有的视频叫早上好.mp4,有的叫片段2.mov,我用Go写了个目录扫描器:
// 这是第一个版本,很糙但够用
func scanVideos(root string) []string {
var files []string
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(info.Name(), ".mp4") || strings.HasSuffix(info.Name(), ".mov") {
files = append(files, path)
}
return nil
})
return files
}
然后定义了一个视频元数据结构体:
type DailyVideo struct {
Path string
Date time.Time
Duration float64 // 秒
Tag string // “日常”“风景”“自拍”
}
第二周我学会了用FFmpeg提取视频元数据,Go里的os/exec包跑FFmpeg命令,拿到时长、分辨率,这时候你可能会问:为什么不直接用库?因为FFmpeg最稳。长期项目里,稳比酷重要。
第二阶段:视频命名和归档的噩梦
365个视频,文件名必须独一无二,我定了规则:YYYY-MM-DD_序号_标签.mp4,Go的时间格式化是2006-01-02(别问我为什么,Go的时间是参照这个时间创建的),我写了个辅助函数:
func generateFileName(date time.Time, seq int, tags []string) string {
tag := strings.Join(tags, "_")
return fmt.Sprintf("%s_%03d_%s.mp4", date.Format("2006-01-02"), seq, tag)
}
但问题来了:用户可能一天拍了3段,或者某天忘了拍,我加了个校验逻辑:如果某天没文件,就往日志里写WARNING:DAY 187 MISSING,你猜怎么着?第187天我真忘了——那天加班到凌晨2点。

第三阶段:合成视频——最痛苦的环节
到360天时,我开始合成长达15秒的最终版,核心任务是把365个片段按时间戳拼接,每段取前20秒,这里涉及到:
- 视频转码统一(有的4K有的1080p,必须统一
- 时间戳嵌入(每段上方显示“第X天”)
- 背景音乐混音(把求婚时的BGM铺上去)
Go里调用FFmpeg的命令大概长这样(简化版):
func concatVideos(videos []string, output string) error {
// 生成文件列表
listFile := "concat_list.txt"
f, _ := os.Create(listFile)
for _, v := range videos {
f.WriteString(fmt.Sprintf("file '%s'\n", v))
}
f.Close()
cmd := exec.Command("ffmpeg", "-f", "concat", "-safe", "0",
"-i", listFile, "-c", "copy", output)
return cmd.Run()
}
但FFmpeg的concat协议有坑:所有视频必须同编码同分辨率,我补充了个强制转码步骤:
- 用
-vf "scale=1920:1080"统一尺寸 - 用
-c:v libx264统一编码 - 用
-preset slow保证质量
这里特别注意:不要用太高的preset,否则合成一个1小时视频需要跑3天,我试过-preset veryslow,第二天一看才40%,赶紧改成-preset fast,1小时搞定。
第四阶段:加字幕和特效——Go+FFmpeg的组合
既然要浪漫,得有机场,我在最后加了一段文字:“嫁给我好吗?”——用FFmpeg的drawtext滤镜:
func addText(videoPath string, text string, outputPath string) error {
cmd := exec.Command("ffmpeg", "-i", videoPath,
"-vf", fmt.Sprintf("drawtext=text='%s':fontfile=/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc:fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:enable='between(t,60,65)'", text),
"-codec:a", "copy", outputPath)
return cmd.Run()
}
这里有个小陷阱:中文字体路径,在Mac上字体路径和Linux不同,我用了个系统检测函数:
func getFontPath() string {
if runtime.GOOS == "darwin" {
return "/System/Library/Fonts/PingFang.ttc"
}
return "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc"
}
另外就是水印时间戳,我在每段左上角加了个持续3秒的“Day 1”“Day 2”… 用的是FFmpeg的drawtext+trim组合,但注意不要用太多滤镜,否则FFmpeg会卡死。
第五阶段:自动生成“预告片”和缩略图
求婚视频发布前,我想做个10秒预告片,Go里写个随机采样函数,从365天里挑出10个片段,拼成30秒的精彩集锦,代码逻辑简单,但有个坑:片段之间的转场,我用-vf "fade=t=in:st=0:d=0.5,fade=t=out:st=9.5:d=0.5"让每段末尾淡出。
缩略图我用-ss 00:00:02 -i video.mp4 -frames:v 1抓取每段第2秒的帧,然后用ImageMagick拼成海报墙——当然也是Go调的命令。
踩坑日记:你一定会遇到这些问题
| 问题 | 表现 | 解决方法 |
|---|---|---|
| 视频编码不一致 | 合成后花屏 | 强制统一为libx264 |
| 缺少某天视频 | 最终视频跳帧 | 用占位符(黑屏+文字“第X天空缺”) |
| 音频不同步 | 音乐和画面错位 | 增加-async 1 |
| 文件权限 | 编译后找不到FFmpeg | 用exec.LookPath确认路径 |
| 春节视频 | 拍有说有笑的场景 | 手动标记happy标签,排序时优先 |
特别是视频时长不均匀的问题:有的片段3秒(匆匆拍),有的30秒(聊了一天),我加了动态裁剪函数——每段只保留前10秒,如果不足10秒就重复播放(用-stream_loop -1),效果还行,但搞笑的片段重复后反而更魔性了。
运行时的那些“意外惊喜”
第200天时,我女朋友发现我每天对着手机拍,她问:“你干嘛呢?”我说:“写个Go程序。”她信了……第350天她开始怀疑,但没追问。
最惊险的是第364天晚上,硬盘空间只剩500MB——所有视频加起来快60GB,我紧急写了个压缩任务,用Go的compress/gzip压缩旧备份,才撑过去。
第365天凌晨,合成完成,第二天上午,我把U盘插到电视上,播放到第300天时,女朋友哭了,嫁给我好吗?”出来时,她说“好”。这段Go代码是我写过最成功的项目。
如果可以重来,我会改这些
- 一开始就用FFmpeg的concat demuxer,别用concat protocol
- 每天自动备份到云端,用Go的
cloud.google.com/go/storage - 给视频加上GPS坐标,用
github.com/twpayne/go-geom - 提前写好错误处理,比如某个视频损坏时的回退逻辑
但话说回来,不完美才是程序的常态,那天晚上,我坐在沙发上看完完整365段视频,发现有几天的画面明显歪了(因为手抖)。那些歪歪扭扭的片段,反而成了最真实的记忆。
如果你也想用Go搞个类似的长期视频项目,别等代码完美才动手,先拍第1天,剩下的让Go来管。 你可能会发现,比起最终视频,这365天里每天拍15秒的习惯本身,就是最好的礼物。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.uss1.cn/qiche/1878.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Go语言写365天求婚视频,我踩过的坑和学到的经验》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:去年这时候,我正蹲在电脑前,对着一个20秒的视频片段发愣,女朋友生日快到了,我想送个特别的礼物,最后咬牙决定:用Go语言,每天拍一段视频...