用Golang写一周年纪念视频?365天的代码与回忆,我都给你打包好了

说实话,我一开始也没想到会用Go语言来搞一周年纪念视频,你想啊,纪念视频这种东西,通常不都是用剪映、Premiere,或者至少是Pyth...

说实话,我一开始也没想到会用Go语言来搞一周年纪念视频,你想啊,纪念视频这种东西,通常不都是用剪映、Premiere,或者至少是Python调一下OpenCV吗?但我偏不,我就是那种喜欢折腾的人,而且这一年我写的最多的语言就是Go,从命令行工具到HTTP服务,从并发爬虫到中间件开发,几乎每天都在跟它打交道,所以当365天纪念日快到了的时候,我脑子里蹦出一个念头:能不能用Go来生成一个“一周年纪念视频”?

答案是:能,而且还挺好玩。

为什么选Go?性能、并发、还有那种“硬核浪漫”

先说说我为什么非要用Go,很多人会觉得,视频处理这种活儿,应该交给C++或者Python,但Go这几年在多媒体处理方面其实已经有不少积累,更重要的是,Go的并发模型实在太适合做这种“逐帧处理+批量合成”的任务了。

你想啊,一周年纪念视频,365天,每天至少一张照片或者一段小视频片段,如果按顺序一张一张地处理,那得等到猴年马月?但Go的goroutine一开,几十个协程同时干活,CPU直接拉满,速度嗖嗖的,而且Go的标准库就自带imageimage/colorimage/draw这些包,虽然功能比较基础,但做一些简单的图片合成、文字叠加、渐变效果,完全够用。

更重要的是——用Go写纪念视频这件事本身,就是一种纪念,因为这一整年,我写的每一行Go代码,都在为这个视频做准备,它不只是视频,它是我这一年的编程日志。

技术选型:FFmpeg + Go,完美搭档

纯用Go标准库做视频编码是不现实的,Go的image包只能处理静态图片,没法直接生成视频流,所以我的方案是:

  • Go负责所有图片/视频的预处理、合成、文字叠加
  • FFmpeg负责把图片序列帧压成视频,或者做最后的拼接

这个分工非常清晰,FFmpeg是命令行的视频瑞士军刀,Go是控制中枢,我只需要在Go里把每一帧的图片生成好,然后用os/exec调用FFmpeg命令,几秒钟就能出一段高清视频。

365天,我是怎么把数据整理成“帧”的

做纪念视频的第一步,是整理素材,365天,每天我都拍了一张照片——不是那种刻意摆拍的,就是日常的随手拍,早上起床的咖啡、地铁上的夕阳、深夜写代码的屏幕、周末遛弯遇见的猫……每一张照片背后都是一天。

我把这些照片按日期命名:2023-10-01.jpg2023-10-02.jpg,一直到2024-09-30.jpg,然后在Go里写一个扫描目录的函数:

func loadImages(dir string) ([]string, error) {
    files, err := ioutil.ReadDir(dir)
    if err != nil {
        return nil, err
    }
    var paths []string
    for _, f := range files {
        if strings.HasSuffix(f.Name(), ".jpg") {
            paths = append(paths, filepath.Join(dir, f.Name()))
        }
    }
    sort.Strings(paths)
    return paths, nil
}

注意这里排序很重要——照片必须按时间顺序排列,不然视频放出来就是乱序的,纪念的意义就没了。

每一帧都加一点“生活气息”

光是把照片串成视频太无聊了,我想在每一帧上叠加一些信息:日期、星期几、当天的关键词、甚至当天的天气和步数(如果你有可穿戴设备的数据),这就需要用到Go的imagegolang.org/x/image/font包。

我写了一个函数,接受一张图片和当天的元数据,返回一张新的带文字的图片:

