你有没有想过,如果能把和爱人在一起的每一天都录下一段视频,365天后会是什么样子?我去年春节突发奇想,真的和另一半开始了这个“365天在一起”的视频计划,但问题来了——手机里堆积如山的视频片段,怎么整理?怎么剪辑?怎么确保不会丢失?作为一个写过几年Go的程序员,我决定用Go语言写一套小工具来解决这个甜蜜的烦恼。
别担心,我写这篇文章不是为了推销什么高深的技术,就算你只是听说过Go这个名字,也能看懂我接下来要讲的东西,咱们边聊边写,就像我当初边想边写代码一样。
为什么要用Go语言来处理视频?
先说说为什么选Go,市面上有Python、有JavaScript,甚至用命令行脚本也能完成部分工作,但我的亲身经历是:Go语言在处理文件、并发任务和跨平台运行时,简直不要太顺手。
举个例子,当我要把365个视频文件按日期重命名时,Python可能会因为环境依赖问题在另一台电脑上跑不起来,但Go编译成一个可执行文件,扔给女朋友的Windows电脑、我自己的Mac或者家里的Linux服务器,双击就运行,这种“一次编译,到处运行”的特性,对于非程序员来说简直是救星。
Go的并发模型在处理大量视频时优势明显,想象一下,你要同时读取几十个视频的元数据、生成缩略图、检查文件完整性——用Go的goroutine,代码写起来像串行一样清晰,但执行起来却是并发的,我当初写完这个工具后,处理全年视频的速度比用Python快了三倍多。
第一步:建立视频文件管理的基础
最开始,我面对的是手机自动生成的杂乱文件名,像什么“VID_20230101_093012.mp4”、“IMG_20230102_183045.mp4”,有的甚至还带着中文,要把它们整理成“2023-01-01-新年第一天.mp4”这样的格式,首先得读取文件的创建时间。
// 这只是个思路,不用真的运行
import (
"os"
"time"
"path/filepath"
)
func getFileCreateTime(path string) time.Time {
info, _ := os.Stat(path)
// 不同操作系统取时间的方式不同,但Go帮你封装好了
stat := info.Sys()
// 这里省略具体实现,但思路是提取创建时间
return info.ModTime() // 简化版,实际需要考虑各种情况
}
这里犯了个小错误——我只是凭记忆写了这段伪代码,实际写的时候,不同操作系统获取文件创建时间的方式确实不同,Linux下需要用到syscall.Stat_t,而Windows和Mac又有自己的结构体,这让我意识到,写工具不能想当然,得考虑用户用的是啥系统,后来我加了个简单的判断,根据不同操作系统调用不同的底层接口。
不过对于初学者,我建议直接用ModTime(修改时间)代替创建时间,虽然不完美,但对于365天连续拍摄的视频来说,修改时间和创建时间基本一致,这就像写文章一样,先让功能跑起来,再追求完美。
第二步:生成每日视频的缩略图和元数据
整理好文件名只是第一步,365个视频,每个几分钟到十几分钟不等,如果要剪辑成一部纪念短片,总不能一个一个打开看内容吧?这时就需要自动化提取关键信息。
我用Go的os/exec包调用了ffmpeg这个开源工具(感谢FFmpeg团队!),用Go调用外部程序其实很简单:
package main
import (
"fmt"
"os/exec"
"strings"
)
func extractFrame(videoPath string, timePoint string, outputPath string) error {
cmd := exec.Command("ffmpeg",
"-i", videoPath,
"-ss", timePoint,
"-vframes", "1",
outputPath,
)
// 运行并捕获错误
if err := cmd.Run(); err != nil {
return fmt.Errorf("提取帧失败: %v", err)
}
return nil
}
这段代码会在视频的第timePoint秒抓取一帧作为缩略图,我设置的是每段视频的第5秒——因为大多数人在开拍时会先对准镜头笑一下,前几秒的画面往往最有纪念意义。
但这里有个实际的问题:ffmpeg需要提前安装在系统上,为了降低使用门槛,我写了个安装检测函数,如果发现没有ffmpeg,就自动从网上下载便携版,说实话,这个功能花了我整整两天调试,因为要处理不同操作系统下的权限问题。但看到女朋友在Windows电脑上双击运行成功的那一刻,我觉得值了。
生成的缩略图会按照日期命名,存放在thumbnails文件夹里,这样,365个视频一目了然——今天去看了樱花,明天在家腻歪,后天吵了架(缩略图里俩人都板着脸),翻起来特别有感触。
第三步:统计视频时长和质量检查
你有没有遇到过这种情况:以为录了完整的视频,结果发现只有3秒,或者画面全是黑的?365天积攒下来的视频,如果中间有几天是损坏的,等到最后剪辑时才后悔就来不及了。

