golang之媒体处理

[视频]

获取视频封面图:

1) 如果是使用oss的话, 可以添加指定的后缀生成指定图片

 视频截帧: https://help.aliyun.com/zh/oss/user-guide/video-snapshots?spm=a2c4g.11186623.0.0.8ea266d4kR5bST

 

2) 借助第三方包实现: https://github.com/u2takey/ffmpeg-go

注: 他是需要依赖ffmpeg的

package main

import (
    "bytes"
    "fmt"
    "github.com/disintegration/imaging"
    ffmpeg "github.com/u2takey/ffmpeg-go"
    "log"
    "os"
    "strings"
)

func main() {
    var videoPath = "./test.mp4"
    videoPath = "https://hk-cloud-screen-test.oss-cn-hangzhou.aliyuncs.com/ai_moment/108f17a6-2f94-4b2d-8667-d369058a5455.mp4"
    snapshotName, err := GetSnapshot(videoPath, "test", 1)
    if err != nil {
        fmt.Println(snapshotName, err)
    }
}

func GetSnapshot(videoPath, snapshotPath string, frameNum int) (snapshotName string, err error) {
    buf := bytes.NewBuffer(nil)
    err = ffmpeg.Input(videoPath).
        Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
        Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
        WithOutput(buf, os.Stdout).
        Run()

    if err != nil {
        log.Fatal("生成缩略图失败:", err)
        return "", err
    }

    img, err := imaging.Decode(buf)
    if err != nil {
        log.Fatal("生成缩略图失败:", err)
        return "", err
    }

    err = imaging.Save(img, snapshotPath+".png")
    if err != nil {
        log.Fatal("生成缩略图失败:", err)
        return "", err
    }

    names := strings.Split(snapshotPath, "\\")
    snapshotName = names[len(names)-1] + ".png"
    return
}

 还支持更多操作 转GIF,视频格式转换等操作

 

 

获取视频长度:

1)若是oss上的视频,则可以使用阿里云的IMM中的提取视频信息的服务

注意这里获取需要使用到签名之后获取对应的数据

获取视频信息: https://help.aliyun.com/zh/oss/user-guide/video-information-extraction?spm=a2c4g.11186623.0.0.93817902yhOJ2r

这里使用基于阿里云oss包: 

github.com/aliyun/aliyun-oss-go-sdk/oss
 示例:
client, err := oss.New("oss-cn-hangzhou.aliyuncs.com", "accessKeyID", "accessKeySecret")
if err != nil {
    panic(err)
}
bucketName := "buekct名称"
bucket, err := client.Bucket(bucketName)
if err != nil {
    panic(err)
}


objectKey := "pp/cbe1655d-9f67-490c-901a-20932f79443c.mp4"

// Get object
options := []oss.Option{
    oss.AddParam("x-oss-process", "video/info"),
}

signedURL, err := bucket.SignURL(objectKey, oss.HTTPGet, 10*60, options...)
if err != nil {
    panic(err)
}
fmt.Println(signedURL)

// 获取请求信息
resp, err := http.Get(signedURL)
if err != nil {
    panic(err)
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
fmt.Println("data:", string(body))

 

 

 

2)对于在线视频

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "io"
    "os"
)

// 获取本地文件视频秒数

// BoxHeader 信息头
type BoxHeader struct {
    Size       uint32
    FourccType [4]byte
    Size64     uint64
}

func main() {
    url := "./video.mp4"
    file, err := os.Open(url)
    if err != nil {
        panic(err)
    }
    duration, err := GetMP4Duration(file)
    if err != nil {
        panic(err)
    }
    fmt.Println(duration)
}

