无需保存视频,如何从字节数组提取时长与缩略图?
直接从视频字节数组提取时长与缩略图(无需保存文件)
完全可以借助FFmpeg实现,不需要先将字节数组保存为本地文件再读取。在Go中有两种常用方案:
方案一:使用FFmpeg的Go绑定库(如ffmpeg-go)
ffmpeg-go封装了FFmpeg的API,支持直接从io.Reader读取数据,刚好匹配你手里的fileBytes字节数组。
实现示例
package utils import ( "bytes" "fmt" "image" "image/jpeg" "github.com/u2takey/ffmpeg-go" ) func ExtractDurationAndFrameFromVideo(fileBytes []byte) (float64, image.Image, error) { // 将字节数组转为io.Reader videoReader := bytes.NewReader(fileBytes) // 提取视频时长 durationCtx, err := ffmpeg.ProbeReader(videoReader) if err != nil { return 0, nil, fmt.Errorf("probe video failed: %w", err) } duration, err := durationCtx.GetDuration() if err != nil { return 0, nil, fmt.Errorf("get duration failed: %w", err) } // 提取缩略图(取视频第1秒的帧) thumbnailBuf := &bytes.Buffer{} // 重置reader指针,因为ProbeReader已经读取过一次 videoReader.Seek(0, 0) err = ffmpeg.Input("pipe:0", ffmpeg.KwArgs{"f": "mp4"}) // 根据实际视频格式调整f参数 .Filter("select", ffmpeg.Args{"gte(t,1)"}) .Output("pipe:1", ffmpeg.KwArgs{"vframes": 1, "f": "image2", "vcodec": "mjpeg"}) .WithInput(videoReader) .WithOutput(thumbnailBuf) .Run() if err != nil { return 0, nil, fmt.Errorf("extract thumbnail failed: %w", err) } // 将JPEG字节转为image.Image thumbnailImg, err := jpeg.Decode(thumbnailBuf) if err != nil { return 0, nil, fmt.Errorf("decode thumbnail failed: %w", err) } return duration, thumbnailImg, nil }
方案二:直接调用FFmpeg命令(通过管道传递数据)
如果你不想引入第三方库,可以直接用Go的os/exec包调用系统中的FFmpeg命令,通过标准输入(stdin)传入视频字节数组,从标准输出(stdout)获取结果。
实现示例
package utils import ( "bytes" "fmt" "image" "image/jpeg" "os/exec" "strconv" "strings" ) func ExtractDurationAndFrameFromVideo(fileBytes []byte) (float64, image.Image, error) { // 提取视频时长 durationCmd := exec.Command("ffmpeg", "-i", "pipe:0", "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0") durationCmd.Stdin = bytes.NewReader(fileBytes) durationOutput, err := durationCmd.Output() if err != nil { return 0, nil, fmt.Errorf("ffmpeg get duration failed: %w", err) } durationStr := strings.TrimSpace(string(durationOutput)) duration, err := strconv.ParseFloat(durationStr, 64) if err != nil { return 0, nil, fmt.Errorf("parse duration failed: %w", err) } // 提取缩略图 thumbnailCmd := exec.Command("ffmpeg", "-i", "pipe:0", "-ss", "1", "-vframes", "1", "-f", "image2pipe", "-vcodec", "mjpeg", "-") thumbnailCmd.Stdin = bytes.NewReader(fileBytes) thumbnailOutput, err := thumbnailCmd.Output() if err != nil { return 0, nil, fmt.Errorf("ffmpeg extract thumbnail failed: %w", err) } thumbnailImg, err := jpeg.Decode(bytes.NewReader(thumbnailOutput)) if err != nil { return 0, nil, fmt.Errorf("decode thumbnail failed: %w", err) } return duration, thumbnailImg, nil }
注意事项
- 确保系统中已安装FFmpeg(方案二依赖系统FFmpeg,方案一也需要FFmpeg的动态库支持)
- 根据实际视频格式调整FFmpeg的参数(比如
-f指定的格式) - 处理大视频时,可优化为边接收gRPC流边向FFmpeg管道写入,无需一次性将所有字节存到
fileBytes中,降低内存占用
内容的提问来源于stack exchange,提问作者Humam Husam Eldeen
相关产品推荐
相关产品推荐

