当前位置:首页 > 汽车 > 正文

每天一秒的视频,365天,用Go语言写出你的人生浓缩书

  • 汽车
  • 2026-06-30 20:11:26
  • 305
摘要: 你有没有想过,如果每天只录一秒的视频,365天后,你会得到什么?我算过,是6分5秒,不多不少,刚好一首歌的长度,但这一秒,不是随...

你有没有想过,如果每天只录一秒的视频,365天后,你会得到什么?

我算过,是6分5秒,不多不少,刚好一首歌的长度。

但这一秒,不是随便的一秒,是你人生切片里,最浓墨重彩的那一帧,就像把一整天的喜怒哀乐,蒸馏成一滴精华,再把这365滴精华,串成一条时间的项链。

这事听起来浪漫,做起来,却是个不折不扣的技术活,尤其是当你像我一样,是个程序员,还恰好会用Go语言。

我试过用手机剪,用各种App拼,最后都放弃了,因为我要的不是“能看”,而是自动化、不丢帧、不用手动对齐,我要的是,每天睡前,程序自己帮我记下这1秒,然后一年后,直接给我一个成品。

这东西,用Go写,出奇地合适。

为什么是Go,不是Python?

你可能在想:“录视频这种事,Python不是更常见吗?”

对,但Python是给数据科学家用的,Go是给造桥用的,你要在这座桥上,每天跑365趟,数据不能错,时序不能乱,内存不能崩。

Go的并发模型——goroutine,天然适合处理这种“每天一次、持续一年”的任务,而且它的编译产物是二进制文件,不依赖环境,你拿给不懂编程的朋友,双击就能跑,这不是黑魔法,这是工程上的贴心。

我写的第一版,用的是Python,跑了三个月,有一天发现第73天的视频丢了,原因是某次重装系统,依赖包版本冲突,脚本直接报错,而我压根没注意到。

痛定思痛,我用Go重写了全套。

核心架构:每天一秒,怎么存?

先说整体思路,再说代码,你不必照抄,但核心逻辑你大概率用得上:

目录结构

vault/
├── 2025-01-01.mp4
├── 2025-01-02.mp4
├── ...
└── 2025-12-31.mp4

每天一个1秒的原始视频文件,我这命名规则,用的是ISO 8601标准,按时间排序时非常自然。

元数据记录

光有文件不行,你得知道,这1秒里发生了什么,我用的是一份JSON文件:

{
  "2025-01-01": {
    "file": "vault/2025-01-01.mp4",
    "desc": "早上下雪了,儿子在院子里画了个笑脸"
  },
  "2025-01-02": {
    "file": "vault/2025-01-02.mp4",
    "desc": "加班到11点,路灯下的影子拉得很长"
  }
}

这个文件,是整个项目的“灵魂”,你将来回顾时,只看文件名,根本想不起那天的事,但看一眼描述,所有记忆就全回来了。

核心技术点:用FFmpeg截取1秒

Go本身不能直接操作视频,但可以通过exec包调用FFmpeg,这是最稳的方案,没有之一。

我写了一个函数,来实现“从视频文件里截取第N秒”的功能:

func extractOneSecond(inputFile, outputFile string, second int) error {
    cmd := exec.Command("ffmpeg", 
        "-i", inputFile,
        "-ss", strconv.Itoa(second),
        "-t", "1",
        "-c", "copy",
        outputFile,
    )
    return cmd.Run()
}

参数解释

  • -ss:开始时间(第N秒)
  • -t:持续时长(1秒)
  • -c copy:直接复制编码,不重新编码,速度极快,而且画质无损耗

我一般是在睡前,把白天拍的视频(可能三五分钟)拖进一个“待处理”文件夹,然后运行这个命令,自动截取今天的第1秒。

持续365天,你可能会遇到的实际坑

写代码容易,坚持一年难,这365天里,我踩了不少坑,有些特别值得提前说:

坑1:磁盘空间

1秒的视频,根据分辨率,大概在1-5MB之间,365天,就是365-1825MB,如果你还像我一样,保留了原始视频(以防截错了),那空间会翻几倍。

我的解决方案是:每天处理完,只保留那1秒的视频,原片删除,用个cron job或定时任务,每天凌晨自动清理。

func cleanupOriginalVideos(dir string) {
    files, _ := filepath.Glob(filepath.Join(dir, "raw_*.mp4"))
    for _, f := range files {
        os.Remove(f)
    }
}

坑2:视频文件编码不一致

你手机拍的可能是H.264,相机拍的是H.265,网上下的素材又是其他编码,用-c copy的前提是,所有输入的文件编码一致,否则会导致最终合成的视频无法播放。

我的解决方案是:强制转为H.264,在截取时就用-c:v libx264重新编码,虽然慢几秒,但换来一年的稳定性,值。

cmd := exec.Command("ffmpeg", 
    "-i", inputFile,
    "-ss", strconv.Itoa(second),
    "-t", "1",
    "-c:v", "libx264",
    "-preset", "ultrafast",
    outputFile,
)

坑3:文件命名排序

这可能是最隐蔽的坑,你录的视频,文件名通常是IMG_001.mp4这样的,按字符串排序,IMG_001IMG_010IMG_100,顺序是对的,但如果你是用时间戳命名的(比如1735689600_001.mp4),排序就会出问题。

