当前位置:首页 > 科技 > 正文

一年365天视频怎么做?我拿Golang写了套方案,结果真能跑

  • 科技
  • 2026-07-20 22:22:50
  • 138
摘要: 说起来你可能不信,我第一次想搞“一年365天视频”这个事,是被逼的,朋友开了个民宿,说每天都要发一条短视频宣传,问我能不能自动化...

说起来你可能不信,我第一次想搞“一年365天视频”这个事,是被逼的,朋友开了个民宿,说每天都要发一条短视频宣传,问我能不能自动化,我当时心想:365条视频,每一条都手动剪辑渲染?这不是要命么,后来我琢磨了一下,用Golang撸了一套方案,从脚本生成到视频合成,还真就全自动跑起来了。

今天我就把这套东西拆开讲,不说高大上的概念,就说我踩过的坑和实际能跑的代码逻辑。

为什么选Golang来做这件事?

跨平台编译太香了

我机器是Mac,但最后部署在Linux服务器上,Golang一条命令GOOS=linux GOARCH=amd64 go build,编译出来扔上去就能跑,Python倒是也能跨平台,但依赖装起来真能让你怀疑人生。

并发处理视频是刚需

365条视频,你要是顺序处理,电脑得烧起来,Golang的goroutine天然适合并行渲染,我一开始用sync.WaitGroup控制并发数,后来改成了带缓冲的channel限流,CPU利用率直接拉满。

视频素材从哪来?我用了三种方式

素材类型 来源 处理方式
图片 Unsplash API 每天自动下载一张
视频片段 本地录制或剪映导出 切片后循环使用
字幕文字 自己写的模板+变量替换 动态插入

图片这块我踩过大坑:有的API限制每天下载次数,后来我直接本地存了500张高清图,每天随机选,省心。

核心代码怎么组织的?

视频元数据管理

type VideoMeta struct {
    Date       string `json:"date"`     string `json:"title"`
    ImagePath  string `json:"image_path"`
    AudioPath  string `json:"audio_path"`
    OutputPath string `json:"output_path"`
}

每天跑任务前,先读一个JSON文件,里面存了365天的元数据,这文件我是一次性写好的,用Excel转JSON,省得每天手动填。

FFmpeg命令的封装

Golang本身不处理视频,它是调FFmpeg,我封装了一个函数:

func RenderVideo(meta VideoMeta) error {
    cmd := exec.Command("ffmpeg",
        "-loop", "1",
        "-i", meta.ImagePath,
        "-i", meta.AudioPath,
        "-c:v", "libx264",
        "-t", "60",
        "-pix_fmt", "yuv420p",
        meta.OutputPath,
    )
    return cmd.Run()
}

这段代码看着简单,但我调了至少三天参数。"-t"参数必须放在输入文件后面,否则FFmpeg不认,还有-shortest参数也经常搞混。

并发渲染的坑

第一次跑并发,200个goroutine同时调FFmpeg,电脑风扇直接起飞,然后死机了,后来改成信号量限流

sem := make(chan struct{}, 4) // 同时最多4个渲染任务
for _, meta := range allMetas {
    sem <- struct{}{}
    go func(m VideoMeta) {
        defer func() { <-sem }()
        RenderVideo(m)
    }(meta)
}

这样CPU负载控制在80%左右,能稳定跑完。

字幕和配音怎么自动化?

文字转语音

我用的是离线TTS引擎,本地部署了一个Mozilla TTS的镜像,Golang通过HTTP调用:

func GenerateTTS(text string) ([]byte, error) {
    resp, err := http.PostForm("http://localhost:5002/api/tts",
        url.Values{"text": {text}, "voice": {"zh-CN"}})
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

语音生成是整个流程最慢的一环,一条60秒的音频,TTS要跑5-8秒,但好在可以缓存:如果同样的文本生成过,直接读本地文件。

字幕硬编码

我试过两种方式:

  • 软字幕:FFmpeg加-vf "subtitles=sub.srt",但有的播放器不认
  • 硬字幕-vf "drawtext=text='你好':fontfile=simhei.ttf:fontsize=24"

最后选了硬字幕,因为兼容性好,不过要注意字体文件必须放在同目录,否则报错让你找半天。

实际跑起来会遇到哪些问题?

磁盘空间爆炸

365条视频,每条60秒,1080p,MP4格式,算下来大概200GB,我一开始没算,跑到第100条的时候磁盘满了,解决方案是边渲染边上传到OSS,本地只保留最近7天的缓存。

文件名冲突

我最初用日期命名:2025-01-01.mp4,结果遇到时区问题,服务器UTC时间比北京时间差8小时,文件名都串了,后来改成时间戳+随机字符串

outputName := fmt.Sprintf("%d_%s.mp4", time.Now().Unix(), randomString(6))

渲染质量不一致

同样的参数,有的视频清晰有的模糊,排查发现是图片分辨率不一致,我增加了一步预处理:所有图片统一缩放到1920x1080,用nano库处理:

func ResizeImage(inputPath string) error {
    img, err := imaging.Open(inputPath)
    if err != nil {
        return err
    }
    resized := imaging.Fit(img, 1920, 1080, imaging.Lanczos)
    return imaging.Save(resized, inputPath)
}

有没有更简单的方案?

说实话,如果你只有几十条视频,手动剪映就行,但到了365条这个量级,自动化是唯一出路,Golang的优势在于:

  • 编译成单文件部署
  • 天然支持高并发
  • FFmpeg调用稳定
  • 跨平台无依赖

我后来把整个项目放在了GitHub上,目录结构是这样的:

video365/
├── cmd/
│   └── main.go          // 入口
├── internal/
│   ├── render.go        // 渲染逻辑
│   ├── tts.go           // 语音生成
│   └── utils.go         // 工具函数
├── data/
│   ├── metas.json       // 元数据
│   └── images/          // 图片素材
├── output/              // 输出目录
└── go.mod

这一套跑下来,每天自动生成一条视频,传到自媒体平台,朋友用了三个月,粉丝涨了8000,虽然不能说全是视频的功劳,但至少断更问题彻底解决了

关于性能的几个实测数据

操作 耗时(单条) 365条总耗时 备注
图片下载 5-2秒 约10分钟 网络波动影响大
TTS生成 5-8秒 约40分钟 可缓存重复文本
视频渲染 15-30秒 约2小时 看视频长度和分辨率
上传OSS 3-5秒 约30分钟 百兆宽带

如果你用我的方案,建议先跑10条测试,确认没问题再全量跑,我吃过一次亏:跑了一半发现字体文件路径写错了,所有字幕都没显示,只能删掉重来。

写这篇文章的时候,我又看了眼那个项目的Star数,居然有47个,可能大家都有“一年365天视频”这个需求吧,说到底,技术就是拿来解决问题的,Golang干这事,挺顺手的。

一年365天视频怎么做?我拿Golang写了套方案,结果真能跑