前阵子我朋友生娃了,天天在朋友圈晒娃,突然有一天问我:“你会不会做那种视频,就是宝宝出生365天,每天一张照片,然后数字从1滚到365那种?”我说这不就是数字滚动效果嘛,网上模板一大堆,但他偏要自己用代码搞,说这样有意义,好吧,我寻思着用Golang写一个也不是不行,反正他懂点编程。
你别说,真上手弄的时候,发现坑还挺多的,比如视频帧率怎么控制、数字滚动的平滑度、图片和数字怎么叠加,今天我就把整个思路和代码拆开了揉碎了跟你聊聊,保证你看完自己能动手写一个。
数字滚动到底是个啥原理?
先别急着写代码,咱得搞清楚数字滚动的本质,你看那些PR模板,数字从1变到365,不是一下子跳过去的,而是一格一格、像老式计程车计价器那样翻过去,这种效果在视频里其实就两件事:
- 每一帧的数字都不一样
- 数字变化的速度要跟视频帧率匹配
如果你每秒视频有30帧,那1秒钟内数字就要变化30次,从1滚到365,假设你总共需要10秒,那每秒钟要变化36.5次,每帧变化大约1.2,但这数字不能是小数啊,所以你得取整,或者用小数累加再取整。
我一开始写了个简单的循环,每帧加1,结果发现数字滚得太快,还没看清就结束了,后来又调整了步长和帧率,总算像那么回事了。
用Golang生成视频?选对库很重要
Golang本身不处理视频,得用第三方库,我试过几个,最后锁定了两个:
| 库名 | 用途 | 优点 | 缺点 |
|---|---|---|---|
| ffmpeg-go | 调用FFmpeg生成视频 | 功能强大,支持各种格式 | 需要系统安装FFmpeg |
| gocv | 基于OpenCV处理图像 | 可以直接操作帧 | 依赖OpenCV,安装略麻烦 |
| imaging | 处理图片叠加文字 | 轻量级,适合文字渲染 | 不支持视频 |
我最后的选择是:用imaging处理图片和数字文字,然后用ffmpeg-go拼成视频,为啥?因为单纯为了个数字滚动去装OpenCV,有点杀鸡用牛刀,imaging库简单粗暴,几十行代码就能在图片上写字。
手把手教你写核心代码
咱们直接上代码,首先你得装好FFmpeg,然后在Go项目里引入这几个包:

