feat: 🎸 视频上传接口
hls切片
This commit is contained in:
parent
e88c0b4adb
commit
b8cfc8d9cf
@ -6,8 +6,9 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"hotgo/internal/model/input/sysin"
|
"hotgo/internal/model/input/sysin"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UploadFileReq 上传文件
|
// UploadFileReq 上传文件
|
||||||
@ -17,6 +18,14 @@ type UploadFileReq struct {
|
|||||||
|
|
||||||
type UploadFileRes *sysin.AttachmentListModel
|
type UploadFileRes *sysin.AttachmentListModel
|
||||||
|
|
||||||
|
type UploadVideoReq struct {
|
||||||
|
g.Meta `path:"/upload/video" tags:"附件" method:"post" summary:"上传视频"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadVideoRes struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
// CheckMultipartReq 检查文件分片
|
// CheckMultipartReq 检查文件分片
|
||||||
type CheckMultipartReq struct {
|
type CheckMultipartReq struct {
|
||||||
g.Meta `path:"/upload/checkMultipart" tags:"附件" method:"post" summary:"检查文件分片"`
|
g.Meta `path:"/upload/checkMultipart" tags:"附件" method:"post" summary:"检查文件分片"`
|
||||||
|
@ -7,12 +7,17 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"hotgo/api/admin/common"
|
"hotgo/api/admin/common"
|
||||||
"hotgo/internal/library/storager"
|
"hotgo/internal/library/storager"
|
||||||
"hotgo/internal/service"
|
"hotgo/internal/service"
|
||||||
"hotgo/utility/validate"
|
"hotgo/utility/validate"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Upload = new(cUpload)
|
var Upload = new(cUpload)
|
||||||
@ -36,6 +41,70 @@ func (c *cUpload) UploadFile(ctx context.Context, _ *common.UploadFileReq) (res
|
|||||||
return service.CommonUpload().UploadFile(ctx, uploadType, file)
|
return service.CommonUpload().UploadFile(ctx, uploadType, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UploadVideo 上传视频
|
||||||
|
func (c *cUpload) UploadVideo(ctx context.Context, _ *common.UploadVideoReq) (res common.UploadVideoRes, err error) {
|
||||||
|
r := g.RequestFromCtx(ctx)
|
||||||
|
uploadType := r.Header.Get("uploadType")
|
||||||
|
if uploadType != "default" && !validate.InSlice(storager.KindSlice, uploadType) {
|
||||||
|
err = gerror.New("上传类型是无效的")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file := r.GetUploadFile("file")
|
||||||
|
if file == nil {
|
||||||
|
err = gerror.New("没有找到上传的文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 保存上传视频到本地临时目录
|
||||||
|
tmpDir := "./tmp/video"
|
||||||
|
_, _ = file.Save(tmpDir)
|
||||||
|
tmpFile := tmpDir + "/" + file.Filename
|
||||||
|
|
||||||
|
// 2. 用ffmpeg切片为m3u8和ts文件
|
||||||
|
|
||||||
|
m3u8UUID := uuid.New().String()
|
||||||
|
|
||||||
|
hlsDir := "./tmp/hls/" + m3u8UUID
|
||||||
|
_ = os.MkdirAll(hlsDir, 0755)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(hlsDir)
|
||||||
|
}()
|
||||||
|
|
||||||
|
m3u8File := m3u8UUID + ".m3u8"
|
||||||
|
m3u8Path := hlsDir + "/" + m3u8File
|
||||||
|
ffmpegCmd := exec.Command("ffmpeg", "-i", tmpFile, "-c:v", "libx264", "-hls_time", "10", "-hls_playlist_type", "vod", m3u8Path)
|
||||||
|
err = ffmpegCmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
err = gerror.Wrap(err, "ffmpeg切片失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 遍历切片目录,上传所有m3u8和ts文件到minio
|
||||||
|
minioDrive := new(storager.MinioDrive)
|
||||||
|
var m3u8MinioPath string
|
||||||
|
files, _ := filepath.Glob(hlsDir + "/*")
|
||||||
|
for _, f := range files {
|
||||||
|
fileInfo, errStat := os.Stat(f)
|
||||||
|
if errStat == nil && !fileInfo.IsDir() {
|
||||||
|
minioPath, err2 := minioDrive.UploadFromPath(ctx, f, filepath.Base(f))
|
||||||
|
if err2 != nil {
|
||||||
|
err = err2
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if filepath.Ext(f) == ".m3u8" {
|
||||||
|
m3u8MinioPath = minioPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 返回m3u8文件的minio路径
|
||||||
|
return common.UploadVideoRes{
|
||||||
|
Path: m3u8MinioPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CheckMultipart 检查文件分片
|
// CheckMultipart 检查文件分片
|
||||||
func (c *cUpload) CheckMultipart(ctx context.Context, req *common.CheckMultipartReq) (res *common.CheckMultipartRes, err error) {
|
func (c *cUpload) CheckMultipart(ctx context.Context, req *common.CheckMultipartReq) (res *common.CheckMultipartRes, err error) {
|
||||||
data, err := service.CommonUpload().CheckMultipart(ctx, &req.CheckMultipartInp)
|
data, err := service.CommonUpload().CheckMultipart(ctx, &req.CheckMultipartInp)
|
||||||
|
@ -7,14 +7,18 @@ package storager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"mime"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/net/ghttp"
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
"github.com/gogf/gf/v2/os/gfile"
|
||||||
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||||
"mime"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MinioDrive minio对象存储驱动
|
// MinioDrive minio对象存储驱动
|
||||||
@ -63,6 +67,59 @@ func (d *MinioDrive) Upload(ctx context.Context, file *ghttp.UploadFile) (fullPa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UploadFromPath 从本地文件上传
|
||||||
|
func (d *MinioDrive) UploadFromPath(ctx context.Context, path string, filename string) (fullPath string, err error) {
|
||||||
|
if config.MinioPath == "" {
|
||||||
|
err = gerror.New("minio存储驱动必须配置存储路径!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := minio.New(config.MinioEndpoint, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(config.MinioAccessKey, config.MinioSecretKey, ""),
|
||||||
|
Secure: config.MinioUseSSL == 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s3utils.CheckValidBucketName(config.MinioBucket); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath = config.MinioPath + gtime.Date() + "/" + filename
|
||||||
|
if err = s3utils.CheckValidObjectName(fullPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
return "", fmt.Errorf("上传路径是目录: %s", path)
|
||||||
|
}
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
stat, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := minio.PutObjectOptions{
|
||||||
|
ContentType: mime.TypeByExtension(filepath.Ext(path)),
|
||||||
|
}
|
||||||
|
if opts.ContentType == "" {
|
||||||
|
opts.ContentType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.PutObject(ctx, config.MinioBucket, fullPath, file, stat.Size(), opts)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// CreateMultipart 创建分片事件
|
// CreateMultipart 创建分片事件
|
||||||
func (d *MinioDrive) CreateMultipart(ctx context.Context, in *CheckMultipartParams) (res *MultipartProgress, err error) {
|
func (d *MinioDrive) CreateMultipart(ctx context.Context, in *CheckMultipartParams) (res *MultipartProgress, err error) {
|
||||||
err = gerror.New("当前驱动暂不支持分片上传!")
|
err = gerror.New("当前驱动暂不支持分片上传!")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user