统一的解决方案:用YYYY-MM-DD格式命名,这样字符串排序和日期排序完全一致。

today := time.Now().Format("2006-01-02")
outputFile := filepath.Join("vault", today + ".mp4")

年终合成:把365个1秒变成6分钟

一年之后,你会有365个1秒视频,下一步,是把它们合成一个完整的视频。

这步其实最简单,因为FFmpeg自带concat功能,但有个前提:所有视频的分辨率、编码、帧率必须完全一致。

如果你按我之前说的,统一用了H.264,那这一步就不会有任何问题。

合成方式分两种:

方法A:直接拼接(适合所有场景)

先生成一个文件列表,然后让FFmpeg按列表顺序拼接:

func generateConcatList(output string) {
    var lines []string
    for i := 1; i <= 365; i++ {
        date := fmt.Sprintf("2025-%02d-%02d", (i-1)/30+1, (i-1)%30+1)
        lines = append(lines, "file 'vault/"+date+".mp4'")
    }
    ioutil.WriteFile("list.txt", []byte(strings.Join(lines, "\n")), 0644)
}

然后运行:

ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4

这个命令会直接把所有视频拼起来,不重新编码,速度极快,画质无损,我自己的2023年年终视频,就是这样合成的,整个过程不到10秒。

方法B:带转场效果(适合有追求的)

直接拼接,视觉上会比较“硬”,如果你希望视频切换时有点转场效果(比如淡入淡出),那就需要重新编码了。

ffmpeg -i output.mp4 -vf "fade=t=in:st=0:d=0.5,fade=t=out:st=10:d=0.5" -c:v libx264 output_fade.mp4

但说实话,对6分钟的短片,我试过加转场,反而觉得破坏了那种“一天跳一天”的抽帧感,后来我就一直用无转场的纯拼接,看着有种很奇妙的节奏感。

进阶玩法:让每一秒“活”起来

纯拼接的视频,看一遍可能就够了,但你可以用Go给每个片段加点“料”,让它变成真正可交互的东西。

玩法1:叠加日期水印

在每1秒视频的左下角,叠加上当天的日期,这样你一眼就知道,这段来自哪一天。

func addDateOverlay(input, output, date string) {
    cmd := exec.Command("ffmpeg",
        "-i", input,
        "-vf", fmt.Sprintf("drawtext=text='%s':x=10:y=10:fontsize=24:fontcolor=white", date),
        "-c:a", "copy",
        output,
    )
    cmd.Run()
}

玩法2:生成HTML时间线

用Go生成一个HTML页面,把365个视频片段做成时间线,点击每个缩略图,就能播放对应的1秒。

func generateTimeline() {
    html := "<html><body><table>"
    for i := 1; i <= 365; i++ {
        date := fmt.Sprintf("2025-%02d-%02d", (i-1)/30+1, (i-1)%30+1)
        html += fmt.Sprintf(`
            <tr>
                <td><strong>%s</strong></td>
                <td><iframe src='%s.mp4'></iframe></td>
            </tr>`, date, date)
    }
    html += "</table></body></html>"
    ioutil.WriteFile("timeline.html", []byte(html), 0644)
}

这个HTML文件,你可以存在本地,也可以挂到NAS上,随时翻看,我去年放了个平板在家里的走廊,循环播放这个时间线,每天走过都能瞥见几天的片段,感觉很棒。

玩法3:统计你的“高光时刻”

用Go把元数据文件里的描述,做个简单的词频统计,看看这一年,哪些词出现得最多。

出现次数
工作 87
家人 64
吃饭 52
熬夜 38
旅行 12

你会很惊讶地发现,原来自己一年里,“工作”说了87次,“家人”只说了64次,这些数字比任何总结都直白。

代码之外的“工程良心”

写到最后,我想说点技术之外的东西。

设计这个系统时,我有几个原则,你可以参考:

  1. 不要依赖云服务,所有视频、代码、元数据,都放在本地,你永远不知道自己上传的视频,三年后会不会因为公司倒闭而被删。

  2. 元数据比视频更宝贵,视频丢了,你还有记忆;但如果你连那1秒里发生了什么都不知道,那视频就只是噪音,描述字段,别偷懒。

  3. 坚持比技术重要,这个项目最难的部分,不是用Go调用FFmpeg,不是用goroutine管理并发,而是365天每晚都能想起来“哦对,今天还没录那1秒”。

我解决这个问题的方法也很“程序员”:在手机设了个每晚10点的闹钟,闹钟名就叫“停下,录1秒”,365天,就算出差、旅游、生病,这个闹钟也雷打不动。

你能得到什么,如果真坚持完一年?

第一遍看,你可能会笑——那些前几天的事,你还记得清清楚楚;

第二遍看,你可能会沉默——中间有几个月的片段,你甚至需要点开描述才能记起那天发生了什么;

第三遍看,你可能会流泪——不是煽情,是当你看到365个1秒快速闪过,你才真正感受到时间的密度,有些日子重如千钧,有些日子轻如鸿毛,但它们都是你的。

这就是为什么我用Go写了这个项目,不是因为它能跑得更快,而是因为它让我能坚持跑完这365天。

生活不止有诗和远方,还有每天那1秒的准时交付。

每天一秒的视频,365天,用Go语言写出你的人生浓缩书