// GetMP4Duration 获取视频时长,以秒计
func GetMP4Duration(reader io.ReaderAt) (lengthOfTime uint32, err error) {
    var info = make([]byte, 0x10)
    var boxHeader BoxHeader
    var offset int64 = 0
    // 获取moov结构偏移
    for {
        _, err = reader.ReadAt(info, offset)
        if err != nil {
            return
        }
        boxHeader = getHeaderBoxInfo(info)
        fourccType := getFourccType(boxHeader)
        if fourccType == "moov" {
            break
        }
        // 有一部分mp4 mdat尺寸过大需要特殊处理
        if fourccType == "mdat" {
            if boxHeader.Size == 1 {
                offset += int64(boxHeader.Size64)
                continue
            }
        }
        offset += int64(boxHeader.Size)
    }
    // 获取moov结构开头一部分
    moovStartBytes := make([]byte, 0x100)
    _, err = reader.ReadAt(moovStartBytes, offset)
    if err != nil {
        return
    }
    // 定义timeScale与Duration偏移
    timeScaleOffset := 0x1C
    durationOffest := 0x20
    timeScale := binary.BigEndian.Uint32(moovStartBytes[timeScaleOffset : timeScaleOffset+4])
    Duration := binary.BigEndian.Uint32(moovStartBytes[durationOffest : durationOffest+4])
    lengthOfTime = Duration / timeScale
    return
}

// getHeaderBoxInfo 获取头信息
func getHeaderBoxInfo(data []byte) (boxHeader BoxHeader) {
    buf := bytes.NewBuffer(data)
    binary.Read(buf, binary.BigEndian, &boxHeader)
    return
}

// getFourccType 获取信息头类型
func getFourccType(boxHeader BoxHeader) (fourccType string) {
    fourccType = string(boxHeader.FourccType[:])
    return
}

另外还有其他第三方库可以使用:

go get -u github.com/Stitch-Zhang/gmp4

 可以处理非下载模式的在线资源

 

 

可以借助ffmpeg, 添加到环境变量中

https://github.com/BtbN/FFmpeg-Builds

package main

import (
    "context"
    "fmt"
    "gopkg.in/vansante/go-ffprobe.v2"
    "log"
    "time"
)

func main() {

    api := "https://www.runoob.com/try/demo_source/movie.mp4"
    //api := "https://hk-ai-test.oss-cn-hangzhou.aliyuncs.com/volcVideo/ac6f7816-d0de-4ddd-b57d-590cc5e2e94f_0.mp4"
    ctx, cancelFunc := context.WithTimeout(context.Background(), 20*time.Second)
    defer cancelFunc()

    data, err := ffprobe.ProbeURL(ctx, api)
    if err != nil {
        panic(err)
    }

    if data == nil {
        log.Fatal("获取数据为空")
    }

    fmt.Println(data.Format.Duration().Seconds())
}

 

 

 

视频上传oss:

    videoUrl := "https://muse.console.volcengine.com/api/storage/objects/media/7307071523758653478_origin.mp4?x-muse-token=ChUIARABGIzen6sGIIyBpasGKgM2MTMSmQEKDgiggI3e06eehGQQl5UBEgAaAhgCIggKAmxmEgJjbioGCNo0EIYDMikKJ3siYml6X2lkIjoiMTAwMDAwMDAxIiwiZGVyaXZlZF91aWQiOiIifUIUChJpY2FyY2guaWFtLmF1dGhycGNKLi9hcGkvc3RvcmFnZS9vYmplY3RzL21lZGlhLzczMDcwNzE1MjM3NTg2NTM0Nzga2AJpSExFbFAzM0RjWFZLSzJDenk5UXVlU3QxZXRidlkwSDZnaFBDdFB2VkZ4YjU3YjFnVlpmdlBkaEFtK3pZeEx1U3N2aWEydmZ1RVBaRStFNmd0aFdJSk5sUmFVM3RvNDkvWTZ1ME1IWUlOeVY5bjdicWxCZFI3ZGhZT2VRd3dZS2dXSEdhTzl5VllxSVRQcTgvdk14M3lXd1hvQ21LWjYwY0tUN2dBMlFjQStIN0cxWnJwRzc0bzU2UEtqd3ZTbVVPYzBLdlhtN1pJVTROOStTVFVoaExZdGpMUzFWVEo2Z3FqalVZeGMxV0FsTkN0WXJ6YitabitJandHMTl1WGMveHJZb2xOeFhZQk9sZ3JwNXBQTmV5QmRBYVdQSHpPdDlTbHpITytpeXJITzR3SmRQc0FWQ1E0cXJPbnpPaGJlWEdTVENZME5KelJISnJzT0ZuZE4xTFE9PQ==&infer_mime=ext"
    resp, err := http.Get(videoUrl)
    if err != nil {
        fmt.Println("网络发生错误")
        return
    }
    if resp.StatusCode == http.StatusNotFound {
        fmt.Println("图片资源不存在")
        return
    }
    defer resp.Body.Close()

    fmt.Println(resp.StatusCode)
    // 读取文件内容
    body, err := io.ReadAll(resp.Body)

    // 读取本地文件并上传
    //videoPath := "./video.mp4"
    //f, err := os.Open(videoPath)
    //if err != nil {
    //  panic(err)
    //}
    //fmt.Println("-----读取视频文件")
    //defer f.Close()
    //data := make([]byte, 0)
    //n, err := f.Read(data)
    //fmt.Println(n, err)
    //
    err = bucket.PutObject("demo.mp4", bytes.NewReader(body))
    fmt.Println("上传结果:", err)

 

 

