我在365dni截取了一段视频,然后学会了用Go写剪辑程序

说来有点不好意思,我其实是个电影迷,但从来不看那种特别火的片子。《365dni》这个名字,我是在B站刷视频的时候偶然看到的,当时有人剪了...

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

我在365dni截取了一段视频,然后学会了用Go写剪辑程序

不过我今天不是来聊剧情的,真正让我上头的,是那个剪辑视频的过程,当时我想把电影里我最喜欢的那一幕——男主在游艇上回头看女主的那几秒——单独截出来,做成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

(10)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-06-30

    我是be365的签约作者“kyadmin”!

  • kyadmin
    kyadmin 2026-06-30

    希望本篇文章《我在365dni截取了一段视频,然后学会了用Go写剪辑程序》能对你有所帮助!

  • kyadmin
    kyadmin 2026-06-30

    本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名

  • kyadmin
    kyadmin 2026-06-30

    本文概览:说来有点不好意思,我其实是个电影迷,但从来不看那种特别火的片子。《365dni》这个名字,我是在B站刷视频的时候偶然看到的,当时有人剪了...

    联系我们

    工作时间:周一至周五,9:30-18:30,节假日休息

    关注我们