当前位置:首页 > 健康 > 正文

365天一天一秒视频,用Go语言记录一整年的时光切片

  • 健康
  • 2026-07-12 21:48:16
  • 314
摘要: 你有没有想过,一年到底有多长?不是365天那个数字,而是那种“嗖一下过去”的感觉,去年春节吃的饺子味道还没散,今年又该包了,我试...

你有没有想过,一年到底有多长?不是365天那个数字,而是那种“一下过去”的感觉,去年春节吃的饺子味道还没散,今年又该包了,我试过用日记、照片、甚至朋友圈来抓时间,但都败给了懒惰。

直到有一天,看到有人用“一天一秒”的视频记录了一年,那种震撼——365秒,刚好6分钟多一点,却能看完整整一年的喜怒哀乐,我想,这玩意儿我能用Go写出来。

为什么非得用Go语言?

别急着学Python或FFmpeg命令行,Go的好处在于:编译快、跨平台、并发强,你要处理365个视频片段,每个1秒,拼接、转码、加字幕——用Go可以写成一个单文件工具,扔给谁都能用。

更关键的是,Go标准库里的os/exec配合FFmpeg,效率比你手动操作高十倍,而且写出来的程序能跑在Windows、macOS、Linux上,不用装Python环境

视频素材怎么准备?先解决“每一天”的问题

你不可能每天想起拍视频,这很正常,我一开始也这样,后来定了三个原则

  • 固定时刻:比如每天午休后13:00,手机架好拍10秒
  • 固定场景:办公桌、窗边、或者镜子前
  • 固定动作:挥挥手、喝口水、或者只是眨眨眼

如果某天漏了怎么办?用前后几天的素材补一个模糊的“代表画面”,总比黑屏强。

素材格式尽量统一:1920×1080,30fps,横屏,竖屏也能拼,但上下黑边看着难受,用手机拍的话,建议开4K,最后统一缩放到1080p,画质损失小。

核心代码:用Go调用FFmpeg拼视频

这才是重头戏,我先用Go写一个文件扫描器,把365个视频按日期排好。

package main
import (
    "fmt"
    "os"
    "os/exec"
    "path/filepath"
    "sort"
    "strings"
)
func main() {
    // 假设素材都在 ./clips/ 目录下,命名格式:2024-01-01.mp4
    dir := "./clips/"
    files, _ := filepath.Glob(dir + "*.mp4")
    sort.Strings(files) // 按文件名排序就是按日期
    // 生成FFmpeg的concat列表
    listFile, _ := os.Create("filelist.txt")
    defer listFile.Close()
    for _, f := range files {
        listFile.WriteString(fmt.Sprintf("file '%s'\n", f))
    }
    // 拼合命令
    cmd := exec.Command("ffmpeg",
        "-f", "concat",
        "-safe", "0",
        "-i", "filelist.txt",
        "-c", "copy",
        "output.mp4")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
}

你看,核心就这几行,但真实情况比这复杂得多

  1. 视频长宽不一:需要先统一缩放
  2. 码率不同:不转码可能导致音画不同步
  3. 有黑帧:某天没拍怎么办

所以改进版得加上预处理,我先用Go并发处理每个视频:

func processClip(path string) {
    tmpPath := strings.Replace(path, ".mp4", "_tmp.mp4", 1)
    cmd := exec.Command("ffmpeg",
        "-i", path,
        "-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2",
        "-t", "1", // 强制1秒
        "-y", tmpPath)
    cmd.Run()
    os.Rename(tmpPath, path)
}

然后用协程池同时处理8个视频:

sem := make(chan struct{}, 8)
for _, f := range files {
    sem <- struct{}{}
    go func(video string) {
        defer func() { <-sem }()
        processClip(video)
    }(f)
}
for i := 0; i < cap(sem); i++ {
    sem <- struct{}{}
}

这样365个视频,几分钟就能处理完,要是单个跑,得半小时。

加上时间戳字幕:让每一秒有“重量”

光有画面不够,一年后的你,根本记不清哪天是哪天,所以得在画面右下角加上日期。

FFmpeg的drawtext滤镜可以搞定,但Go里得拼接复杂的滤镜链:

func addTimestamp(input, output, date string) {
    // date格式: 2024-01-01
    filter := fmt.Sprintf(
        "drawtext=text='%s':x=w-tw-10:y=h-th-10:fontsize=24:fontcolor=white:shadowcolor=black:shadowx=2:shadowy=2",
        date)
    cmd := exec.Command("ffmpeg",
        "-i", input,
        "-vf", filter,
        "-codec:a", "copy",
        "-y", output)
    cmd.Run()
}

注意:每次跑FFmpeg都会重新编码视频,这是最耗时的部分,所以最好在预处理阶段就把时间戳加上,别等最后拼接时再加。

