先说点有的没的
其实一开始我根本没想过要截什么视频,那天我正抱着笔记本电脑窝在沙发上,刷着B站,突然看到一个UP主用Go写了个小工具,能把视频里特定时间段的画面一帧帧提取出来,我当时就乐了——这不就是我前两天想干的事吗?我电脑里那个《365dni》的电影片段,我老想着把里面那段罗马街头的空镜头截下来当壁纸,你说巧不巧?

于是我就想,既然我会点Go,干嘛不自己写一个?反正也就几十行代码的事儿,结果这一搞,就搞了我整整一个下午加半个晚上。
365dni》这个视频
先说说这个视频本身,我截的是《365dni》里男主和女主第一次在罗马街头相遇的那段——大概2分17秒左右,那一段的构图特别漂亮,夕阳把整个街道染成暖黄色,喷泉的水珠在光线下闪闪发亮,我本来是想截个gif发朋友圈,但后来一想,不如直接写个工具把每一帧都存下来,挑最好的一张。
这个视频我存的是 1080p的MP4 格式,体积大概有1.2个G,用 ffprobe 看了下,帧率是 976fps,编码是 h264,这种参数对于Go来说,直接处理原始视频流其实是有点麻烦的,但好处是我不用解码画面,只要调 ffmpeg 就行。
我用Go干了什么
第一步:安装依赖
其实也没啥特别的依赖,我用了 os/exec 包来调用系统里的 ffmpeg,再加了个 strconv 做时间计算,主要代码就三个部分:
- 读取视频文件
- 计算要截取的时间点
- 调用
ffmpeg把那一帧写出来
说实话,最开始我连 ffmpeg 的命令都写错了——我把 -ss 放在了 -i 后面,结果截出来的画面总是偏了半秒钟,后来查了官方文档才知道,-ss 放前面是快速定位,放后面是精确解码,我就需要精确的帧,所以得放后面。
第二步:写代码(边写边踩坑)
下面是我大概写的代码骨架,我把它简化了一下,只留核心逻辑:
package main
import (
"fmt"
"os/exec"
"strconv"
"time"
)
func extractFrame(videoPath string, seconds float64, outputPath string) error {
// 把秒数转换成时间格式
totalSeconds := strconv.FormatFloat(seconds, 'f', 3, 64)
cmd := exec.Command("ffmpeg",
"-i", videoPath,
"-ss", totalSeconds,
"-vframes", "1",
outputPath,
)
return cmd.Run()
}
func main() {
// 我截的是第137.5秒的位置
err := extractFrame("/Users/me/Videos/365dni.mp4", 137.5, "/Users/me/Desktop/frame.jpg")
if err != nil {
fmt.Println("哦豁,截图失败了:", err)
} else {
fmt.Println("截图成功啦!快去桌面看看!")
}
}
你看,就这么简单,但我实际跑的时候遇到一堆破事。
第三步:踩坑实录
坑1:时间不对
我第一次设的 0 秒,结果截出来是男主侧脸,不是我要的街景,我直觉告诉我应该再往后调0.5秒,但 ffmpeg 的 -ss 参数不接受 5 这种带小数的写法吗?其实接受,但要在 -i 后面才能精确到毫秒级,我改成 -ss 137.500 之后,总算对上了。
坑2:输出格式不对
我一开始输出的是 .png,结果一张图30多兆,想想也不对,我要做壁纸,jpg就够了,改成 .jpg 之后,一张图大概2.3MB,清晰度完全够用。
坑3:Go调用ffmpeg时的路径问题
我用的是相对路径,结果执行的时候 cmd.Dir 没设置,导致ffmpeg找不到视频文件,后来我干脆用了绝对路径,省事。
最终我得到了什么
我跑了代码,成功截到了那一帧,画面完美——夕阳刚好打在喷泉上,水珠形成彩虹光圈,路人的影子拉得老长,我把这张图设成了电脑桌面,现在还天天看着呢,但其实我不仅仅是得到一张图,我还写了一个 函数库,可以复用。
我把这个函数封装成了一个小工具,起名叫 frameSnatch,大概长这样:
| 参数 | 说明 | 示例 |
|---|---|---|
videoPath |
视频文件路径 | "/Videos/365dni.mp4" |
timeInSeconds |
要截取的时间点(秒) | 500 |
outputPath |
输出图片路径 | "/Desktop/frame.jpg" |
frameCount |
截取多少帧 | 1 |
我还加了一个批量截取的功能,比如我想截视频里每0.5秒一帧,然后挑出最美的,但后来发现《365dni》那个片段也就4分多钟,全截下来得480帧,处理起来太慢了,所以我还是老老实实只截了一个关键帧。
一点小感悟
其实用Go写这个工具,最大的感受就是:别把简单的需求想复杂了,我一开始还想着要不要用 gocv 库自己解码,后来发现 os/exec + ffmpeg 才是最直接的方案,Go的强项就是调用系统命令、处理IO、管理并发,我干嘛非要跟自己过不去?
而且我发现,代码写出来之后,我不光可以截《365dni》,随便什么视频都能截,我后来还试了截《后翼弃兵》里安雅·泰勒-乔伊下棋的特写,效果一样好,甚至我还帮同事截了他家猫跳窗的视频去打印照片。
你要是也想试试,我强烈建议你自己动手写一写,不需要多复杂的逻辑,一个命令行工具,配合ffmpeg,就能搞定90%的视频截图需求,真的,你只用花半天时间,就能得到属于自己的视频工具。
最后说点有的没的
墙上的钟已经指向凌晨一点多了,我盯着桌面上那张《365dni》的截图,罗马的夕阳暖洋洋的,虽然我知道那是个电影,但就是觉得心里挺舒服的,我想:以后要是有朋友问我“你学Go到底能干啥”,我就把这截图发给他,说——你看,我用Go把一个电影变成了壁纸。
好了,就写到这儿吧,我得去睡了。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/nba/1024.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《我在365dni截了一个视频—一个Golang开发者的真实记录》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:先说点有的没的其实一开始我根本没想过要截什么视频,那天我正抱着笔记本电脑窝在沙发上,刷着B站,突然看到一个UP主用Go写了个小工具,...