一年有365个舞蹈视频,我用Golang记录每一天的舞动
- 单站
- 2026-07-15 12:20:12
- 235
这事儿得从去年元旦说起,那天我刷到一个博主,说ta坚持每天拍一个跳舞视频,一年下来攒了365个,我当时就想,这不就是我想要的吗?但问题来了——我拍得乱七八糟,存在手机里,文件名都是“视频_20230101_203015”,跟乱码似的,找起来简直要命。
作为一个写代码的,我第一反应是:写个程序来管理这些视频,用什么语言?Python?也行,但我想挑战点新鲜的,就选了Golang,为啥?因为Go编译快、跑起来利索,处理文件操作和并发特别顺手,说干就干,我边学边写,最后还真整出了个能用的工具。
为啥要用Golang来管舞蹈视频
先列几个理由,你感受下:
- 编译成单个二进制文件,丢哪都能跑,不用装环境
- 并发处理,同时分析多个视频的时长、分辨率,不卡壳
- 标准库强大,
os、io、time、encoding/json,基本不用第三方包 - 跨平台,Windows、macOS、Linux通吃
我当时想的是,每天拍一个视频,一年就是365个。如果用手工整理,光改文件名、写标签、记日期,每天至少花10分钟,一年下来就是3650分钟,超过60个小时。有这时间,我都能多练30支舞了。
我写的第一个版本:结构体和文件扫描
先定义数据结构,这是我纠结最久的部分——既要存基本信息,又得能扩展。
type DanceVideo struct {
FileName string // 原始文件名
Renamed string // 重命名后的文件名
Date string // 拍摄日期,格式:2023-01-01
DanceName string // 舞蹈名称
Duration float64 // 时长(秒)
Resolution string // 分辨率,"1920x1080"
Tags []string // 标签,["hip-hop", "编舞"]
Notes string // 备注,状态不错,节奏卡得准”
}
我让程序扫描指定文件夹,把所有视频文件列出来。关键点:通过文件的修改时间或者Exif信息提取拍摄日期,Go里有个os.Stat()能拿到ModTime,再格式化成2006-01-02这个奇怪但固定的格式(Go的时间格式化就是这德行,习惯了就好)。
func scanVideos(dir string) ([]DanceVideo, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
var videos []DanceVideo
for _, entry := range entries {
if isVideoFile(entry.Name()) {
info, _ := entry.Info()
v := DanceVideo{
FileName: entry.Name(),
Date: info.ModTime().Format("2006-01-02"),
}
videos = append(videos, v)
}
}
return videos, nil
}
自动重命名:让文件名有灵魂
一年365个视频,按日期重命名是最基本的,我设的规则是:日期_舞蹈名称_序号.mp4,2023年1月1日拍的waacking,就叫2023-01-01_waacking_001.mp4。
但问题来了——程序怎么知道舞蹈名称? 我一开始想用API识别,后来觉得太麻烦,就偷懒了:每天拍完后,手动输个舞蹈名,或者用命令行参数传进去,我的工具支持两种模式:
- 交互模式:扫描出所有视频,一个一个问你要名字
- 批量模式:提前写好一个csv,程序照着改名
我大部分时间用交互模式,因为每天拍完,脑子里还记得今天跳的啥。直接在终端里敲个名字,几秒钟搞定,这比在手机相册里一个个重命名快太多了。
用并发提速:处理365个视频不卡
最让我得意的改进,是我加了并发处理,Go的goroutine太适合这种场景了——每个视频独立分析时长和分辨率,互不干扰。
func analyzeVideos(videos []DanceVideo) []DanceVideo {
var wg sync.WaitGroup
sem := make(chan struct{}, 5) // 限制最多5个并发
for i := range videos {
wg.Add(1)
go func(v *DanceVideo) {
defer wg.Done()
sem <- struct{}{}
duration := getVideoDuration(v.FileName)
resolution := getVideoResolution(v.FileName)
v.Duration = duration
v.Resolution = resolution
<-sem
}(&videos[i])
}
wg.Wait()
return videos
}
这里用了sem作为信号量,控制同时只跑5个任务,防止CPU爆炸,实测,分析365个视频的总时长从原来串行的15分钟,缩短到不到2分钟。生活就像并发,心里装太多事反而跳不好——我这个程序大概也懂。
生成标签和统计:跳了什么,一目了然
我一开始只存基本信息,后来发现没有标签,找视频还是费劲,比如我想找“跳得特别开心的那些”,按日期翻不如按标签搜快。
所以我加了个标签系统,每个视频可以打多个标签,"hip-hop"、"编舞"、"即兴"、"跳错了但贼开心"(我没开玩笑,这个标签我用了很多次),然后程序能生成一个标签云,统计每个标签出现的次数。
半年下来,我统计了一次:
- hip-hop:出现最多,132次
- 编舞:87次
- 即兴:56次
- 状态不好但硬跳:43次
- 跳错了:28次
- 完美一遍过:才12次(说明我对自己挺严格)
这些数字让我突然意识到:我其实更喜欢自由发挥,即兴的占比不低,而且那些“跳错了但贼开心”的视频,回头看反而笑得最大声。
年度回顾:365个视频,我看见了什么
用这个程序跑完一整年的视频,我生成了一个年度统计报告,用Go写了个简单的HTML生成器,把数据渲染成一个表。
年度舞蹈数据汇总(节选)
| 月份 | 视频数 | 总时长(分钟) | 最常用标签 |
|---|---|---|---|
| 1月 | 31 | 467 | 新年新舞 |
| 2月 | 28 | 422 | 编舞 |
| 3月 | 31 | 480 | hip-hop |
| 4月 | 30 | 455 | 即兴 |
| 5月 | 31 | 478 | 状态不错 |
| 6月 | 30 | 451 | 跳错了 |
| 总计 | 365 | 5489 | 即兴 |
5489分钟,大概91个小时。也就是说,这一年里,我有将近4天的时间,在跳舞,平均每个视频15分钟,最长的一个跳了45分钟(那次状态实在太好,停不下来),最短的2分钟(感冒了跳不动,但不想断掉)。
程序还统计了我的最佳时段:大部分视频在晚上8点到10点之间拍,偶尔早上拍,都是周末,这说明我白天都在工作、写代码,晚上才是自己的时间。数据不会说谎,它只是忠实地记录你。
Golang帮了我什么
我用Go写这个工具,不是为了炫技,是真的需要个趁手的家伙,Go的静态编译让我一键部署——生成的exe文件扔进DanceVideo文件夹,双击就开干。defer和goroutine让代码干净利落,我最喜欢的一点是:Go的error处理逼着你直面问题,比如视频文件损坏时,程序不会静悄悄跳过,而是打印一条警告,让你决定怎么办,这比某些语言默默吞掉异常强多了。
当然也有不爽的地方,Go的ffmpeg绑定库不太好用,我索性直接用exec.Command调用系统ffmpeg,简单粗暴,还有,写GUI太麻烦,我就用终端命令行了,朋友笑我“2023年了还对着黑框框操作”,但我觉得:命令行有命令行的浪漫,当我对着Terminal敲下dance --year 2023 --stats,然后看到一整年的数据铺展开来时,那种掌控感是鼠标点不出来的。
一年是365个舞蹈视频,也是365次按下录制键,写Go让我看到了代码之外的东西——它不只是解决技术问题,更是把你跟生活连接起来的桥梁,我跳的每一段舞,都被记录下来了,不是因为视频在硬盘里,而是因为有个程序,替我记住了每一刻的汗水、笑容、跑调的音乐,和偶尔的没错我就是跳错了但我不在乎。
如果你也在做一件需要坚持一年的事,不管是什么,找个趁手的工具,不一定是Go,可以是任何让你觉得舒服的东西,关键是,不要一个人硬撑着,代码可以帮你扛一部分,剩下的,交给时间。