我踩过这个坑:先拼合再统一加字幕,结果FFmpeg报“滤镜图太复杂”,因为365个视频,每个都要单独计算字幕位置,滤镜图会爆炸,所以得逐个加

音乐和转场:让365秒不枯燥

纯视频拼接会有点干,加一段背景音乐会好很多,选无版权的轻音乐,长度6分钟左右,用Go的amix混合:

cmd := exec.Command("ffmpeg",
    "-i", "output.mp4",
    "-i", "background.mp3",
    "-filter_complex", "[1:a]volume=0.3[a1];[0:a][a1]amix=inputs=2:duration=first",
    "-c:v", "copy",
    "-y", "final.mp4")

音量调低一点,30% 左右,别盖过原声,如果原视频有声音(比如环境音、说话声),保留一些会更有生活气息

至于转场,简单点就用交叉淡化,FFmpeg的xfade滤镜能实现,但会增加几秒的总时长,如果你要严格365秒,就别加转场,我试过加0.5秒转场,总时长变成365+364×0.5=547秒,感觉节奏变了。

想了个折衷办法:只在月份切换时加0.5秒闪白,用Go判断文件名中的月份是否变化:

lastMonth := ""
for _, f := range files {
    month := strings.Split(filepath.Base(f), "-")[1]
    if month != lastMonth && lastMonth != "" {
        // 这里插入转场逻辑
    }
    lastMonth = month
}

性能优化:别让CPU烧了

处理365个视频,CPU能跑满,这时候Go的并发优势就出来了,但有几个坑:

  1. FFmpeg本身就用多核,再开20个Go协程同时跑,CPU直接100%卡死,建议协程数≤CPU核心数
  2. 磁盘I/O是瓶颈:尽量用SSD,别用机械硬盘,我试过用HDD,写入速度只有30MB/s,预处理卡了2小时
  3. 临时文件清理:每个视频都生成临时文件,365个视频就是365个文件,用os.Remove()及时清理

我写了个进度条,用sync.WaitGroup统计完成数:

var wg sync.WaitGroup
var mu sync.Mutex
completed := 0
for _, f := range files {
    wg.Add(1)
    go func(v string) {
        defer wg.Done()
        processClip(v)
        mu.Lock()
        completed++
        fmt.Printf("\r处理进度: %d/%d", completed, len(files))
        mu.Unlock()
    }(f)
}
wg.Wait()

看着数字从1跳到365,那种成就感真不是盖的。

导出和分发:考虑不同平台

最终视频可能是6分钟,导出时注意:

  • 高码率:建议20Mbps,保证细节,365个1秒片段,每个都可能有不同光照,低码率会出现色块
  • H.264编码:兼容性最好,H.265虽然体积小一半,但很多播放器不支持
  • 音频AAC:256kbps以上

如果要在抖音/小红书发,记得输出竖屏——但这需要重新裁剪,损失两边画面,我选择横屏发B站,加个“这一年的365秒

也可以用Go写一个多尺寸导出功能:

func exportForPlatform(input, output, resolution string) {
    var scale string
    switch resolution {
    case "1080p": scale = "1920:1080"
    case "720p":  scale = "1280:720"
    case "vertical": scale = "1080:1920" // 竖屏
    }
    cmd := exec.Command("ffmpeg",
        "-i", input,
        "-vf", fmt.Sprintf("scale=%s", scale),
        "-c:a", "copy",
        "-y", output)
    cmd.Run()
}

真实案例:我那一年的365秒

说真的,一开始觉得每天拍1秒没啥,但到第100天回头看,泪崩了

1月1日,新年第一天,我在阳台拍日出,冻得直哆嗦,2月14日,和老婆吃饭,她给我夹菜,3月15日,加班到深夜,办公室只剩我一个,4月1日,愚人节被整,哭笑不得,5月,爸妈来住了一周,视频里他们笑得很开心,6月,搬家,镜头里全是纸箱,7月,第一次跑10公里,喘成狗,8月,海边,阳光刺眼,9月,项目上线,连续熬了三天,10月,国庆堵在路上,11月,感冒,镜头里我脸色惨白,12月31日,又一年结束,我在同一个阳台拍日落。

365秒,365个瞬间。 拼在一起,就是活生生的一年。

用Go写这个工具,不是为了炫技,而是因为Python打包太大,C++太难写,Go编译出来一个二进制文件,5MB,扔到任何电脑都能跑,配合FFmpeg,就是最实用的视频自动剪辑工具

很多朋友看完我的视频也想要,我就直接把程序给他们,把素材扔进clips文件夹,双击运行,等几分钟,final.mp4就出来了。就这么简单。

如果你也想记录一年,别犹豫,今天就开始,每天花10秒拍一段素材,想想一年后你能看到昨天所有的今天,那个画面,值得。

365天一天一秒视频,用Go语言记录一整年的时光切片