你有没有想过,如果有一天,你打开手机,收到一条视频,视频里的人对你说:“今天是第187天,祝你今天心情像阳光一样暖。” 你可能会愣一下——谁这么有心?这不是什么神秘朋友,这是我用Golang写的一段代码,它每天生成一条“祝福视频”,一生成就是365条,听起来像科幻,但其实就是把程序、视频剪辑、语音合成和日期逻辑凑一块儿,我把我踩的坑、淌过的河、写秃的代码,都跟你聊聊。
这个“一年365个祝福的视频”到底是个啥?
先说清楚,不是什么AI生成的未来幻想,就是一个很实在的程序:它读取一个日期,从1月1日到12月31日,每天生成一个短视频,视频里包含:
- 当天的日期和星期几
- 一句祝福语(每天不重样)
- 一个背景画面(静态或动态)
- 一个配音(TTS语音合成)
从程序的角度看,它就是个日程生成器,只不过输出的是视频文件,而不是文本,用Golang写,是因为Golang在并发、文件处理和跨平台上都挺顺的,尤其是我需要同时生成很多条视频的时候,goroutine帮了大忙。
为什么非要365条,而不是随便循环一下?
因为每一秒的祝福都不该一样,如果只是循环一个模板,那是流水线,但每条视频的祝福语、色调、背景图片甚至配乐都可以跟着日期走,比如大年初一有喜庆的红,中秋节有温柔的黄,清明节是淡淡的蓝,程序里我写了一个DateToTheme函数,它把日期映射到色值,视频就跟着季节变。
用Golang写这个视频生成器,核心是什么?
让我把代码拆成三块:视频合成、语音生成、日期逻辑,这三块就像做菜的三步:备菜、炒菜、装盘。
第一块:视频合成——用ffmpeg“拼积木”
Golang本身不会合成视频,但它能调用系统里的ffmpeg,这就像你不会造轮子,但你会用扳手,我在代码里用os/exec包执行ffmpeg命令,把一张背景图、一个文字水印、一段语音合在一起。
func generateVideo(date string, text string, bgImage string) error {
cmd := exec.Command("ffmpeg",
"-loop", "1",
"-i", bgImage,
"-i", "temp_audio.wav",
"-c:v", "libx264",
"-t", "5",
"-pix_fmt", "yuv420p",
"-vf", fmt.Sprintf("drawtext=text='%s':fontsize=24:fontcolor=white:x=(w-text_w)/2:y=h-th-20", text),
"output_"+date+".mp4",
)
return cmd.Run()
}
上面这段代码里,drawtext过滤器把文字嵌在画面上,但有个坑——中文字体,如果你不指定中文字体路径,ffmpeg默认的字体没有中文字形,出来的视频全是方框,我后来下载了Noto Sans CJK SC,在代码里写死路径,才算搞定。