[图片]

上传图片到oss:

//imgPath := "./oss.jpg"
    // 本地读取文件后上传

    //f, err := os.OpenFile(imgPath, os.O_RDONLY, 0644)
    //if err != nil {
    //  panic(err)
    //}
    //defer f.Close()
    //
    //stat, err := f.Stat()
    //if err != nil {
    //  panic(err)
    //}
    //
    //// 创建一个byte切片
    //data := make([]byte, stat.Size())
    //count, err := f.Read(data)
    //fmt.Println("文件长度:", count)

    // 读取一个文件base64
    pictureUrl := "http://pic.netbian.com/uploads/allimg/210423/224716-1619189236e4d9.jpg"
    //pictureUrl := "http://pic.netbian.com/uploads/allimg/210423/224716-16191892369.jpg" // 不存在的图片
    resp, err := http.Get(pictureUrl)
    if err != nil {
        fmt.Println("网络发生错误")
        return
    }
    if resp.StatusCode == http.StatusNotFound {
        fmt.Println("图片资源不存在")
        return
    }
    defer resp.Body.Close()

    fmt.Println(resp.StatusCode)
    // 读取文件内容
    body, err := io.ReadAll(resp.Body)
    // 将[]byte数据转换成base64的字符串
    imgBase64 := base64.StdEncoding.EncodeToString(body)
    fmt.Println("img base64:", len(imgBase64))
    // 解析base64字符串到[]byte
    decode, err := base64.StdEncoding.DecodeString(imgBase64)

    err = bucket.PutObject("test222.jpg", bytes.NewReader(decode))

    // 从本地文件上传
    //err = bucket.PutObjectFromFile("test.jpg", imgPath)
    if err != nil {
        // HandleError(err)
        panic(err)
    }
    fmt.Println("上传图片成功")

 

获取远程图片信息方法:

func GetImageInfo(imgUrl string) (*types.ImgInfo, error) {
    resp, err := http.Get(imgUrl)
    if err != nil {
        return nil, err
    }
    if resp.StatusCode != http.StatusOK {
        logx.Infof("[获取图片信息]失败:%v 状态码", err, resp.StatusCode)
        return nil, errors.New("获取远程图片失败")
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    resp.Body = io.NopCloser(bytes.NewReader(body))

    m, _, err := image.Decode(resp.Body)
    if err != nil {
        return nil, err
    }

    imgBase64 := base64.StdEncoding.EncodeToString(body)
    return &types.ImgInfo{
        Width:     m.Bounds().Dx(),
        Heigh:     m.Bounds().Dy(),
        Base64Str: imgBase64,
    }, nil
}

 

注:

1. 对于上传到oss的资源,如果使用未绑定域名方式的话, 他会默认附件下载的方式, 如果绑定了域名之后,可以在线查看资源

 

posted @ 2023-12-05 10:28  X-Wolf  阅读(147)  评论(0编辑  收藏  举报