所以我写了一个视频质量检查函数,用Go逐帧读取视频信息:
func checkVideoQuality(videoPath string) (map[string]string, error) {
cmd := exec.Command("ffprobe",
"-v", "error",
"-show_entries", "stream=width,height,duration,codec_name",
"-of", "csv=p=0",
videoPath,
)
output, err := cmd.Output()
if err != nil {
return nil, err
}
// 解析输出
parts := strings.Split(strings.TrimSpace(string(output)), ",")
return map[string]string{
"width": parts[0],
"height": parts[1],
"duration": parts[2],
"codec": parts[3],
}, nil
}
这段代码会返回视频的分辨率、时长和编码格式,如果某天的视频分辨率特别低(比如只有240p,正常都是1080p),或者时长只有1秒,我就会标记出来,提醒重新录制或找备份。
实际使用中,我遇到过一个挺有趣的事,有段视频的编码格式是hevc(高效视频编码),而我的剪辑软件不支持这种格式,幸亏Go程序提前检测出来,我赶紧用ffmpeg转成了通用的h264。要是等到最后才发现,那才叫欲哭无泪。
我还建了一个简单的表格,记录每天视频的状态,这个表格是用Markdown生成的,但为了更直观,我把它转成了HTML:
| 日期 | 时长 | 分辨率 | 状态 |
|---|---|---|---|
| 2023-01-01 | 00:02:35 | 1920x1080 | 正常 |
| 2023-01-02 | 00:01:12 | 1280x720 | 分辨率较低 |
| 2023-01-03 | 00:00:00 | 0x0 | 损坏 |
看到表格里的损坏记录了吧?2023年1月3日的视频不知怎么坏了,幸好Go程序发现了,我赶紧从手机云备份里找回了原始文件,如果不检查,这个数据缺口可能永远无法弥补。
第四步:按主题生成视频合集
有了365个整洁的视频文件,接下来就是剪辑成有意义的合集,一起去过的城市”、“争吵与和好”、“日常小确幸”等主题。
这个功能我用了Go来生成剪辑脚本,就是生成一个ffmpeg的concat命令,把所有相关视频连接起来:
func generateConcatScript(videoPaths []string, outputPath string) error {
// 先创建一个临时文件列表
listFile := "files.txt"
var content strings.Builder
for _, path := range videoPaths {
content.WriteString(fmt.Sprintf("file '%s'\n", path))
}
// 写入文件列表
if err := os.WriteFile(listFile, []byte(content.String()), 0644); err != nil {
return err
}
// 执行ffmpeg拼接
cmd := exec.Command("ffmpeg",
"-f", "concat",
"-safe", "0",
"-i", listFile,
"-c", "copy", // 直接复制流,不重新编码,速度超快
outputPath,
)
return cmd.Run()
}
这里用到了-c copy参数,意思是直接复制视频流而不重新编码,这样做的好处是:365个视频拼接只需要几秒钟,而不是让你干等几个小时。
如果你想要添加转场效果、背景音乐或者字幕,那就需要更专业的剪辑软件了,我的Go程序只是负责把原始片段按顺序拼好,后续的精加工还是得靠人,但这已经帮我省了80%的重复劳动——手动拖拽365个文件到时间轴?想想就可怕。
第五步:归档与异地备份
费了好大劲整理好的视频,如果硬盘坏了或者手机丢了,一切归零,所以安全备份是绝对必要的。
我写了个自动备份函数,把视频复制到外置硬盘和阿里云OSS(对象存储服务),用Go实现多线程上传很香:
func backupToCloud(localPath string, remotePath string) {
// 用goroutine并发上传
go func() {
// 这里调用阿里云SDK上传
// 省略具体代码,但本质上是HTTP PUT请求
}()
go func() {
// 同时复制到本地外置硬盘
// cp命令或io.Copy
}()
}
但说实话,云备份这部分我做得不够好,因为要处理大文件的分片上传、断点续传和权限认证,而且阿里云的认证信息不能硬编码在程序里,我最终选择用环境变量来保存密钥,并在程序启动时检查。如果你只是为自己写工具,可以简单点,把密钥写在配置文件中,但记得别上传到GitHub。
说到备份,我突然想到一个教训,有一次我误删了2月份的全部视频,幸好外置硬盘里有备份,但我女朋友的外置硬盘是NTFS格式的,在Mac上只能读不能写,Go的os包在写入这个磁盘时报了权限错误,我没有处理这个错误,导致备份失败,后来加了个错误处理,自动把备份路径改成用户的家目录,所以啊,写Go代码时一定要处理好错误,特别是涉及文件操作的时候,用户的环境千差万别。
“365天在一起”项目给我的感悟
这个项目前前后后写了大概两周,都是下班后和周末弄的,代码不到1000行,但解决了我们的大问题,女朋友现在每天睡前会录一小段视频,然后拖进程序里,程序自动重命名、生成缩略图、检查质量,还会弹出一个进度条,显示“今天是第157天”。
说真的,技术只是工具,真正有价值的是这些视频背后的情感,有一天我回看1月份的视频,发现那时候我们刚买了新相机,画质一般,构图也歪,但随着时间推移,到12月份的视频已经拍得很稳甚至加了点小创意,这365天不只记录了我们的样子,还记录了我们如何一起学会记录生活。
用Go写这个工具是临时起意,现在看来选对了,它的编译产物就一个文件,女朋友的Windows电脑上直接双击运行。要是我用Python写,还得帮她装解释器和各种库,光解释怎么装pip就能吵一架。
如果你也想做类似的事情,我建议从最简单的一步开始:先把文件整理好,然后慢慢添加功能——质量检查、缩略图、自动拼接,别一口气想做成完美产品,编程和恋爱一样,都是从一点点小事开始的。
对了,最后补一句:这段代码是基于Go 1.21写的,用到了标准库和少量的第三方库(主要是阿里云OSS SDK),如果你要克隆这个思路,记得更新到最新版本的依赖。 这是我在写代码时踩到的坑,现在共享给你。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.uss1.cn/fnagchan/1771.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Go语言记录365天在一起的视频,从零开始构建你的爱情时光机》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:你有没有想过,如果能把和爱人在一起的每一天都录下一段视频,365天后会是什么样子?我去年春节突发奇想,真的和另一半开始了这个“365天在...