第二块:语音生成——让程序“说话”
文字有了,声音怎么来?简单点,用Golang调一个TTS(Text-to-Speech)服务的API,我选了百度语音合成,因为它的SDK有Go版本,虽然文档写得像天书,但接口还算稳。
func generateAudio(text string, date string) error {
client := speech.NewClient(appID, apiKey, secretKey)
res, err := client.Synthesize(text, speech.SynthesisOption{
Volume: 7,
Speed: 4,
Person: 1, // 女声
})
if err != nil {
return err
}
return ioutil.WriteFile("temp_audio.wav", res.AudioData, 0644)
}
音频生成的参数特别讲究,我第一次用默认速度,结果语音像开了2倍速,听起来像在催命,后来把Speed调到4(范围是1-9),才像正常说话,你可能会问:“那365条祝福语从哪来?”我自己写了一个文本文件,按照日期排列,手动写了110多条,剩下的用AI补全,程序启动时读进一个map[string]string,按日期匹配。
第三块:日期逻辑——怎么算“一年365天”?
Golang的time包很强大,但有两个坑我差点掉进去:
- 闰年,2月29日如果没处理好,程序会崩,我用
time.Date(year, 2, 29, 0, 0, 0, 0, time.UTC)判断,如果返回错误,就跳过。 - 时区,如果你在纽约跑这个程序,生成的是UTC时间,那“1月1日”的祝福可能变成12月31日,我统一用
time.FixedZone("CST", 8*3600)强制东八区。
还有一个细节:星期几,代码里用date.Weekday()返回的枚举是0-6(Sunday-Monday),但我们需要中文,我写了一个映射表:
var weekdays = map[time.Weekday]string{
time.Sunday: "周日",
time.Monday: "周一",
time.Tuesday: "周二",
time.Wednesday: "周三",
time.Thursday: "周四",
time.Friday: "周五",
time.Saturday: "周六",
}
生成365个视频,内存会爆吗?
会,我第一次尝试用for循环一口气生成365条,程序跑到第130条左右,内存飚到2.3GB,然后被系统杀掉,后来我用生产者-消费者模式,用channel控制并发数,同时只跑8个goroutine生成视频,队列里最多存10个任务。
| 并发数 | 生成时间(365条) | 内存峰值 |
|---|---|---|
| 1 | 约73分钟 | 280MB |
| 8 | 约12分钟 | 890MB |
| 16 | 约9分钟 | 7GB |
| 32 | 系统崩溃 | 爆了 |
最后我选了8并发,稳也快,代码像这样:
func worker(jobs <-chan int, wg *sync.WaitGroup) {
for day := range jobs {
date, text := getMessage(day)
generateVideo(date, text, getBgImage(day))
wg.Done()
}
}
踩过的坑:你以为视频生成完了就完了?
天真了,视频文件的大小是个问题,每条视频默认生成5秒,码率默认2Mbps,一条视频大约1.2MB,365条就是437MB,如果上传到云存储,流量费够吃一顿火锅,后来我在ffmpeg参数里加了 -b:v 500k,把码率压到0.5Mbps,画质还能接受,文件大小降到300KB左右,365条总共109MB。
还有个问题——背景图,我本来想用一张四季通用的图,但看起来太敷衍,后来我找了12张高清风光图,每个月换一张,程序里用time.Now().Month()判断月份,再对应图片路径,代码很简单:
func getBgImage(day int) string {
month := int(day / 30) + 1
if month > 12 {
month = 12
}
return fmt.Sprintf("bg_%02d.jpg", month)
}
但这只是个粗略的映射,每个月的天数不一样,2月才28天,我直接按30天切割,导致1月31日就跳到2月背景图了,后来改成按具体日期算:
func getBgImage(t time.Time) string {
month := int(t.Month())
return fmt.Sprintf("bg_%02d.jpg", month)
}
你能拿这个“一年365个祝福视频”干什么?
说实话,我给老婆做了个生日礼物,她把那些视频存在手机里,每天早晨看一条,说是“比天气预报还准”,也可以送给朋友当新年礼物,甚至做成一个定时推送的公众号——每天定点发一条祝福视频,持续一年。
技术层面,它就是个大型的批量视频生产线,你可以扩展成:
- 每天一个学习提示
- 每天一句鼓励的话
- 每天一个笑话(不过笑话容易冷场)
- 每天一个菜谱提示
我甚至想过加入用户数据,比如读取用户的生日,在那天的视频里加一段专属祝福,Golang处理JSON和数据库都挺快的,加个MySQL表就行。
写代码时的那些小确幸
你可能会觉得,写这种程序没啥技术含量,不就是调别人API吗,但真正写的时候,你会碰到各种奇怪的事,比如我用的TTS服务,同一个文字每天请求,语音时长居然不一致,导致视频和语音有0.2秒的错位,后来发现是服务器负载波动,解决方案是在生成视频前加一个time.Sleep(100 * time.Millisecond),稳得像老狗。
还有一次,我写了一个“祝福语随日期递增”的功能:第1天是“祝你开心”,第100天是“祝你开心100倍”,但写逻辑的时候忘了处理“100倍”的文字长度,结果drawtext把文字挤到画面外面去了,解决办法是动态调整字号:
fontSize := 24
if len(text) > 10 {
fontSize = 18
}
你知道那种感觉吗?就像小时候折纸飞机,尾巴折歪了,飞出去打转,你非得把它拆了重新折,写程序也是这样,错一点,整个视频就丑了。
最后说几句(但不算总结)
我现在每天早上打开电脑,跑一遍脚本,生成当天的视频,存进一个文件夹,文件夹命名叫blessings_365,打开看的时候,那种粗糙的合成感反而有种手工的味道——背景图边缘的锯齿,语音稍微带点机械音,文字偶尔超出画面一点点。它不完美,像是一本自己印的日历,每一页都有折痕。
可能这就是想要的吧,一年365个祝福的视频,不是给谁看的,是给自己留的一个念想,时间过得快,但代码跑得慢,写到这,我去跑今天的第213条了,祝福语是“有时候不完美,才是最真实的祝福”,嗯,还挺应景。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.uss1.cn/jiankang/1467.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《一年365个祝福的视频,用Golang写一部会说话的日历》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:你有没有想过,如果有一天,你打开手机,收到一条视频,视频里的人对你说:“今天是第187天,祝你今天心情像阳光一样暖。”你可能会愣一下—...