用Golang写一周年纪念视频,365秒刚刚好

我记得去年这时候,我媳妇儿跟我说:“咱们结婚一周年,能不能搞个视频?”我当时就懵了,不是不想搞,是真不知道咋搞,后来我琢磨了一下,既然是...

我记得去年这时候,我媳妇儿跟我说:“咱们结婚一周年,能不能搞个视频?”我当时就懵了,不是不想搞,是真不知道咋搞,后来我琢磨了一下,既然是搞技术的,那就用代码搞定呗,用Golang写个一周年纪念视频生成工具,365秒,一秒不多一秒不少。

为什么是365秒,为什么用Golang

一周年嘛,365天,365秒,每一秒代表一天,这么一想还挺浪漫的,当然你也可以改成366,闰年嘛,随你。

至于为什么用Golang,实话实说,因为我只会这个,但用下来发现,Go做视频处理其实挺顺手的。FFmpeg在Go里有绑定库,图像处理有imaging,再加个ffmpeg-go,基本上能搞定大部分需求,你要是问我用Python行不行,那肯定行,但Go部署简单,编译出来一个二进制文件扔哪都能跑,省心。

准备工作:装东西

先说说你需要装什么,别怕,不难。

用Golang写一周年纪念视频,365秒刚刚好

  • FFmpeg:视频处理的核心,必须装,Mac用户brew install ffmpeg,Ubuntu用户sudo apt install ffmpeg
  • Go语言环境:1.16以上版本就行
  • 几个Go库:
go get github.com/u2takey/ffmpeg-go
go get github.com/disintegration/imaging

装完就差不多了,我一开始还装了一堆别的库,后来发现根本用不上。

第一步:准备素材

这个步骤最花时间,但也是最有意思的部分,你得准备365张照片,每张代表一天,别怕,不用真的每天拍一张,可以从手机里挑,从相册里翻,我媳妇儿跟我说“你去年每一天都在干嘛”,我说我哪记得,但照片里有。

照片不用太大,1024x768就够,多了也没用,视频分辨率一般也就1920x1080,你要是照片尺寸不一样,程序会自动缩放,但最好统一一下尺寸,免得变形。

第二步:写代码核心逻辑

好了,说到重点了,代码其实不复杂,但有几个坑我得跟你说说。

package main
import (
    "fmt"
    "image"
    "image/color"
    "image/draw"
    "image/jpeg"
    "os"
    "path/filepath"
    "strconv"
    "strings"
    "time"
    "github.com/disintegration/imaging"
    ffmpeg "github.com/u2takey/ffmpeg-go"
)

这一段是库引用,没啥好说的,就是标准的Go写法。

核心逻辑是这样的:读取365张图片,每张图片加个日期标签,然后合成一个视频,视频时长365秒,每张图片显示1秒。

等等,我是不是漏了什么?对了,还要加文字,一周年纪念视频嘛,得有点仪式感,每张图片左下角加个“第X天”,右上角加个日期,中间可以加一句想说的话,但别每张都加,太假了,365张都加一样的话也没意思,我是在开头10张和结尾10张加了话,中间就加个日期。

func addLabel(img image.Image, x, y int, label string) image.Image {
    // 这个函数是为了给图片加文字
    // 但Go标准库没有直接的文字绘制功能
    // 我用的方法是先生成一个带文字的图层,然后叠加
    dst := imaging.Clone(img)
    // 这里用imaging的DrawText功能
    // 实际上imaging也没有DrawText,得用freetype
    // 所以我换了个思路
    return dst
}

这段代码我是写到一半才发现imaging不支持文字绘制,坑啊,后来我改成了用freetype库,或者更简单的方法:用FFmpeg的文字叠加功能,在合成视频的时候统一加文字。

说实话,我折腾了三天才搞明白这个,如果你不想折腾文字绘制,直接在图片上用Photoshop或者美图秀秀把文字加上,省事。

第三步:合成视频

合成视频这一步,Go调用FFmpeg来做,这是最爽的部分,因为你不用自己处理编码解码。

func createVideo(imageDir, outputFile string) error {
    // 先确保图片按顺序重命名
    // 从001.jpg到365.jpg
    err := renameImages(imageDir)
    if err != nil {
        return fmt.Errorf("重命名图片失败: %v", err)
    }
    // 用FFmpeg合成视频
    // 输入帧率设为1,每张图片显示1秒
    err = ffmpeg.Input(
        filepath.Join(imageDir, "%03d.jpg"),
        ffmpeg.KwArgs{"framerate": "1"},
    ).
    Output(outputFile, ffmpeg.KwArgs{
        "c:v":     "libx264",
        "pix_fmt": "yuv420p",
        "r":       "30",
        "t":       "365",
    }).
    OverWriteOutput().
    Run()
    return err
}

关键参数解释一下:

  • framerate: 1:每秒1帧,对应每张图片显示1秒
  • c:v: libx264:视频编码格式,H.264,最通用的
  • pix_fmt: yuv420p:像素格式,兼容性最好
  • r: 30:输出视频帧率30fps,这样每张图片实际会重复30帧
  • t: 365:视频时长365秒

这步跑完,你就得到一个365秒的视频文件了,但别急着开心,我第一个版本跑出来发现图片顺序不对,因为我的文件名是按照拍照日期来的,不是按照1到365编号的,所以必须在前面加一步重命名。

第四步:重命名图片的小坑

重命名这块,我踩了个坑,Go读取目录文件默认是按文件名排序的,但如果你从手机导出的照片,文件名可能是IMG_20230101_123456.jpg这种,按字母排序会乱。

