使用Golang为网站构建视频编辑功能的后端
使用Golang为网站构建视频编辑功能的后端 大家好,
我正在开发一个视频编辑网站,并使用Go构建后端。虽然大部分功能都已实现,但我遇到了一些技术挑战:
-
使用Go进行视频处理: 我正在使用
ffmpeg-go来处理视频处理任务,如剪辑、合并和添加转场。然而,对于较大的视频,处理时间明显高于预期。是否有办法优化Go的视频处理,或者我应该考虑其他方法,例如并行运行FFmpeg任务? -
实时视频预览: 我希望生成实时视频预览(例如,在用户应用滤镜或进行编辑后)。Go是否适合高效处理此类任务,还是我需要将其卸载到专门的服务器?如果Go可以处理,您推荐哪些库或模式?
-
处理大文件上传: 用户经常上传大型视频文件(有时超过2GB)。我正在使用基于Go的服务器配合多部分表单处理器,但上传偶尔会失败,尤其是对于较大的文件或在较慢的连接上。在Go中处理大文件上传以确保可靠性和性能的最佳方法是什么?
-
用于进度更新的WebSocket集成: 我已经实现了WebSocket来通知用户其视频处理任务的进度,但更新不一致。有时服务器会在任务中途停止发送进度更新。Go的WebSocket库是否存在已知问题,或者这可能是我实现的问题?
-
为前端优化API: 我的前端(使用React构建)经常与Go后端交互以获取视频元数据和更新编辑内容。随着更多用户加入平台,API调用的延迟正在增加。如何优化我的Go API以处理高流量,同时保持快速的响应时间?
我很乐意听取任何有使用Go构建视频密集型平台经验的人的建议、最佳实践或示例。任何帮助都将不胜感激!
提前感谢!
更多关于使用Golang为网站构建视频编辑功能的后端的实战教程也可以访问 https://www.itying.com/category-94-b0.html
有人能帮我解决这个问题吗?我非常希望得到您的指导。谢谢!
更多关于使用Golang为网站构建视频编辑功能的后端的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
joeroot:
在Go中处理大文件上传,同时确保可靠性和性能的最佳方法是什么?
我还没有测试过,但理论上这应该可行。
- 创建一个S3对象存储(不仅AWS提供此服务)
- 将文档上传到你的S3存储桶
- 将路径和元数据存储在关系型数据库中
S3对象存储可以处理TB级数据,并且或许可以使用gRPC进行流式传输?
第五点基本上是我的专长,而且看起来确实有些不对劲。这个网站有多少用户?即使没有优化,你的后端也应该能够处理大量的连接和用户。 如果这是“你的个人网站”,那根本不应该出现这个问题,Go 天生就很快。
此外,你已经集成了 WebSockets,但仍然有很多 API 调用。(这里信息太少,无法判断,但也许值得思考一下)
最后,你的 GitHub 账户显示的是另一个人,可能人们因此不在这里留言;他们以为你是个机器人 
ffmpeg-go:可以尝试 GitHub - asticode/go-astiav: Golang ffmpeg and libav C bindings 或 GitHub - 3d0c/gmf: Go Media Framework。
这两个库我都用过。它们是集成度很好的库。如果你使用静态库编译,运行环境就不需要ffmpeg相关的共享库。 还有一些其他工具库是通过命令行调用的,但在我看来,它们非常糟糕。
对于其他问题,我认为其他人已经回答得差不多了,我就不再补充了。
我重新阅读了你的问题,我认为我应该再次回答:
- ffmpeg-go,这个问题和我之前说的一样。
- 视频预览,这和第一个问题一样,调用一些库方法来处理可以达到目的。至于效率问题:ffmpeg库可以使用硬件加速,这取决于视频类型和运行环境,请自行了解(但纯软件计算也不会太差,至少在我功耗相对较低的CPU结合OpenGL的情况下,我的GUI屏幕仍然可以显示60帧解码后的视频。)
- 对于大文件上传,除了拥有可靠的后端存储,你还需要在前端和后端添加重传机制,以便在传输失败时进行断点续传。这和你的下载逻辑类似。
- websocket?如果只是为了更新进度,我认为ws有点重(如果你没有双向通信或大量复杂API的需求,那么不要使用ws,它只会增加你的维护成本);ws库,据我所知,在普通使用中没有明显的缺陷,但不排除你遇到了bug。
- 看起来你只是没有很好地调度处理逻辑。这是一个复杂的主题。在看到完整代码之前,我无法指出具体问题。但有一些常见的概念。最简单的优化是调整处理逻辑,减少互斥等。
joeroot:
- 与 ffmpeg-go 集成:视频上传到 S3 后,您建议将它们下载到后端服务器进行处理,还是直接在存储服务上处理更好?如果是后者,是否有特定的工具或库可以让 FFmpeg 与 S3 高效协作?
这超出了我的能力范围,但听起来网络服务器应该在发送到 S3 服务器之前进行转换。
- 用于流式传输的 gRPC:您能澄清一下在这种情况下如何利用 gRPC吗?它是用于在后端和 S3 之间流式传输视频数据,还是用于向前端通信进度更新?
我打算做的是一个“微服务”(gRPC 服务器,几乎像一个 API),它只处理网络服务器和 S3 存储之间的通信。或许它可以在将文件传递给存储之前处理转换和其他处理。
- 元数据存储:当您说“将路径和元数据存储在 RDBMS 中”时,您对如何构建这个有偏好吗?例如,我是否应该将文件版本(例如,原始、已处理、预览)存储在一个具有不同路径的表中,还是使用更规范化的结构?
还没到那一步,但我想我应该将元数据存储在 RDBMS 中以实现快速搜索。并且搜索标准可能因情况而异。
感谢您的建议!
使用S3兼容的对象存储来处理大文件上传听起来是一个可靠的方法。我理解将文件存储委托给像S3这样的服务可以减轻Go服务器的负载并提高可扩展性。
关于这种方法,我有几个后续问题:
- 与 ffmpeg-go 集成:视频上传到S3后,您建议将它们下载到后端服务器进行处理,还是直接在存储服务上进行处理更好?如果是后者,是否有特定的工具或库可以让FFmpeg高效地与S3协同工作?此外,我注意到像CapCut这样的视频编辑器能高效执行高级处理任务——您认为类似的优化方法是否适用于服务器端视频编辑流水线?对于那些有兴趣探索像CapCut中那样的高级编辑功能的人,可以点击这里查看CapCut修改版apk版本。
- 用于流式传输的 gRPC:您能详细说明在这种情况下如何利用gRPC吗?它是用于在后端和S3之间流式传输视频数据,还是用于向前端通信进度更新?
- 元数据存储:当您说“将路径和元数据存储在RDBMS中”时,对于如何构建这个结构有偏好吗?例如,我应该在单个表中存储文件版本(例如,原始、已处理、预览)并附带不同的路径,还是使用更规范化的结构?
再次感谢您的意见!我将进一步探索这个方向,看看它如何能优化我的平台。
1. 使用Go进行视频处理优化
对于大型视频处理,建议采用并行FFmpeg任务处理。使用Go的goroutine池可以显著提升处理效率。以下是示例代码:
package main
import (
"context"
"fmt"
"github.com/u2takey/ffmpeg-go"
"golang.org/x/sync/errgroup"
)
func processVideoSegment(inputPath, outputPath string, start, duration float64) error {
err := ffmpeg_go.Input(inputPath, ffmpeg_go.KwArgs{"ss": start}).
Output(outputPath, ffmpeg_go.KwArgs{"t": duration, "c": "copy"}).
OverWriteOutput().
Run()
return err
}
func parallelVideoProcessing(segments []VideoSegment) error {
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(4) // 限制并发数
for _, seg := range segments {
seg := seg
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return processVideoSegment(seg.Input, seg.Output, seg.Start, seg.Duration)
}
})
}
return g.Wait()
}
type VideoSegment struct {
Input string
Output string
Start float64
Duration float64
}
2. 实时视频预览处理
Go适合处理实时视频预览,推荐使用gstreamer绑定库。以下是示例:
package main
import (
"github.com/notedit/gstreamer-go"
"net/http"
)
func generatePreview(w http.ResponseWriter, r *http.Request) {
pipelineStr := "filesrc location=input.mp4 ! decodebin ! videoconvert ! jpegenc ! multipartmux boundary=frame ! appsink name=sink"
pipeline, err := gstreamer.New(pipelineStr)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sink := pipeline.GetByName("sink")
w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=frame")
for {
sample := sink.PullSample()
if sample == nil {
break
}
w.Write(sample.Data())
w.(http.Flusher).Flush()
}
}
3. 大文件上传处理
使用分块上传和流式处理可以解决大文件上传问题:
package main
import (
"io"
"net/http"
"os"
"path/filepath"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(32 << 20) // 32MB内存缓冲
file, header, err := r.FormFile("video")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
// 创建临时文件
tempFile, err := os.CreateTemp("uploads", "upload-*.tmp")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer tempFile.Close()
// 流式写入
buf := make([]byte, 1024*1024) // 1MB缓冲区
for {
n, err := file.Read(buf)
if err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if n == 0 {
break
}
if _, err := tempFile.Write(buf[:n]); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// 重命名为最终文件
finalPath := filepath.Join("uploads", header.Filename)
os.Rename(tempFile.Name(), finalPath)
w.WriteHeader(http.StatusCreated)
}
4. WebSocket进度更新
使用gorilla/websocket库实现稳定的进度更新:
package main
import (
"log"
"net/http"
"time"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func progressHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
// 模拟处理进度
for i := 0; i <= 100; i += 10 {
time.Sleep(1 * time.Second)
err := conn.WriteJSON(map[string]interface{}{
"progress": i,
"status": "processing",
})
if err != nil {
log.Println("Write error:", err)
break
}
// 添加ping/pong保持连接
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
}
conn.WriteJSON(map[string]interface{}{
"progress": 100,
"status": "completed",
})
}
5. API性能优化
使用缓存、连接池和异步处理优化API性能:
package main
import (
"context"
"database/sql"
"encoding/json"
"net/http"
"sync"
"time"
"github.com/go-redis/redis/v8"
_ "github.com/lib/pq"
)
var (
rdb *redis.Client
db *sql.DB
cache sync.Map
)
func init() {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
PoolSize: 100, // 连接池大小
})
db, _ = sql.Open("postgres", "user=postgres dbname=video_edit sslmode=disable")
db.SetMaxOpenConns(50)
}
func getVideoMetadata(w http.ResponseWriter, r *http.Request) {
videoID := r.URL.Query().Get("id")
// 检查内存缓存
if val, ok := cache.Load(videoID); ok {
json.NewEncoder(w).Encode(val)
return
}
// 检查Redis缓存
ctx := context.Background()
val, err := rdb.Get(ctx, "video:"+videoID).Result()
if err == nil {
var data map[string]interface{}
json.Unmarshal([]byte(val), &data)
cache.Store(videoID, data)
json.NewEncoder(w).Encode(data)
return
}
// 数据库查询
row := db.QueryRowContext(ctx,
"SELECT id, title, duration FROM videos WHERE id = $1", videoID)
var video Video
if err := row.Scan(&video.ID, &video.Title, &video.Duration); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
// 缓存结果
videoJSON, _ := json.Marshal(video)
rdb.Set(ctx, "video:"+videoID, videoJSON, 10*time.Minute)
cache.Store(videoID, video)
json.NewEncoder(w).Encode(video)
}
type Video struct {
ID string `json:"id"`
Title string `json:"title"`
Duration float64 `json:"duration"`
}

