你刷到过那种“365天第三部小视频”的合集吗?别急着关页面,我不是来搞颜色的,去年冬天我窝在出租屋里,盯着硬盘里一堆散乱的家庭录像——我爸拍的菜地,我妈炖的鱼,还有我侄女在客厅里跳《孤勇者》的鬼畜片段,突然就想,要是能把这些碎片按日期串起来,做成一部“365天生活记录”的小视频,那得多带劲。
但问题来了:我手机里存了800多个视频文件,文件名全是IMG_20240315_183245.mp4这种鬼样子,要按日期挑出“第三季”的素材,也就是第365天前后那批,手动找得瞎眼,那天恰好我电脑上装着Go语言环境,就顺手写了段小工具,把这事儿给自动化了,今天就把这活儿拆开揉碎了讲给你听,保证你听完能自己动手做一个。
第一步:Go语言凭什么干这活?
你可能会问,用Python不香吗?我承认,Python写脚本快,但Go编译出来是个单文件二进制,扔到NAS、树莓派甚至老旧的Windows XP上都能跑,不依赖环境变量,而且Go的filepath.Walk遍历目录、time.Parse解析时间戳,处理文件元数据简直是削铁如泥。
我当时的需求特朴素:把“365天第三部小视频”理解成“第365天当天拍摄的第3个视频”,所以核心逻辑是三步走:读文件名里的时间戳→按天分组→每组取第三个。
第二步:先写个能跑通的最小循环
别一上来就整高深的并发通道,先把主流程用fmt.Println怼出来,我当时在main.go里塞了这段:
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"time"
)
func main() {
dir := "/Users/me/Videos/2024"
files, _ := filepath.Glob(filepath.Join(dir, "IMG_*.mp4"))
re := regexp.MustCompile(`IMG_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})`)
dayMap := make(map[string][]string)
for _, f := range files {
m := re.FindStringSubmatch(f)
if m == nil {
continue
}
// 拼成 YYYY-MM-DD 的key
dayKey := fmt.Sprintf("%s-%s-%s", m[1], m[2], m[3])
dayMap[dayKey] = append(dayMap[dayKey], f)
}
// 找出第365天对应的日期
targetDate := time.Date(2024, 12, 30, 0, 0, 0, 0, time.Local) // 假设第365天是12月30日
targetKey := targetDate.Format("2006-01-02")
if list, ok := dayMap[targetKey]; ok {
sort.Strings(list) // 按文件名排序,相当于按时间排序
if len(list) >= 3 {
fmt.Printf("第三部小视频是:%s\n", list[2])
} else {
fmt.Println("那天只拍了不到3个视频")
}
} else {
fmt.Println("第365天没有视频记录")
}
}
你看,这段代码糙得能当砂纸用,但它就是能干活。sort.Strings这步特关键,因为IMG_开头的文件名后缀是时间戳,字典序就等于时间先后序,你拿20241230_183245和20241230_092011比一比,09开头的肯定排在18前面,没毛病。
第三步:用费曼法给自己讲明白“分组”这回事
我在写dayMap的时候,脑子里突然冒出一个画面——就像菜市场大妈分橘子,按大小往不同筐里扔,Go里的map[string][]string就是这么个筐:key是日期字符串,value是那个日期下所有视频文件的路径切片,每遇到一个文件,就查一下它属于哪个“筐”,然后扔进去。
这里有个坑是:time.Parse和time.Format的布局字符串,也就是"2006-01-02"那个魔数,指的是2006年1月2日下午3点4分5秒(Go语言的生日),好多新手第一次写都会写成"YYYY-MM-DD",结果报错报得怀疑人生,你记住,2006这个数字就是Go的基准时间,别问为什么,这是语言设计者的恶趣味。

