说来有点不好意思,我其实是个电影迷,但从来不看那种特别火的片子。《365dni》这个名字,我是在B站刷视频的时候偶然看到的,当时有人剪了一段男主在意大利海边奔跑的镜头,配乐很上头,我心想,这画面质感不错啊,就去找了原片,结果一看,好家伙,这是波兰版的《五十度灰》啊。

不过我今天不是来聊剧情的,真正让我上头的,是那个剪辑视频的过程,当时我想把电影里我最喜欢的那一幕——男主在游艇上回头看女主的那几秒——单独截出来,做成gif当表情包用,我打开手机上的剪辑软件,折腾了半小时,结果导出的时候发现画质糊成一团,我气得差点把手机摔了。
为什么要用Go来截视频?
你可能觉得我疯了,截个视频而已,用啥不行?Premiere、Final Cut Pro、甚至剪映不都行吗?是的,它们都行,但我不想用,原因很简单:我是个程序员,而且我是个喜欢“自己造轮子”的程序员。
我平时写Go比较多,Go的特性很适合做这种工具类的开发。性能好、跨平台、编译成单个二进制文件,丢到服务器上就能跑,更重要的是,Go标准库虽然不直接处理视频,但通过调用FFmpeg的C接口或者直接用exec包调用命令行,能非常优雅地实现视频截取。
费曼学习法告诉我:如果你不能把一个东西解释清楚,那你可能还没真正理解它,所以我想通过这篇文章,把我用Go截取《365dni》视频的整个过程,像教朋友一样讲给你听,你不需要是Go专家,甚至不需要会编程,跟着思路走就行。
准备工作:你需要什么
在开始之前,你得准备好以下几样东西:
| 工具/库 | 作用 | 获取方式 |
|---|---|---|
| Go语言环境 | 编译和运行代码 | go.dev下载 |
| FFmpeg | 视频编解码核心 | 官网下载或brew安装 |
| 视频文件 | 你要截取的素材 | 365dni》的mp4文件 |
我用的FFmpeg版本是6.1,Go版本是1.21。版本不同但流程基本一样,别担心。
核心思路:用Go调用FFmpeg
Go本身处理不了视频文件,但是我们可以通过os/exec包调用系统里安装的FFmpeg,这就像是Go负责写剧本,FFmpeg负责演戏。
来看看最基本的代码结构:
package main
import (
"fmt"
"os/exec"
)
func main() {
// 设定参数
inputFile := "365dni.mp4"
outputFile := "clip.mp4"
startTime := "00:25:30" // 开始时间戳
duration := "10" // 截取时长,单位秒
// 构造FFmpeg命令
cmd := exec.Command("ffmpeg",
"-i", inputFile,
"-ss", startTime,
"-t", duration,
"-c", "copy", // 直接复制编码,避免重新编码
outputFile,
)
// 执行命令
err := cmd.Run()
if err != nil {
fmt.Println("出错了:", err)
} else {
fmt.Println("截取完成,文件已保存为:", outputFile)
}
}
这段代码的意思是:从25分30秒开始,截取10秒钟的视频片段。-c copy这个参数很关键,它告诉FFmpeg不要重新编码视频,直接复制原始数据,这样速度飞快,而且画质无损。
我第一次运行这段代码的时候,心脏砰砰跳,听到终端打印出“截取完成”的时候,我激动得差点从椅子上蹦起来,那个游艇回眸的镜头,真的被我截下来了!
进阶玩法:加上时间轴选择
单纯截取一段还不够,我还想精准定位到那一刻。《365dni》里这场戏,我反复看了好几遍,发现最好看的瞬间是在25分31秒到25分33秒之间,但是手动输入时间戳太麻烦,而且还不够直观。
所以我写了一个更高级的版本:先用FFmpeg生成视频的时间轴信息,再让你选择要截取的范围。
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
func main() {
inputFile := "365dni.mp4"
// 第一步:获取视频总时长
cmd := exec.Command("ffprobe",
"-v", "error",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
inputFile,
)
output, _ := cmd.Output()
durationStr := strings.TrimSpace(string(output))
totalSeconds, _ := strconv.ParseFloat(durationStr, 64)
fmt.Printf("视频总时长: %.2f 秒\n", totalSeconds)
fmt.Println("你想从第几秒开始截取? (输入数字,比如30.5)")
// 第二步:读取用户输入
reader := bufio.NewReader(os.Stdin)
startStr, _ := reader.ReadString('\n')
startStr = strings.TrimSpace(startStr)
startSeconds, _ := strconv.ParseFloat(startStr, 64)
fmt.Println("你想截取多少秒?")
durationStr, _ = reader.ReadString('\n')
durationStr = strings.TrimSpace(durationStr)
clipDuration, _ := strconv.ParseFloat(durationStr, 64)
// 第三步:执行截取
outputFile := "my_clip.mp4"
execCmd := exec.Command("ffmpeg",
"-i", inputFile,
"-ss", fmt.Sprintf("%.2f", startSeconds),
"-t", fmt.Sprintf("%.2f", clipDuration),
"-c", "copy",
outputFile,
)
fmt.Println("正在截取中,请稍候...")
err := execCmd.Run()
if err != nil {
fmt.Println("啊哦,截取失败了:", err)
return
}
fmt.Printf("搞定!片段保存在: %s\n", outputFile)
}
这个版本我用了ffprobe来获取视频信息。ffprobe是FFmpeg家族的一员,专门用来读取多媒体文件的元数据,这样一来,程序就知道视频多长,不会让你输入超出范围的时间。
运行的时候,我输入了25.5,然后2,截出来的是25分30秒到25分32秒之间的镜头。那两秒的回眸,我反复看了不下二十遍,画面里男主侧过脸,眼神里带着点狡黠的笑意,海风吹着他的头发——截下来当表情包,绝了。
如果你想要更高清:重新编码
前面我们用-c copy来保证速度和画质,但如果你想把片段的分辨率调低,比如为了让文件更小点方便发微信,那就得重新编码了。
cmd := exec.Command("ffmpeg",
"-i", inputFile,
"-ss", "25:30",
"-t", "5",
"-vf", "scale=640:360", // 缩放到640x360分辨率
"-c:v", "libx264", // 视频编码器
"-crf", "28", // 控制质量,值越高画质越低
outputFile,
)
-vf scale=640:360就是缩放滤镜。-crf 28是控制质量的参数,范围0-51,28是个折中的值,文件大小和画质都还行,我试过一次,原本12MB的10秒片段,缩完后只有1.5MB,发微信秒传。
踩坑实录:我犯过的几个错误
写这个程序的过程中,我栽了不少跟头。
第一个坑:时间戳格式不对。 FFmpeg接受的时间格式有多种,比如00:25:30.500表示25分30秒500毫秒,但我一开始想当然地写了25:30,结果报错说解析不了,后来查文档才知道,小时部分必须写两位,哪怕小于1也要写0,所以应该是00:25:30。
第二个坑:输出文件覆盖问题。 第一次运行时我忘了指定输出路径,FFmpeg默认会在当前目录生成文件,第二次运行同样的代码,它直接覆盖了前一个文件——我那精心截取的两秒片段就这么没了!所以我后来在代码里加了判断,如果文件已存在就自动改名。
第三个坑:跨平台路径问题。 在Windows上写路径要用反斜杠,但在Mac或Linux上用正斜杠,为了通用,我后来用了Go的path/filepath包来处理路径。
更酷的玩法:批量截取所有高光时刻
《365dni》里其实有不少我觉得不错的画面:男主第一次露脸、海边开摩托车、还有那个著名的游泳镜头,我懒得一个个手动截,于是写了一个批量处理版本,用一个配置文件来定义所有要截取的片段。
type Clip struct {
Start float64
End float64
Name string
}
var clips = []Clip{
{25.5, 27.5, "boat_lookback"},
{45.0, 48.0, "motorcycle_scene"},
{120.0, 123.0, "swimming_scene"},
}
for _, clip := range clips {
outputName := clip.Name + ".mp4"
exec.Command("ffmpeg",
"-i", inputFile,
"-ss", fmt.Sprintf("%.2f", clip.Start),
"-t", fmt.Sprintf("%.2f", clip.End - clip.Start),
"-c", "copy",
outputName,
).Run()
}
一次运行,三个片段全部搞定。这个过程真的会上瘾,看着一个个高清片段被精准截取出来,那种控制感太爽了。
我不是在教你怎么截视频
其实写到这里,我发现我真正在做的,不是教你用Go截视频,我只是想借这个例子告诉你:你想搞什么,就去搞,想截视频,不用等别人给你开发好App,你自己就能写一个,想学Go,不用等什么绝佳机会,今天打开终端就能开始。
我那天晚上截完那两秒的回眸镜头,把它做成了微信表情包,每次朋友问我“你这表情包哪来的”,我就说:我自己写的程序搞的,他们一脸懵,但我觉得挺酷。
《365dni》的那段剪影,现在还在我手机里存着,而那个Go程序,被我重构了三四次,加上了进度条、日志输出、甚至还有了一个简单的图形界面版本。从一段视频开始,我竟然学会了怎么操作多媒体文件,怎么处理命令行参数,怎么写跨平台代码。
这大概就是编程的魔力吧,你只是想做一件小事,但它会拉着你走进一个完全没想到的世界,而那个世界里,每一个工具都在等你按你自己的想法去摆弄它们。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/lvyou/730.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《我在365dni截取了一段视频,然后学会了用Go写剪辑程序》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:说来有点不好意思,我其实是个电影迷,但从来不看那种特别火的片子。《365dni》这个名字,我是在B站刷视频的时候偶然看到的,当时有人剪了...