import (
"image"
"image/color"
"image/draw"
"image/png"
"github.com/disintegration/imaging"
"github.com/anthonynsimon/bild/transform"
"os"
"strconv"
"fmt"
)
第一步:生成带数字的图片
核心函数就是在图片上写字,我写了这么一个函数:
func generateNumberImage(number int, width, height int) *image.RGBA {
// 创建一个白色背景图片
img := image.NewRGBA(image.Rect(0, 0, width, height))
white := color.RGBA{255, 255, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{white}, image.Point{}, draw.Src)
// 把数字转成字符串
numStr := strconv.Itoa(number)
// 这里用imaging的DrawText函数(需要自己写,或者用其他库)
// 为了方便,我用了github.com/fogleman/gg库来画文字
// 实际上imaging本身不带文字绘制功能
return img
}
等等,上面这个代码其实有缺陷——imaging库本身不支持直接写文字,我踩过这个坑,后来换成了gg库(全名github.com/fogleman/gg),它专门用来画矢量图形和文字。
你可能会问:“那为啥不直接用gg生成视频?”因为gg只能生成图片,不能处理视频流,所以我们还是老办法:先生成一系列带数字的图片,再用FFmpeg合成视频。
第二步:批量生成数字图片
假设我们要从1到365,每张图片数字递增,总共365张图片,但这样生成的视频太短了(如果按30帧/秒,才12秒),所以我们要加一些重复帧,让数字停留久一点。
我后来改成每张图片重复10帧,这样总共有3650帧,按30帧/秒算,大概2分钟,嗯,差不多够用了。
func generateAllFrames(outputDir string) {
for i := 1; i <= 365; i++ {
// 生成带数字i的图片
img := drawNumberImage(i, 1920, 1080)
// 保存到临时目录
filename := fmt.Sprintf("%s/frame_%04d.png", outputDir, i)
f, _ := os.Create(filename)
png.Encode(f, img)
f.Close()
}
}
这里有个小技巧:文件名一定要按顺序命名,比如frame_0001.png、frame_0002.png,不然FFmpeg合成视频时顺序会乱。
第三步:用FFmpeg合成视频
图片都准备好了,最后一步就是调用FFmpeg,我用的库是ffmpeg-go,代码很简单:
func createVideo(inputDir string, outputFile string) {
err := ffmpeg.Input(inputDir+"/frame_%04d.png").
Output(outputFile, ffmpeg.KwArgs{"r": "30", "pix_fmt": "yuv420p", "c:v": "libx264"}).
OverWriteOutput().
Run()
if err != nil {
fmt.Println("视频生成失败:", err)
}
}
这个命令的意思是:把frame_%04d.png这种格式的图片,按30帧/秒的速度,编码成H.264格式的MP4视频。pix_fmt: yuv420p一定要加上,不然有些播放器打不开。
让数字滚动更“丝滑”的进阶技巧
上面的方法虽然能跑,但说实话,数字滚动看起来有点生硬,为什么?因为每一帧的数字都是整数,变化没有过渡,如果你把视频放慢看,会发现数字是“跳”过去的,不是“滚”过去的。
怎么解决?我琢磨了两个办法:
小数步长累加
不每帧加1,而是加5或者2,然后取整显示,这样数字在变化时会有“闪烁”效果,看起来更流畅。
current := 1.0
step := 0.3 // 根据帧率和总时长调整
for i := 0; i < totalFrames; i++ {
displayNum := int(math.Floor(current))
if displayNum > 365 {
displayNum = 365
}
// 生成图片...
current += step
}
引入缓动函数
让数字滚动的速度先快后慢,开始的时候数字变化很快,快到365时慢下来,这需要计算每帧的数字位置。
func easingNumber(frameIndex, totalFrames int) int {
progress := float64(frameIndex) / float64(totalFrames)
// 使用easeOut缓动
eased := 1 - math.Pow(1-progress, 3)
return int(math.Round(eased * 365))
}
这个easeOut函数会让数字在开头变化快,结尾变化慢,看起来特别像那种倒计时视频的质感,我试过,发给朋友看他说“卧槽,这跟PR模板差不多了”。
踩过的坑:字体问题、性能问题和内存泄漏
写这个项目的时候,我至少踩了三个大坑,你一定要注意:
字体路径写死
用gg库画文字时,需要指定字体文件路径,我一开始写死了/usr/share/fonts/...,结果换到Windows电脑上直接崩了,后来改成从配置文件读取字体路径,或者用go-assets把字体打包进二进制文件。
内存占用爆炸
生成3650张1920x1080的图片是什么概念?每张图片大约8MB(未压缩),总共接近30GB!我的电脑直接卡死。
解决办法是:边生成边合成,不保留中间图片,用FFmpeg的pipe模式,直接把图片数据流式输入到FFmpeg进程,代码稍微复杂点,但内存占用能降到几百MB。
// 使用exec.Command直接调用FFmpeg,从stdin写入图片数据
cmd := exec.Command("ffmpeg", "-f", "image2pipe", "-r", "30", "-i", "-", "-c:v", "libx264", "output.mp4")
stdin, _ := cmd.StdinPipe()
// 在循环中生成图片并写入stdin
数字重叠背景图
我朋友要求宝宝照片作为背景,数字浮在右上角,这个实现起来其实不复杂:先生成背景图,然后把数字文字绘制上去,关键是要处理好图层顺序和透明度。
// 先加载背景图片
bg, _ := imaging.Open("baby_photo.jpg")
// 创建数字图层
dc := gg.NewContext(bg.Bounds().Dx(), bg.Bounds().Dy())
dc.DrawImage(bg, 0, 0)
// 设置字体和颜色
dc.SetRGB(1, 1, 1) // 白色文字
dc.DrawStringAnchored("365", 1800, 150, 0.5, 0.5)
// 保存最终图片
dc.SavePNG("final.png")
整个流程串起来,做个完整的脚本
我最后写了个完整的Go程序,大概200行,功能包括:
- 读取配置(总天数、帧率、视频分辨率、字体路径)
- 生成每一帧(带数字的图片,可叠加背景)
- 调用FFmpeg合成视频
- 自动清理临时文件
这个程序放到GitHub上,朋友直接go run main.go就跑起来了,他把他家娃365天的照片按日期命名好,丢到程序里,生成了一个2分多钟的视频,数字从001滚到365,每一个数字背后都是一张不同的照片,我看完觉得,确实比网上的模板有意义。
不过说实话,这程序目前还有几个小毛病:
- 如果照片尺寸不一样,程序会自适应拉伸,但比例可能不对
- 文字阴影效果没做,白底白字会看不清
- 没有进度条,跑起来不知道要等多久
这些问题我打算在下个版本修,不过反正朋友用着挺开心的,我也就不着急了。
其实有时候,用代码做一个东西,比直接用现成模板更有成就感,因为你知道每一帧是怎么来的,每一个数字是怎么变的,这种掌控感,是PR给不了的。
所以你要是也想给自家娃做一个这样的纪念视频,或者给公司做一个周年庆倒计时视频,用Golang搞一套,其实不难,照着上面的思路,顶多花个周末下午写写改改,就成了。
对了,你要是真动手写了,记得把字体文件换成你系统里有的,Windows用C:/Windows/Fonts/arial.ttf,Mac用/System/Library/Fonts/Helvetica.ttc,别问我怎么知道的,问就是踩过坑。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/lvyou/1420.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Golang搞个PR出生365天视频的数字滚动效果,其实没那么难》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:前阵子我朋友生娃了,天天在朋友圈晒娃,突然有一天问我:“你会不会做那种视频,就是宝宝出生365天,每天一张照片,然后数字从1滚到365那...