第四步:处理“365天”到底对应哪天?
你说“365天”,但一年可能闰年,我用了个笨办法:从1月1日数起,数到第365天,在代码里就是:
start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.Local) targetDate := start.AddDate(0, 0, 364) // 365天就是加364天
这样不管是闰年还是平年,targetDate都不会错,我当时犯了个二逼错误:直接写了AddDate(0,0,365),结果跑出来是第二年的1月1日,好悬没把“第三部小视频”定义成新年第一拍。
第五步:把结果做成一个可交互的命令行工具
光打印一行字没意思,我把程序升级成带-date参数的模式,这样你可以指定任意一天找“第三段视频”:
var dateFlag = flag.String("date", "", "目标日期,格式 YYYY-MM-DD")
flag.Parse()
if *dateFlag != "" {
t, err := time.Parse("2006-01-02", *dateFlag)
if err != nil {
fmt.Println("日期格式错了,老铁")
os.Exit(1)
}
targetDate = t
}
然后我把所有包含dayMap的代码抽成一个函数findThirdVideo(dayMap, targetDate),再给输出加个颜色(用\033[32m绿色高亮),那感觉瞬间不一样了,就像从路边摊炒饭升级成了夜市铁板烧。
第六步:真实世界哪有那么听话——处理脏数据
我跑真实数据时遇到了几百个HEIC后缀(iPhone拍照格式)混在视频里,我的正则只匹配.mp4,所以这些文件被静默忽略了,但更恶心的是,有些文件名是20241230_183245.png,还有VID_20241230_183245.mp4这种前缀,于是正则得改:(IMG|VID)_开头,后缀加个|\.mov|\.mp4$。
我还发现有个视频文件的大小是0字节,拍了一半断电了,如果你用os.Stat检查文件大小,小于1KB的直接跳过,省得后面处理时崩溃。
表格铺一下我当时踩过的坑:
| 情况 | 错误表现 | 处理方式 |
|---|---|---|
| 文件名没有下划线 | 正则匹配不到 | 加一个宽松匹配:[\w-]+_\d{8}_\d{6} |
| 同一天拍了超过50个 | 排序后取第三个还是对的 | 不用管,Go排序很快 |
| 视频文件损坏 | 用ffprobe读时长会卡死 | 先用os.Stat过滤大小<1024字节的文件 |
| 日期跨年 | AddDate处理错了 |
统一用基准日加天数,避免手动改年份 |
第七步:让程序自己“说话”——日志和进度条
我加了log包,每处理完100个文件就打印一条进度:
count := 0
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
count++
if count%100 == 0 {
log.Printf("已扫描 %d 个文件...\n", count)
}
// 处理...
})
这感觉就像你数着一叠钞票,数几百张心里踏实,程序跑完会在终端显示:
已扫描 100 个文件...
已扫描 200 个文件...
第365天第三部小视频是:/Users/me/Videos/2024/IMG_20241230_183245.mp4
用时:0.423秒
那0.4秒的耗时让我有点意外,Go遍历文件系统真不是盖的,对比我之前用Python写的版本,跑了3秒多,还经常爆内存——因为Python用list存全路径,再加上正则回溯,几百MB就卡了。
第八步:越用越顺手——批量场景
后来我不只找“第三部小视频”,还给嫂子拷一份“年度精选”,思路简单:遍历所有dayMap,每个日期取最后拍摄的那个视频(当天的“每日一影”),输出到bestof.txt,然后写个rsync -av --files-from=bestof.txt . /mnt/usb/,一键备份。
这会你会发现,Go的map是无序的,所以最好把日期key排个序,免得备份时顺序乱:
keys := make([]string, 0, len(dayMap))
for k := range dayMap {
keys = append(keys, k)
}
sort.Strings(keys)
给你留个吊胃口的事
现在这个工具我还在用,每周末跑一遍,把一周的“每日一影”合并成个5分钟的小电影,用ffmpeg拼接,Go代码里直接调用exec.Command("ffmpeg", "-f", "concat", ...),挺野的路子,但它就是跑得稳。
你不妨也试试:先拿手机里一个月的视频跑跑看,说不定翻出某天傍晚你在阳台上拍的那朵云,配着第三部小视频的标题,突然就想起那天为什么停机了一会儿。代码这玩意儿,有时候比相册还懂得怎么帮你留日子。
哦对了,有个坑提前提一下:filepath.Glob不会递归子目录,我视频文件都在同一层所以没事,你要是分文件夹存的,得换filepath.Walk,那又得跟权限问题打交道了,先去试试,遇到问题再回来翻翻这篇文章,咱们评论区见。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://hljbesthome.com/jiankang/2402.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Go语言写个365天第三部小视频采集器?这事儿我真干过》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:你刷到过那种“365天第三部小视频”的合集吗?别急着关页面,我不是来搞颜色的,去年冬天我窝在出租屋里,盯着硬盘里一堆散乱的家庭录像——我...