func addTextToImage(img image.Image, text string) image.Image {
    bounds := img.Bounds()
    newImg := image.NewRGBA(bounds)
    draw.Draw(newImg, bounds, img, bounds.Min, draw.Src)
    // 加载字体
    fontData, _ := ioutil.ReadFile("fonts/NotoSansSC-Regular.ttf")
    font, _ := freetype.ParseFont(fontData)
    c := freetype.NewContext()
    c.SetDPI(72)
    c.SetFont(font)
    c.SetFontSize(24)
    c.SetClip(bounds)
    c.SetDst(newImg)
    c.SetSrc(image.NewUniform(color.RGBA{255, 255, 255, 255}))
    pt := freetype.Pt(10, 30)
    c.DrawString(text, pt)
    return newImg
}

字体文件需要自己准备,我用的Noto Sans SC,谷歌的开源字体,直接在GitHub上搜一下就能下载。

并发处理365张图片:goroutine的威力

单线程一张张处理365张图片,每张图片叠加文字+保存,大概需要2-3秒一张,总共得等十几分钟,这太慢了,我改成用worker pool模式,开10个goroutine并发处理:

type job struct {
    index int
    path  string
    text  string
}
func worker(id int, jobs <-chan job, results chan<- string) {
    for j := range jobs {
        img, _ := loadImage(j.path)
        newImg := addTextToImage(img, j.text)
        outPath := fmt.Sprintf("frames/frame_%04d.png", j.index)
        saveImage(newImg, outPath)
        results <- outPath
    }
}

这样10个worker同时干活,处理速度直接提升了10倍,不到两分钟全部搞定,看着终端里刷刷刷的输出,那种感觉真的很爽——就像看着365天在眼前快速翻过

用FFmpeg把帧合成视频

所有帧都生成好之后,最后一步就是合成视频,在Go里调用FFmpeg非常简单:

func makeVideo() {
    cmd := exec.Command("ffmpeg", 
        "-framerate", "24",
        "-i", "frames/frame_%04d.png",
        "-c:v", "libx264",
        "-pix_fmt", "yuv420p",
        "anniversary_video_365.mp4")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
}

这里我用的帧率是24fps,365张图片就是大概15秒的视频,如果你想慢一点,可以降到12fps或者8fps,这样每张照片停留的时间更长,更有回忆的味道。

不只是视频,还内置了一个“时间轴”功能

做这个视频的过程中,我顺便写了一个小工具,可以把照片的元数据导出一个HTML时间轴,用Go内置的html/template包,生成一个带时间线的网页,每一张照片旁边配一段文字,这样你不仅有了视频,还有一个可以直接点开看的“365天日记”。

用Golang写一周年纪念视频?365天的代码与回忆,我都给你打包好了

这个功能是我后来临时加的,但没想到女朋友最喜欢的就是这个——“你看,2023年11月15号那天,你说项目上线压力大,晚上吃了三碗泡面,哈哈哈哈。” 你看,生活就是这样,你以为忘了的东西,照片和代码都替你记着

关于素材和版权的一点提醒

做视频用的照片、音乐、字体,一定要确认版权,我用的照片都是自己拍的,音乐是从Free Music Archive上下载的CC0协议的音乐,字体是Google Fonts的开源字体,不要随便从网上扒素材,尤其是商用的话,被找上门就不好玩了。

FFmpeg和Go本身都是开源工具,完全没问题。

性能不够?那就优化

做完第一版之后,我遇到了一个痛点:生成出来的视频太大了,365张照片,每张都是3000x4000的分辨率,合成后视频文件接近2GB,这分享起来太麻烦了。

于是我加了一个压缩步骤:

func resizeImage(img image.Image, maxWidth int) image.Image {
    bounds := img.Bounds()
    w := bounds.Dx()
    h := bounds.Dy()
    if w <= maxWidth {
        return img
    }
    ratio := float64(maxWidth) / float64(w)
    newW := int(float64(w) * ratio)
    newH := int(float64(h) * ratio)
    newImg := resize.Resize(uint(newW), uint(newH), img, resize.Lanczos3)
    return newImg
}