解决办法:读取所有文件的修改时间,按时间排序后再重命名。

func renameImages(dir string) error {
    files, err := os.ReadDir(dir)
    if err != nil {
        return err
    }
    type fileInfo struct {
        name string
        modTime time.Time
    }
    var fileList []fileInfo
    for _, f := range files {
        if f.IsDir() {
            continue
        }
        info, _ := f.Info()
        fileList = append(fileList, fileInfo{
            name: f.Name(),
            modTime: info.ModTime(),
        })
    }
    // 按修改时间排序
    sort.Slice(fileList, func(i, j int) bool {
        return fileList[i].modTime.Before(fileList[j].modTime)
    })
    // 重命名
    for i, f := range fileList {
        ext := filepath.Ext(f.name)
        newName := fmt.Sprintf("%03d%s", i+1, ext)
        os.Rename(
            filepath.Join(dir, f.name),
            filepath.Join(dir, newName),
        )
    }
    return nil
}

这个逻辑是对的,但有一个问题:如果你的照片跨年了,比如从2023年6月到2024年6月,那按修改时间排序没问题,但如果你的照片是不同设备拍的,修改时间可能不准,稳妥起见,我建议你手动整理一下顺序,把照片按1到365命名好再跑程序。

第五步:加点音乐和转场

光有图片太单调了,我加了背景音乐,选了首我俩都喜欢的歌,剪辑成365秒,用FFmpeg混流就行。

func addMusic(videoFile, musicFile, outputFile string) error {
    return ffmpeg.Input(videoFile).
        Input(musicFile).
        Output(outputFile, ffmpeg.KwArgs{
            "c:v": "copy",
            "c:a": "aac",
            "shortest": nil,
        }).
        OverWriteOutput().
        Run()
}

shortest参数会让输出以最短的流为准,也就是如果音乐比视频长,就自动裁剪。

转场效果我试了试,但发现用Go调用FFmpeg做转场太复杂了,我放弃了,直接在图片之间加了个淡入淡出,FFmpeg的fade滤镜可以搞定。

filter := "fade=t=in:st=0:d=1,fade=t=out:st=364:d=1"

这个滤镜在视频开头和结尾各加1秒的淡入淡出,中间图片切换的转场,我没做,因为365张图片每张1秒,加转场会缩短显示时间,或者需要延长总时长,我最后选择了不要转场,朴素点也挺好。

第六步:处理意外情况

我跑了第一版,发现视频出来了,但有黑屏,排查了半天,发现是有几张图片损坏了,或者格式不对,Go的image库解码时直接panic了。

解决办法:在读取图片时加个错误处理,跳过损坏的图片,用一张默认的灰色图片代替。

func loadImage(path string) image.Image {
    img, err := imaging.Open(path)
    if err != nil {
        // 生成一张灰色图片
        img = image.NewRGBA(image.Rect(0, 0, 1024, 768))
        draw.Draw(img, img.Bounds(), 
            &image.Uniform{color.RGBA{128, 128, 128, 255}}, 
            image.Point{}, draw.Src)
    }
    return img
}

这个处理挺糙的,但管用,灰色的图片在视频里虽然不好看,但总比黑屏强。

第七步:测试和调整

写完了你以为就完了?不不不,调参数调了一天。

第一个问题是视频太大了,365张图片,每张1024x768,编码成H.264,出来一个2GB的文件,太大了,传微信都传不了。

解决办法:降低码率。

"b:v": "2000k",  // 视频码率2000kbps

码率降到2000k,文件大小缩减到200MB左右,清晰度还行。

第二个问题是部分图片变形,因为我的照片有的是横着拍的,有的是竖着拍的,竖拍的照片在横屏视频里会留黑边,解决办法:统一填充成16:9比例,不做拉伸。

func resizeToFill(img image.Image, width, height int) image.Image {
    return imaging.Fill(img, width, height, imaging.Center, imaging.Lanczos)
}

这样竖拍的照片会被裁剪掉上下部分,但至少不会变形,我挑了一些关键的照片手动调整了构图。

第八步:生成最终版本

所有代码写完后,我整了一个完整的脚本,自动化处理,从整理照片到生成最终视频,一行命令搞定。

实际上我最后跑了三次才成功,第一次是图片顺序错了,第二次是音乐长度不对,第三次才完美,我媳妇儿看完哭了,说“你还挺用心的”,值了。

完整代码我放在GitHub上了,不过你要是自己写一遍,踩一遍坑,最后出来的效果会更有意义,一周年纪念嘛,重要的不是视频多完美,而是那份心意,365秒,记录365天,每一秒都是真实的生活,有点糊的照片,有点歪的构图,反而更真实。

最后成品我放到了家庭群,我妈看了说“这啥玩意儿,怎么这么多照片”,我爸说“人家这是高科技”,好吧,管他呢,反正我媳妇儿喜欢就行。

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

(11)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-06-29

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

  • kyadmin
    kyadmin 2026-06-29

    希望本篇文章《用Golang写一周年纪念视频,365秒刚刚好》能对你有所帮助!

  • kyadmin
    kyadmin 2026-06-29

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

  • kyadmin
    kyadmin 2026-06-29

    本文概览:我记得去年这时候,我媳妇儿跟我说:“咱们结婚一周年,能不能搞个视频?”我当时就懵了,不是不想搞,是真不知道咋搞,后来我琢磨了一下,既然是...

    联系我们

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

    关注我们