用的是github.com/nfnt/resize这个库,Lanczos3插值算法,画质损失很小,但文件体积直接降到了200MB以内。

多段视频拼接:第三部分我加了“生活BGM”

我把视频分成了三段:

段落 时长 配乐
第一章 365天照片快闪 15秒 轻快钢琴曲
第二章 精选的12段短视频 60秒 温暖吉他
第三章 文字卡+最后的彩蛋 10秒 安静收尾

每一段都是单独用Go+FFmpeg生成的,最后再用FFmpeg拼接:

func concatVideos(videos []string, output string) {
    listFile := "videos.txt"
    var content strings.Builder
    for _, v := range videos {
        content.WriteString(fmt.Sprintf("file '%s'\n", v))
    }
    ioutil.WriteFile(listFile, []byte(content.String()), 0644)
    cmd := exec.Command("ffmpeg", "-f", "concat", "-safe", "0",
        "-i", listFile, "-c", "copy", output)
    cmd.Run()
}

这里的彩蛋我放了一段用Go代码生成的动态文字——“365天,谢谢你一直都在”,那段文字是用image/gif包写的,逐帧闪烁,配合FFmpeg转成视频,虽然技术上不算复杂,但看到它出现在视频最后的时候,我承认我有点眼眶发热。

一些踩坑记录,帮你少走弯路

  1. 字体文件路径一定要写对,我一开始用相对路径,跑起来总报错,后来改成绝对路径才搞定。
  2. 颜色空间要小心,用image.NewRGBA创建新图的时候,如果与原图颜色空间不一致,合成出来颜色会偏,最好统一用color.RGBAModel
  3. FFmpeg的参数顺序不能乱,比如-i必须在-c:v之前,否则报错,建议直接复制我上面的命令别自己改。
  4. 帧命名必须补零frame_%04d.png这种格式,FFmpeg才能正确识别序列,如果文件名有跳跃,它会漏帧或者报错。
  5. 内存管理,365张全尺寸图片同时加载,内存占用会飙升,我改成逐张加载、处理、释放,配合sync.Pool复用临时对象,内存稳定在300MB以内。

如果真的想做一个给自己用

你可能会说,我只是想做一个纪念视频,有必要这么折腾吗?确实,如果你只是想快速出一个视频,用剪映拖拽几下就搞定了,但用Go写这个过程,给我带来的不仅仅是最终那个视频文件

  • 我学会了image包的底层操作
  • 我摸清了FFmpeg的命令行参数
  • 我用goroutine实现了一个完整的流水线
  • 我把一年的数据整理成了可搜索、可重放的时间轴

而且最神奇的是,这个视频我可以每年都跑一次,只需要换一下照片目录和日期范围,就能生成新的,现在已经成了一个自动化纪念视频生成器

我喜欢这种感觉——用代码记录生活,用技术表达感情,Go不是最酷的语言,但它快、稳、简单,就像一年365天,每一天都平平无奇,但串起来就是一部属于你自己的电影。

好了,不多说了,视频已经导出,我准备去给女朋友看了,希望到时候她还记得2023年10月15号那天的红烧牛肉面有多难吃。

本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/tiyu/1434.html

(11)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-07-10

    我是be365的签约作者“kyadmin”!

  • kyadmin
    kyadmin 2026-07-10

    希望本篇文章《用Golang写一周年纪念视频?365天的代码与回忆,我都给你打包好了》能对你有所帮助!

  • kyadmin
    kyadmin 2026-07-10

    本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名

  • kyadmin
    kyadmin 2026-07-10

    本文概览:说实话,我一开始也没想到会用Go语言来搞一周年纪念视频,你想啊,纪念视频这种东西,通常不都是用剪映、Premiere,或者至少是Pyth...

    联系我们

    工作时间:周一至周五,9:30-18:30,节假日休息

    关注我们